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 /WebCore/dom/SelectElement.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 'WebCore/dom/SelectElement.cpp')
-rw-r--r-- | WebCore/dom/SelectElement.cpp | 1042 |
1 files changed, 0 insertions, 1042 deletions
diff --git a/WebCore/dom/SelectElement.cpp b/WebCore/dom/SelectElement.cpp deleted file mode 100644 index 661ba88..0000000 --- a/WebCore/dom/SelectElement.cpp +++ /dev/null @@ -1,1042 +0,0 @@ -/* - * 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 "SpatialNavigation.h" -#include <wtf/Assertions.h> - -#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) || OS(FREEBSD))) -#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<bool>& lastOnChangeSelection = data.lastOnChangeSelection(); - lastOnChangeSelection.clear(); - - const Vector<Element*>& 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<Element*>& 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<Element*>& 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<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); - cachedStateForActiveSelection.clear(); - - const Vector<Element*>& 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.listItems(element).size() || data.activeSelectionAnchorIndex() >= 0); - - unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); - unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); - Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); - - const Vector<Element*>& 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]); - } - - toSelectElement(element)->updateValidity(); - scrollToSelection(data, element); -} - -void SelectElement::listBoxOnChange(SelectElementData& data, Element* element) -{ - ASSERT(!data.usesMenuList() || data.multiple()); - - Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection(); - const Vector<Element*>& 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<Element*>& 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<Element*>(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 <optgroup> and <option> will be found - // within a <select>. We call traverseNextSibling so that we only step - // into those tags that we choose to. For web-compat, we should cope - // with the case where odd tags like a <div> have been added but we - // handle this because such tags have already been removed from the - // <select>'s subtree at this point. - currentNode = currentNode->traverseNextSibling(element); - } -} - -int SelectElement::selectedIndex(const SelectElementData& data, const Element* element) -{ - unsigned index = 0; - - // return the number of the first option selected - const Vector<Element*>& items = data.listItems(element); - for (size_t i = 0; i < items.size(); ++i) { - if (OptionElement* optionElement = toOptionElement(items[i])) { - if (optionElement->selected()) - return index; - ++index; - } - } - - return -1; -} - -void SelectElement::setSelectedIndex(SelectElementData& data, Element* element, int optionIndex, bool deselect, bool fireOnChangeNow, bool userDrivenChange) -{ - const Vector<Element*>& items = data.listItems(element); - int listIndex = optionToListIndex(data, element, optionIndex); - if (!data.multiple()) - deselect = true; - - Element* excludeElement = 0; - if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) { - excludeElement = items[listIndex]; - if (data.activeSelectionAnchorIndex() < 0 || deselect) - setActiveSelectionAnchorIndex(data, element, listIndex); - if (data.activeSelectionEndIndex() < 0 || deselect) - setActiveSelectionEndIndex(data, listIndex); - optionElement->setSelectedState(true); - } - - if (deselect) - deselectItems(data, element, excludeElement); - - // For the menu list case, this is what makes the selected element appear. - if (RenderObject* renderer = element->renderer()) - renderer->updateFromElement(); - - scrollToSelection(data, element); - - // This only gets called with fireOnChangeNow for menu lists. - if (data.usesMenuList()) { - data.setUserDrivenChange(userDrivenChange); - if (fireOnChangeNow) - menuListOnChange(data, element); - RenderObject* renderer = element->renderer(); - if (renderer) { - if (data.usesMenuList()) - toRenderMenuList(renderer)->didSetSelectedIndex(); - else if (renderer->isListBox()) - toRenderListBox(renderer)->selectionChanged(); - } - } - - if (Frame* frame = element->document()->frame()) - frame->page()->chrome()->client()->formStateDidChange(element); -} - -int SelectElement::optionToListIndex(const SelectElementData& data, const Element* element, int optionIndex) -{ - const Vector<Element*>& items = data.listItems(element); - int listSize = (int) items.size(); - if (optionIndex < 0 || optionIndex >= listSize) - return -1; - - int optionIndex2 = -1; - for (int listIndex = 0; listIndex < listSize; ++listIndex) { - if (isOptionElement(items[listIndex])) { - ++optionIndex2; - if (optionIndex2 == optionIndex) - return listIndex; - } - } - - return -1; -} - -int SelectElement::listToOptionIndex(const SelectElementData& data, const Element* element, int listIndex) -{ - const Vector<Element*>& items = data.listItems(element); - if (listIndex < 0 || listIndex >= int(items.size()) || - !isOptionElement(items[listIndex])) - return -1; - - int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list - for (int i = 0; i < listIndex; ++i) - if (isOptionElement(items[i])) - ++optionIndex; - - return optionIndex; -} - -void SelectElement::dispatchFocusEvent(SelectElementData& data, Element* element) -{ - // Save the selection so it can be compared to the new selection when dispatching change events during blur event dispatchal - if (data.usesMenuList()) - saveLastSelection(data, element); -} - -void SelectElement::dispatchBlurEvent(SelectElementData& data, Element* element) -{ - // We only need to fire change events here for menu lists, because we fire change events for list boxes whenever the selection change is actually made. - // This matches other browsers' behavior. - if (data.usesMenuList()) - menuListOnChange(data, element); -} - -void SelectElement::deselectItems(SelectElementData& data, Element* element, Element* excludeElement) -{ - const Vector<Element*>& items = data.listItems(element); - for (unsigned i = 0; i < items.size(); ++i) { - if (items[i] == excludeElement) - continue; - - if (OptionElement* optionElement = toOptionElement(items[i])) - optionElement->setSelectedState(false); - } -} - -bool SelectElement::saveFormControlState(const SelectElementData& data, const Element* element, String& value) -{ - const Vector<Element*>& items = data.listItems(element); - int length = items.size(); - - // FIXME: Change this code to use the new StringImpl::createUninitialized code path. - Vector<char, 1024> characters(length); - for (int i = 0; i < length; ++i) { - OptionElement* optionElement = toOptionElement(items[i]); - bool selected = optionElement && optionElement->selected(); - characters[i] = selected ? 'X' : '.'; - } - - value = String(characters.data(), length); - return true; -} - -void SelectElement::restoreFormControlState(SelectElementData& data, Element* element, const String& state) -{ - recalcListItems(data, element); - - const Vector<Element*>& items = data.listItems(element); - int length = items.size(); - - for (int i = 0; i < length; ++i) { - if (OptionElement* optionElement = toOptionElement(items[i])) - optionElement->setSelectedState(state[i] == 'X'); - } - - setOptionsChangedOnRenderer(data, element); -} - -void SelectElement::parseMultipleAttribute(SelectElementData& data, Element* element, Attribute* attribute) -{ - bool oldUsesMenuList = data.usesMenuList(); - data.setMultiple(!attribute->isNull()); - toSelectElement(element)->updateValidity(); - if (oldUsesMenuList != data.usesMenuList() && element->attached()) { - element->detach(); - element->attach(); - } -} - -bool SelectElement::appendFormData(SelectElementData& data, Element* element, FormDataList& list) -{ - const AtomicString& name = element->formControlName(); - if (name.isEmpty()) - return false; - - bool successful = false; - const Vector<Element*>& items = data.listItems(element); - - for (unsigned i = 0; i < items.size(); ++i) { - OptionElement* optionElement = toOptionElement(items[i]); - if (optionElement && optionElement->selected() && !optionElement->disabled()) { - list.appendData(name, optionElement->value()); - successful = true; - } - } - - // It's possible that this is a menulist with multiple options and nothing - // will be submitted (!successful). We won't send a unselected non-disabled - // option as fallback. This behavior matches to other browsers. - return successful; -} - -void SelectElement::reset(SelectElementData& data, Element* element) -{ - OptionElement* firstOption = 0; - OptionElement* selectedOption = 0; - - const Vector<Element*>& items = data.listItems(element); - for (unsigned i = 0; i < items.size(); ++i) { - OptionElement* optionElement = toOptionElement(items[i]); - if (!optionElement) - continue; - - if (items[i]->fastHasAttribute(HTMLNames::selectedAttr)) { - if (selectedOption && !data.multiple()) - selectedOption->setSelectedState(false); - optionElement->setSelectedState(true); - selectedOption = optionElement; - } else - optionElement->setSelectedState(false); - - if (!firstOption) - firstOption = optionElement; - } - - if (!selectedOption && firstOption && !data.multiple() && data.size() <= 1) - firstOption->setSelectedState(true); - - setOptionsChangedOnRenderer(data, element); - element->setNeedsStyleRecalc(); -} - -enum SkipDirection { - SkipBackwards = -1, - SkipForwards = 1 -}; - -// Returns the index of the next valid list item |skip| items past |listIndex| in direction |direction|. -static int nextValidIndex(const Vector<Element*>& listItems, int listIndex, SkipDirection direction, int skip) -{ - int lastGoodIndex = listIndex; - int size = listItems.size(); - for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) { - --skip; - if (!listItems[listIndex]->disabled() && isOptionElement(listItems[listIndex])) { - lastGoodIndex = listIndex; - if (skip <= 0) - break; - } - } - return lastGoodIndex; -} - -void SelectElement::menuListDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm) -{ - if (event->type() == eventNames().keydownEvent) { - if (!element->renderer() || !event->isKeyboardEvent()) - return; - - const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); - bool handled = false; - -#if ARROW_KEYS_POP_MENU - if (!isSpatialNavigationEnabled(element->document()->frame())) { - if (keyIdentifier == "Down" || keyIdentifier == "Up") { - element->focus(); - - if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event. - return; - - // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(data, element); - if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) - menuList->showPopup(); - - event->setDefaultHandled(); - } - return; - } -#endif - // When using spatial navigation, we want to be able to navigate away from the select element - // when the user hits any of the arrow keys, instead of changing the selection. - if (isSpatialNavigationEnabled(element->document()->frame())) - if (!data.activeSelectionState()) - return; - - UNUSED_PARAM(htmlForm); - const Vector<Element*>& listItems = data.listItems(element); - - int listIndex = optionToListIndex(data, element, selectedIndex(data, element)); - if (keyIdentifier == "Down" || keyIdentifier == "Right") { - listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 1); - handled = true; - } else if (keyIdentifier == "Up" || keyIdentifier == "Left") { - listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 1); - handled = true; - } else if (keyIdentifier == "PageDown") { - listIndex = nextValidIndex(listItems, listIndex, SkipForwards, 3); - handled = true; - } else if (keyIdentifier == "PageUp") { - listIndex = nextValidIndex(listItems, listIndex, SkipBackwards, 3); - handled = true; - } else if (keyIdentifier == "Home") { - listIndex = nextValidIndex(listItems, -1, SkipForwards, 1); - handled = true; - } else if (keyIdentifier == "End") { - listIndex = nextValidIndex(listItems, listItems.size(), SkipBackwards, 1); - handled = true; - } - - if (handled && listIndex >= 0 && (unsigned)listIndex < listItems.size()) - setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex)); - - if (handled) - event->setDefaultHandled(); - } - - // Use key press event here since sending simulated mouse events - // on key down blocks the proper sending of the key press event. - if (event->type() == eventNames().keypressEvent) { - if (!element->renderer() || !event->isKeyboardEvent()) - return; - - int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); - bool handled = false; - - if (keyCode == ' ' && isSpatialNavigationEnabled(element->document()->frame())) { - // Use space to toggle arrow key handling for selection change or spatial navigation. - data.setActiveSelectionState(!data.activeSelectionState()); - event->setDefaultHandled(); - return; - } - -#if SPACE_OR_RETURN_POP_MENU - if (keyCode == ' ' || keyCode == '\r') { - element->focus(); - - if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event. - return; - - // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(data, element); - if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) - menuList->showPopup(); - handled = true; - } -#elif ARROW_KEYS_POP_MENU - if (keyCode == ' ') { - element->focus(); - - if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event. - return; - - // Save the selection so it can be compared to the new selection when dispatching change events during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(data, element); - if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) - menuList->showPopup(); - handled = true; - } else if (keyCode == '\r') { - if (htmlForm) - htmlForm->submitImplicitly(event, false); - menuListOnChange(data, element); - handled = true; - } -#else - int listIndex = optionToListIndex(data, element, selectedIndex(data, element)); - if (keyCode == '\r') { - // listIndex should already be selected, but this will fire the onchange handler. - setSelectedIndex(data, element, listToOptionIndex(data, element, listIndex), true, true); - handled = true; - } -#endif - if (handled) - event->setDefaultHandled(); - } - - if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { - element->focus(); - if (element->renderer() && element->renderer()->isMenuList()) { - if (RenderMenuList* menuList = toRenderMenuList(element->renderer())) { - if (menuList->popupIsVisible()) - menuList->hidePopup(); - else { - // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(data, element); - menuList->showPopup(); - } - } - } - event->setDefaultHandled(); - } -} - -void SelectElement::updateSelectedState(SelectElementData& data, Element* element, int listIndex, - bool multi, bool shift) -{ - ASSERT(listIndex >= 0); - - // Save the selection so it can be compared to the new selection when dispatching change events during mouseup, or after autoscroll finishes. - saveLastSelection(data, element); - - data.setActiveSelectionState(true); - - bool shiftSelect = data.multiple() && shift; - bool multiSelect = data.multiple() && multi && !shift; - - Element* clickedElement = data.listItems(element)[listIndex]; - OptionElement* option = toOptionElement(clickedElement); - if (option) { - // Keep track of whether an active selection (like during drag selection), should select or deselect - if (option->selected() && multi) - data.setActiveSelectionState(false); - - if (!data.activeSelectionState()) - option->setSelectedState(false); - } - - // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option. - // If no option was clicked, then this will deselect all items in the list. - if (!shiftSelect && !multiSelect) - deselectItems(data, element, clickedElement); - - // If the anchor hasn't been set, and we're doing a single selection or a shift selection, then initialize the anchor to the first selected index. - if (data.activeSelectionAnchorIndex() < 0 && !multiSelect) - setActiveSelectionAnchorIndex(data, element, selectedIndex(data, element)); - - // Set the selection state of the clicked option - if (option && !clickedElement->disabled()) - option->setSelectedState(true); - - // If there was no selectedIndex() for the previous initialization, or - // If we're doing a single selection, or a multiple selection (using cmd or ctrl), then initialize the anchor index to the listIndex that just got clicked. - if (data.activeSelectionAnchorIndex() < 0 || !shiftSelect) - setActiveSelectionAnchorIndex(data, element, listIndex); - - setActiveSelectionEndIndex(data, listIndex); - updateListBoxSelection(data, element, !multiSelect); -} - -void SelectElement::listBoxDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm) -{ - const Vector<Element*>& listItems = data.listItems(element); - - if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { - element->focus(); - - if (!element->renderer()) // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event. - return; - - // Convert to coords relative to the list box if needed. - MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); - IntPoint localOffset = roundedIntPoint(element->renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); - int listIndex = toRenderListBox(element->renderer())->listIndexAtOffset(localOffset.x(), localOffset.y()); - if (listIndex >= 0) { -#if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN)) - updateSelectedState(data, element, listIndex, mouseEvent->metaKey(), mouseEvent->shiftKey()); -#else - updateSelectedState(data, element, listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey()); -#endif - if (Frame* frame = element->document()->frame()) - frame->eventHandler()->setMouseDownMayStartAutoscroll(); - - event->setDefaultHandled(); - } - } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && element->document()->frame()->eventHandler()->autoscrollRenderer() != element->renderer()) { - // This makes sure we fire dispatchFormControlChangeEvent for a single click. For drag selection, onChange will fire when the autoscroll timer stops. - listBoxOnChange(data, element); - } else if (event->type() == eventNames().keydownEvent) { - if (!event->isKeyboardEvent()) - return; - const String& keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); - - int endIndex = 0; - if (data.activeSelectionEndIndex() < 0) { - // Initialize the end index - if (keyIdentifier == "Down") - endIndex = nextSelectableListIndex(data, element, lastSelectedListIndex(data, element)); - else if (keyIdentifier == "Up") - endIndex = previousSelectableListIndex(data, element, optionToListIndex(data, element, selectedIndex(data, element))); - } else { - // Set the end index based on the current end index - if (keyIdentifier == "Down") - endIndex = nextSelectableListIndex(data, element, data.activeSelectionEndIndex()); - else if (keyIdentifier == "Up") - endIndex = previousSelectableListIndex(data, element, data.activeSelectionEndIndex()); - } - - if (isSpatialNavigationEnabled(element->document()->frame())) - // Check if the selection moves to the boundary. - if (keyIdentifier == "Left" || keyIdentifier == "Right" || ((keyIdentifier == "Down" || keyIdentifier == "Up") && endIndex == data.activeSelectionEndIndex())) - return; - - if (keyIdentifier == "Down" || keyIdentifier == "Up") { - // Save the selection so it can be compared to the new selection when dispatching change events immediately after making the new selection. - saveLastSelection(data, element); - - ASSERT_UNUSED(listItems, !listItems.size() || (endIndex >= 0 && (unsigned) endIndex < listItems.size())); - setActiveSelectionEndIndex(data, endIndex); - - // If the anchor is unitialized, or if we're going to deselect all other options, then set the anchor index equal to the end index. - bool deselectOthers = !data.multiple() || !static_cast<KeyboardEvent*>(event)->shiftKey(); - if (data.activeSelectionAnchorIndex() < 0 || deselectOthers) { - data.setActiveSelectionState(true); - if (deselectOthers) - deselectItems(data, element); - setActiveSelectionAnchorIndex(data, element, data.activeSelectionEndIndex()); - } - - toRenderListBox(element->renderer())->scrollToRevealElementAtListIndex(endIndex); - updateListBoxSelection(data, element, deselectOthers); - listBoxOnChange(data, element); - event->setDefaultHandled(); - } - } else if (event->type() == eventNames().keypressEvent) { - if (!event->isKeyboardEvent()) - return; - int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); - - if (keyCode == '\r') { - if (htmlForm) - htmlForm->submitImplicitly(event, false); - event->setDefaultHandled(); - return; - } - } -} - -void SelectElement::defaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm) -{ - if (!element->renderer()) - return; - - if (data.usesMenuList()) - menuListDefaultEventHandler(data, element, event, htmlForm); - else - listBoxDefaultEventHandler(data, element, event, htmlForm); - - if (event->defaultHandled()) - return; - - if (event->type() == eventNames().keypressEvent && event->isKeyboardEvent()) { - KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(event); - if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && isPrintableChar(keyboardEvent->charCode())) { - typeAheadFind(data, element, keyboardEvent); - event->setDefaultHandled(); - return; - } - } -} - -int SelectElement::lastSelectedListIndex(const SelectElementData& data, const Element* element) -{ - // return the number of the last option selected - unsigned index = 0; - bool found = false; - const Vector<Element*>& items = data.listItems(element); - for (size_t i = 0; i < items.size(); ++i) { - if (OptionElement* optionElement = toOptionElement(items[i])) { - if (optionElement->selected()) { - index = i; - found = true; - } - } - } - - return found ? (int) index : -1; -} - -static String stripLeadingWhiteSpace(const String& string) -{ - int length = string.length(); - - int i; - for (i = 0; i < length; ++i) { - if (string[i] != noBreakSpace && (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral))) - break; - } - - return string.substring(i, length - i); -} - -void SelectElement::typeAheadFind(SelectElementData& data, Element* element, KeyboardEvent* event) -{ - if (event->timeStamp() < data.lastCharTime()) - return; - - DOMTimeStamp delta = event->timeStamp() - data.lastCharTime(); - data.setLastCharTime(event->timeStamp()); - - UChar c = event->charCode(); - - String prefix; - int searchStartOffset = 1; - if (delta > typeAheadTimeout) { - prefix = String(&c, 1); - data.setTypedString(prefix); - data.setRepeatingChar(c); - } else { - data.typedString().append(c); - - if (c == data.repeatingChar()) - // The user is likely trying to cycle through all the items starting with this character, so just search on the character - prefix = String(&c, 1); - else { - data.setRepeatingChar(0); - prefix = data.typedString(); - searchStartOffset = 0; - } - } - - const Vector<Element*>& items = data.listItems(element); - int itemCount = items.size(); - if (itemCount < 1) - return; - - int selected = selectedIndex(data, element); - int index = (optionToListIndex(data, element, selected >= 0 ? selected : 0) + searchStartOffset) % itemCount; - ASSERT(index >= 0); - - // Compute a case-folded copy of the prefix string before beginning the search for - // a matching element. This code uses foldCase to work around the fact that - // String::startWith does not fold non-ASCII characters. This code can be changed - // to use startWith once that is fixed. - String prefixWithCaseFolded(prefix.foldCase()); - for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) { - OptionElement* optionElement = toOptionElement(items[index]); - if (!optionElement || items[index]->disabled()) - continue; - - // Fold the option string and check if its prefix is equal to the folded prefix. - String text = optionElement->textIndentedToRespectGroupLabel(); - if (stripLeadingWhiteSpace(text).foldCase().startsWith(prefixWithCaseFolded)) { - setSelectedIndex(data, element, listToOptionIndex(data, element, index)); - if (!data.usesMenuList()) - listBoxOnChange(data, element); - - setOptionsChangedOnRenderer(data, element); - element->setNeedsStyleRecalc(); - return; - } - } -} - -void SelectElement::insertedIntoTree(SelectElementData& data, Element* element) -{ - // When the element is created during document parsing, it won't have any items yet - but for innerHTML - // and related methods, this method is called after the whole subtree is constructed. - recalcListItems(data, element, true); -} - -void SelectElement::accessKeySetSelectedIndex(SelectElementData& data, Element* element, int index) -{ - // first bring into focus the list box - if (!element->focused()) - element->accessKeyAction(false); - - // if this index is already selected, unselect. otherwise update the selected index - const Vector<Element*>& items = data.listItems(element); - int listIndex = optionToListIndex(data, element, index); - if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) { - if (optionElement->selected()) - optionElement->setSelectedState(false); - else - setSelectedIndex(data, element, index, false, true); - } - - if (data.usesMenuList()) - menuListOnChange(data, element); - else - listBoxOnChange(data, element); - - scrollToSelection(data, element); -} - -unsigned SelectElement::optionCount(const SelectElementData& data, const Element* element) -{ - unsigned options = 0; - - const Vector<Element*>& items = data.listItems(element); - for (unsigned i = 0; i < items.size(); ++i) { - if (isOptionElement(items[i])) - ++options; - } - - return options; -} - -// SelectElementData -SelectElementData::SelectElementData() - : m_multiple(false) - , m_size(0) - , m_lastOnChangeIndex(-1) - , m_activeSelectionState(false) - , m_activeSelectionAnchorIndex(-1) - , m_activeSelectionEndIndex(-1) - , m_recalcListItems(false) - , m_repeatingChar(0) - , m_lastCharTime(0) -{ -} - -SelectElementData::~SelectElementData() -{ -} - -void SelectElementData::checkListItems(const Element* element) const -{ -#if !ASSERT_DISABLED - Vector<Element*> items = m_listItems; - SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element, false); - ASSERT(items == m_listItems); -#else - UNUSED_PARAM(element); -#endif -} - -Vector<Element*>& SelectElementData::listItems(const Element* element) -{ - if (m_recalcListItems) - SelectElement::recalcListItems(*this, element); - else - checkListItems(element); - - return m_listItems; -} - -const Vector<Element*>& SelectElementData::listItems(const Element* element) const -{ - if (m_recalcListItems) - SelectElement::recalcListItems(*const_cast<SelectElementData*>(this), element); - else - checkListItems(element); - - return m_listItems; -} - -SelectElement* toSelectElement(Element* element) -{ - if (element->isHTMLElement()) { - if (element->hasTagName(HTMLNames::selectTag)) - return static_cast<HTMLSelectElement*>(element); - if (element->hasTagName(HTMLNames::keygenTag)) - return static_cast<HTMLKeygenElement*>(element); - } - -#if ENABLE(WML) - if (element->isWMLElement() && element->hasTagName(WMLNames::selectTag)) - return static_cast<WMLSelectElement*>(element); -#endif - - return 0; -} - -} |