webengine/osswebengine/WebCore/html/HTMLSelectElement.cpp
changeset 0 dd21522fd290
child 47 e1bea15f9a39
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebCore/html/HTMLSelectElement.cpp	Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,1067 @@
+/*
+ * 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 Apple Inc. All rights reserved.
+ *           (C) 2006 Alexey Proskuryakov (ap@nypop.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 "HTMLSelectElement.h"
+
+#include "CSSPropertyNames.h"
+#include "CSSStyleSelector.h"
+#include "CharacterNames.h"
+#include "Document.h"
+#include "Event.h"
+#include "EventHandler.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "FormDataList.h"
+#include "Frame.h"
+#include "HTMLFormElement.h"
+#include "HTMLNames.h"
+#include "HTMLOptionElement.h"
+#include "HTMLOptionsCollection.h"
+#include "KeyboardEvent.h"
+#include "MouseEvent.h"
+#include "RenderListBox.h"
+#include "RenderMenuList.h"
+#include <math.h>
+#include <wtf/Vector.h>
+
+#if PLATFORM(MAC)
+#define ARROW_KEYS_POP_MENU 1
+#else
+#define ARROW_KEYS_POP_MENU 0
+#endif
+
+using namespace std;
+using namespace WTF;
+using namespace Unicode;
+
+namespace WebCore {
+
+using namespace EventNames;
+using namespace HTMLNames;
+
+static const DOMTimeStamp typeAheadTimeout = 1000;
+
+HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
+    : HTMLFormControlElementWithState(selectTag, doc, f)
+    , m_minwidth(0)
+    , m_size(0)
+    , m_multiple(false)
+    , m_recalcListItems(false)
+    , m_lastOnChangeIndex(-1)
+    , m_activeSelectionAnchorIndex(-1)
+    , m_activeSelectionEndIndex(-1)
+    , m_activeSelectionState(false)
+    , m_repeatingChar(0)
+    , m_lastCharTime(0)
+{
+}
+
+HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
+    : HTMLFormControlElementWithState(tagName, doc, f)
+    , m_minwidth(0)
+    , m_size(0)
+    , m_multiple(false)
+    , m_recalcListItems(false)
+    , m_lastOnChangeIndex(-1)
+    , m_activeSelectionAnchorIndex(-1)
+    , m_activeSelectionEndIndex(-1)
+    , m_activeSelectionState(false)
+    , m_repeatingChar(0)
+    , m_lastCharTime(0)
+{
+}
+
+bool HTMLSelectElement::checkDTD(const Node* newChild)
+{
+    // Make sure to keep <optgroup> in sync with this.
+    return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
+           newChild->hasTagName(scriptTag);
+}
+
+void HTMLSelectElement::recalcStyle( StyleChange ch )
+{
+    if (hasChangedChild() && renderer()) {
+        if (usesMenuList())
+            static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
+        else
+            static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
+    }
+
+    HTMLFormControlElementWithState::recalcStyle(ch);
+}
+
+const AtomicString& HTMLSelectElement::type() const
+{
+    static const AtomicString selectMultiple("select-multiple");
+    static const AtomicString selectOne("select-one");
+    return m_multiple ? selectMultiple : selectOne;
+}
+
+int HTMLSelectElement::selectedIndex() const
+{
+    // return the number of the first option selected
+    unsigned index = 0;
+    const Vector<HTMLElement*>& items = listItems();
+    for (unsigned int i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            if (static_cast<HTMLOptionElement*>(items[i])->selected())
+                return index;
+            index++;
+        }
+    }
+    return -1;
+}
+
+int HTMLSelectElement::lastSelectedListIndex() const
+{
+    // return the number of the last option selected
+    unsigned index = 0;
+    bool found = false;
+    const Vector<HTMLElement*>& items = listItems();
+    for (unsigned int i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            if (static_cast<HTMLOptionElement*>(items[i])->selected()) {
+                index = i;
+                found = true;
+            }
+        }
+    }
+    return found ? (int) index : -1;
+}
+
+void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
+{
+    const Vector<HTMLElement*>& items = listItems();
+    unsigned i;
+    for (i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag) && (items[i] != excludeElement)) {
+            HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[i]);
+            element->setSelectedState(false);
+        }
+    }
+}
+
+void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect, bool fireOnChange)
+{
+    const Vector<HTMLElement*>& items = listItems();
+    int listIndex = optionToListIndex(optionIndex);
+    HTMLOptionElement* element = 0;
+
+    if (!multiple())
+        deselect = true;
+
+    if (listIndex >= 0) {
+        if (m_activeSelectionAnchorIndex < 0 || deselect)
+            setActiveSelectionAnchorIndex(listIndex);
+        if (m_activeSelectionEndIndex < 0 || deselect)
+            setActiveSelectionEndIndex(listIndex);
+        element = static_cast<HTMLOptionElement*>(items[listIndex]);
+        element->setSelectedState(true);
+    }
+
+    if (deselect)
+        deselectItems(element);
+
+    scrollToSelection();
+
+    // This only gets called with fireOnChange for menu lists. 
+    if (fireOnChange && usesMenuList())
+        menuListOnChange();
+}
+
+int HTMLSelectElement::activeSelectionStartListIndex() const
+{
+    if (m_activeSelectionAnchorIndex >= 0)
+        return m_activeSelectionAnchorIndex;
+    return optionToListIndex(selectedIndex());
+}
+
+int HTMLSelectElement::activeSelectionEndListIndex() const
+{
+    if (m_activeSelectionEndIndex >= 0)
+        return m_activeSelectionEndIndex;
+    return lastSelectedListIndex();
+}
+
+unsigned HTMLSelectElement::length() const
+{
+    unsigned len = 0;
+    const Vector<HTMLElement*>& items = listItems();
+    for (unsigned i = 0; i < items.size(); ++i) {
+        if (items[i]->hasLocalName(optionTag))
+            ++len;
+    }
+    return len;
+}
+
+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);
+    if (!ec)
+        setRecalcListItems();
+}
+
+void HTMLSelectElement::remove(int index)
+{
+    ExceptionCode ec = 0;
+    int listIndex = optionToListIndex(index);
+
+    const Vector<HTMLElement*>& items = listItems();
+    if (listIndex < 0 || index >= int(items.size()))
+        return; // ### what should we do ? remove the last item?
+
+    Element *item = items[listIndex];
+    ASSERT(item->parentNode());
+    item->parentNode()->removeChild(item, ec);
+    if (!ec)
+        setRecalcListItems();
+}
+
+String HTMLSelectElement::value()
+{
+    unsigned i;
+    const Vector<HTMLElement*>& items = listItems();
+    for (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 String("");
+}
+
+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<HTMLElement*>& 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::saveState(String& value) const
+{
+    const Vector<HTMLElement*>& items = listItems();
+    int l = items.size();
+    Vector<char, 1024> characters(l);
+    for (int i = 0; i < l; ++i) {
+        HTMLElement* e = items[i];
+        bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
+        characters[i] = selected ? 'X' : '.';
+    }
+    value = String(characters.data(), l);
+    return true;
+}
+
+void HTMLSelectElement::restoreState(const String& state)
+{
+    recalcListItems();
+    
+    const Vector<HTMLElement*>& items = listItems();
+    int l = items.size();
+    for (int i = 0; i < l; i++)
+        if (items[i]->hasLocalName(optionTag))
+            static_cast<HTMLOptionElement*>(items[i])->setSelectedState(state[i] == 'X');
+            
+    setChanged();
+}
+
+bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
+{
+    bool result = HTMLFormControlElementWithState::insertBefore(newChild, refChild, ec);
+    if (result)
+        setRecalcListItems();
+    return result;
+}
+
+bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
+{
+    bool result = HTMLFormControlElementWithState::replaceChild(newChild, oldChild, ec);
+    if (result)
+        setRecalcListItems();
+    return result;
+}
+
+bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
+{
+    bool result = HTMLFormControlElementWithState::removeChild(oldChild, ec);
+    if (result)
+        setRecalcListItems();
+    return result;
+}
+
+bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
+{
+    bool result = HTMLFormControlElementWithState::appendChild(newChild, ec);
+    if (result)
+        setRecalcListItems();
+    return result;
+}
+
+ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
+{
+    ContainerNode* result = HTMLFormControlElementWithState::addChild(newChild);
+    if (result)
+        setRecalcListItems();
+    return result;
+}
+
+void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
+{
+    bool oldUsesMenuList = usesMenuList();
+    if (attr->name() == sizeAttr) {
+        int oldSize = m_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);
+
+        m_size = max(size, 1);
+        if ((oldUsesMenuList != usesMenuList() || !oldUsesMenuList && m_size != oldSize) && attached()) {
+            detach();
+            attach();
+        }
+    } else if (attr->name() == widthAttr) {
+        m_minwidth = max(attr->value().toInt(), 0);
+    } else if (attr->name() == multipleAttr) {
+        m_multiple = (!attr->isNull());
+        if (oldUsesMenuList != usesMenuList() && attached()) {
+            detach();
+            attach();
+        }
+    } 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() == onfocusAttr) {
+        setHTMLEventListener(focusEvent, attr);
+    } else if (attr->name() == onblurAttr) {
+        setHTMLEventListener(blurEvent, attr);
+    } else if (attr->name() == onchangeAttr) {
+        setHTMLEventListener(changeEvent, 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 !usesMenuList(); 
+}
+
+void HTMLSelectElement::selectAll()
+{
+    ASSERT(!usesMenuList());
+    if (!renderer() || !multiple())
+        return;
+    
+    // Save the selection so it can be compared to the new selectAll selection when we call onChange
+    saveLastSelection();
+    
+    m_activeSelectionState = true;
+    setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
+    setActiveSelectionEndIndex(previousSelectableListIndex(-1));
+    
+    updateListBoxSelection(false);
+    listBoxOnChange();
+}
+
+RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
+{
+    if (usesMenuList())
+        return new (arena) RenderMenuList(this);
+    return new (arena) RenderListBox(this);
+}
+
+bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
+{
+    bool successful = false;
+    const Vector<HTMLElement*>& items = listItems();
+
+    unsigned i;
+    for (i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
+            if (option->selected()) {
+                list.appendData(name(), option->value());
+                successful = true;
+            }
+        }
+    }
+
+    // ### this case should not happen. make sure that we select the first option
+    // in any case. otherwise we have no consistency with the DOM interface. FIXME!
+    // we return the first one if it was a combobox select
+    if (!successful && !m_multiple && m_size <= 1 && items.size() &&
+        (items[0]->hasLocalName(optionTag))) {
+        HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
+        if (option->value().isNull())
+            list.appendData(name(), option->text().stripWhiteSpace());
+        else
+            list.appendData(name(), option->value());
+        successful = true;
+    }
+
+    return successful;
+}
+
+int HTMLSelectElement::optionToListIndex(int optionIndex) const
+{
+    const Vector<HTMLElement*>& items = listItems();
+    int listSize = (int)items.size();
+    if (optionIndex < 0 || optionIndex >= listSize)
+        return -1;
+
+    int optionIndex2 = -1;
+    for (int listIndex = 0; listIndex < listSize; listIndex++) {
+        if (items[listIndex]->hasLocalName(optionTag)) {
+            optionIndex2++;
+            if (optionIndex2 == optionIndex)
+                return listIndex;
+        }
+    }
+    return -1;
+}
+
+int HTMLSelectElement::listToOptionIndex(int listIndex) const
+{
+    const Vector<HTMLElement*>& items = listItems();
+    if (listIndex < 0 || listIndex >= int(items.size()) ||
+        !items[listIndex]->hasLocalName(optionTag))
+        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 (items[i]->hasLocalName(optionTag))
+            optionIndex++;
+    return optionIndex;
+}
+
+PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
+{
+    return new HTMLOptionsCollection(this);
+}
+
+void HTMLSelectElement::recalcListItems() const
+{
+    Node* current = firstChild();
+    m_listItems.clear();
+    HTMLOptionElement* foundSelected = 0;
+    while (current) {
+        if (current->hasTagName(optgroupTag) && current->firstChild()) {
+            // ### what if optgroup contains just comments? don't want one of no options in it...
+            m_listItems.append(static_cast<HTMLElement*>(current));
+            current = current->firstChild();
+        }
+        if (current->hasTagName(optionTag)) {
+            m_listItems.append(static_cast<HTMLElement*>(current));
+            if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
+                foundSelected = static_cast<HTMLOptionElement*>(current);
+                foundSelected->setSelectedState(true);
+            } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
+                foundSelected->setSelectedState(false);
+                foundSelected = static_cast<HTMLOptionElement*>(current);
+            }
+        }
+        if (current->hasTagName(hrTag))
+            m_listItems.append(static_cast<HTMLElement*>(current));
+
+        Node* parent = current->parentNode();
+        current = current->nextSibling();
+        if (!current) {
+            if (parent != this)
+                current = parent->nextSibling();
+        }
+    }
+    m_recalcListItems = false;
+}
+
+void HTMLSelectElement::childrenChanged()
+{
+    setRecalcListItems();
+
+    HTMLFormControlElementWithState::childrenChanged();
+}
+
+void HTMLSelectElement::setRecalcListItems()
+{
+    m_recalcListItems = true;
+    if (renderer()) {
+        if (usesMenuList())
+            static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
+        else
+            static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
+    }
+    if (!inDocument())
+        m_collectionInfo.reset();
+    setChanged();
+}
+
+void HTMLSelectElement::reset()
+{
+    bool optionSelected = false;
+    HTMLOptionElement* firstOption = 0;
+    const Vector<HTMLElement*>& items = listItems();
+    unsigned i;
+    for (i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
+            if (!option->getAttribute(selectedAttr).isNull()) {
+                option->setSelectedState(true);
+                optionSelected = true;
+            } else
+                option->setSelectedState(false);
+            if (!firstOption)
+                firstOption = option;
+        }
+    }
+    if (!optionSelected && firstOption && usesMenuList())
+        firstOption->setSelectedState(true);
+    
+    setChanged();
+}
+
+void HTMLSelectElement::dispatchFocusEvent()
+{
+    if (usesMenuList())
+        // Save the selection so it can be compared to the new selection when we call onChange during dispatchBlurEvent.
+        saveLastSelection();
+    HTMLFormControlElementWithState::dispatchFocusEvent();
+}
+
+void HTMLSelectElement::dispatchBlurEvent()
+{
+    // We only need to fire onChange here for menu lists, because we fire onChange for list boxes whenever the selection change is actually made.
+    // This matches other browsers' behavior.
+    if (usesMenuList())
+        menuListOnChange();
+    HTMLFormControlElementWithState::dispatchBlurEvent();
+}
+
+void HTMLSelectElement::defaultEventHandler(Event* evt)
+{
+    if (usesMenuList())
+        menuListDefaultEventHandler(evt);
+    else 
+        listBoxDefaultEventHandler(evt);
+    
+    if (evt->defaultHandled())
+        return;
+
+    if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
+        KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
+    
+        if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() &&
+            isPrintableChar(keyboardEvent->charCode())) {
+            typeAheadFind(keyboardEvent);
+            evt->setDefaultHandled();
+            return;
+        }
+    }
+
+    HTMLFormControlElementWithState::defaultEventHandler(evt);
+}
+
+void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
+{
+    RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
+
+    // Use key press event here since sending simulated mouse events
+    // on key down blocks the proper sending of the key press event.
+    if (evt->type() == keypressEvent) {
+        if (!renderer() || !evt->isKeyboardEvent())
+            return;
+        String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
+        bool handled = false;
+#if ARROW_KEYS_POP_MENU
+        if (keyIdentifier == "Enter") {
+            menuListOnChange();
+            if (form())
+                form()->submitClick(evt);
+            handled = true;
+        }
+        if (keyIdentifier == "Down" || keyIdentifier == "Up" || keyIdentifier == "U+0020") {
+            focus();
+            // 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();
+            menuList->showPopup();
+            handled = true;
+        }
+#else
+        int listIndex = optionToListIndex(selectedIndex());
+        if (keyIdentifier == "Down" || keyIdentifier == "Right") {
+            int size = listItems().size();
+            for (listIndex += 1;
+                 listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
+                 ++listIndex);
+            
+            if (listIndex >= 0 && listIndex < size)
+                setSelectedIndex(listToOptionIndex(listIndex));
+            handled = true;
+        } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
+            int size = listItems().size();
+            for (listIndex -= 1;
+                 listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
+                 --listIndex);
+            
+            if (listIndex >= 0 && listIndex < size)
+                setSelectedIndex(listToOptionIndex(listIndex));
+            handled = true;
+        } else if (keyIdentifier == "Enter") {
+            // listIndex should already be selected, but this will fire the onchange handler.
+            setSelectedIndex(listToOptionIndex(listIndex), true, true);
+            handled = true;
+        }
+#endif
+        if (handled)
+            evt->setDefaultHandled();
+
+    }
+    if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
+        focus();
+        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();
+            menuList->showPopup();
+        }
+        evt->setDefaultHandled();
+    }
+}
+
+void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
+{
+    if (!renderer())
+        return;
+
+    if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
+        focus();
+        
+        MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
+        int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
+        if (listIndex >= 0) {
+            // Save the selection so it can be compared to the new selection when we call onChange during mouseup, or after autoscroll finishes.
+            saveLastSelection();
+
+            m_activeSelectionState = true;
+            
+            bool multiSelectKeyPressed = false;
+#if PLATFORM(MAC)
+            multiSelectKeyPressed = mEvt->metaKey();
+#elif PLATFORM(SYMBIAN)
+            multiSelectKeyPressed = true;
+#else
+            multiSelectKeyPressed = mEvt->ctrlKey();
+#endif
+
+            bool shiftSelect = multiple() && mEvt->shiftKey();
+            bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
+            
+            HTMLElement* clickedElement = listItems()[listIndex];            
+            HTMLOptionElement* option = 0;
+            if (clickedElement->hasLocalName(optionTag)) {
+                option = static_cast<HTMLOptionElement*>(clickedElement);
+                
+                // Keep track of whether an active selection (like during drag selection), should select or deselect
+                if (option->selected() && multiSelectKeyPressed)
+                    m_activeSelectionState = false;
+
+                if (!m_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(option);
+
+            // 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 (m_activeSelectionAnchorIndex < 0 && !multiSelect)
+                setActiveSelectionAnchorIndex(selectedIndex());
+
+            // Set the selection state of the clicked option
+            if (option && !option->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 (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect))
+                setActiveSelectionAnchorIndex(listIndex);
+            
+            setActiveSelectionEndIndex(listIndex);
+            updateListBoxSelection(!multiSelect);
+
+            if (Frame* frame = document()->frame())
+                frame->eventHandler()->setMouseDownMayStartAutoscroll();
+
+            evt->setDefaultHandled();
+        }
+    } else if (evt->type() == mouseupEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer())
+        // This makes sure we fire onChange for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
+        listBoxOnChange();
+    else if (evt->type() == keypressEvent) {
+        if (!evt->isKeyboardEvent())
+            return;
+        String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
+
+        if (keyIdentifier == "Enter") {
+            if (form())
+                form()->submitClick(evt);
+            evt->setDefaultHandled();
+            return;
+        }
+
+        int endIndex = 0;        
+        if (m_activeSelectionEndIndex < 0) {
+            // Initialize the end index
+            if (keyIdentifier == "Down")
+                endIndex = nextSelectableListIndex(lastSelectedListIndex());
+            else if (keyIdentifier == "Up")
+                endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex()));
+        } else {
+            // Set the end index based on the current end index
+            if (keyIdentifier == "Down")
+                endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
+            else if (keyIdentifier == "Up")
+                endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);    
+        }
+        
+        if (keyIdentifier == "Down" || keyIdentifier == "Up") {
+            // Save the selection so it can be compared to the new selection when we call onChange immediately after making the new selection.
+            saveLastSelection();
+
+            ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size()); 
+            setActiveSelectionEndIndex(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 = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey();
+            if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
+                m_activeSelectionState = true;
+                if (deselectOthers)
+                    deselectItems();
+                setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
+            }
+
+            static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex);
+            updateListBoxSelection(deselectOthers);
+            listBoxOnChange();
+            evt->setDefaultHandled();
+        }
+    }
+}
+
+void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
+{
+    m_activeSelectionAnchorIndex = index;
+    
+    // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
+    const Vector<HTMLElement*>& items = listItems();
+    m_cachedStateForActiveSelection.clear();
+    for (unsigned i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
+            m_cachedStateForActiveSelection.append(option->selected());
+        } else
+            m_cachedStateForActiveSelection.append(false);
+    }
+}
+
+void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
+{
+    ASSERT(renderer() && renderer()->isListBox());
+    
+    unsigned start;
+    unsigned end;
+    ASSERT(m_activeSelectionAnchorIndex >= 0);
+    start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
+    end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
+
+    const Vector<HTMLElement*>& items = listItems();
+    for (unsigned i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
+            if (!option->disabled()) {
+                if (i >= start && i <= end)
+                    option->setSelectedState(m_activeSelectionState);
+                else if (deselectOtherOptions)
+                    option->setSelectedState(false);
+                else
+                    option->setSelectedState(m_cachedStateForActiveSelection[i]);
+            }
+        }
+    }
+
+    scrollToSelection();
+}
+
+void HTMLSelectElement::menuListOnChange()
+{
+    ASSERT(usesMenuList());
+    int selected = selectedIndex();
+    if (m_lastOnChangeIndex != selected) {
+        m_lastOnChangeIndex = selected;
+        onChange();
+    }
+}
+
+void HTMLSelectElement::listBoxOnChange()
+{
+    ASSERT(!usesMenuList());
+
+    const Vector<HTMLElement*>& items = listItems();
+    
+    // If the cached selection list is empty, or the size has changed, then fire onChange, and return early.
+    if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
+        onChange();
+        return;
+    }
+    
+    // Update m_lastOnChangeSelection and fire onChange
+    bool fireOnChange = false;
+    for (unsigned i = 0; i < items.size(); i++) {
+        bool selected = false;
+        if (items[i]->hasLocalName(optionTag))
+            selected = static_cast<HTMLOptionElement*>(items[i])->selected();
+        if (selected != m_lastOnChangeSelection[i])      
+            fireOnChange = true;
+        m_lastOnChangeSelection[i] = selected;
+    }
+    if (fireOnChange)
+        onChange();
+}
+
+void HTMLSelectElement::saveLastSelection()
+{
+    const Vector<HTMLElement*>& items = listItems();
+
+    if (usesMenuList()) {
+        m_lastOnChangeIndex = selectedIndex();
+        return;
+    }
+    
+    m_lastOnChangeSelection.clear();
+    for (unsigned i = 0; i < items.size(); i++) {
+        if (items[i]->hasLocalName(optionTag)) {
+            HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
+            m_lastOnChangeSelection.append(option->selected());
+        } else
+            m_lastOnChangeSelection.append(false);
+    }
+}
+
+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 ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
+            break;
+
+    return string.substring(i, length - i);
+}
+
+void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
+{
+    if (event->timeStamp() < m_lastCharTime)
+        return;
+
+    DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
+
+    m_lastCharTime = event->timeStamp();
+
+    UChar c = event->charCode();
+
+    String prefix;
+    int searchStartOffset = 1;
+    if (delta > typeAheadTimeout) {
+        m_typedString = prefix = String(&c, 1);
+        m_repeatingChar = c;
+    } else {
+        m_typedString.append(c);
+
+        if (c == m_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 {
+            m_repeatingChar = 0;
+            prefix = m_typedString;
+            searchStartOffset = 0;
+        }
+    }
+
+    const Vector<HTMLElement*>& items = listItems();
+    int itemCount = items.size();
+    if (itemCount < 1)
+        return;
+
+    int index = (optionToListIndex(selectedIndex()) + searchStartOffset) % itemCount;
+    for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
+        if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
+            continue;
+
+        if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
+            setSelectedIndex(listToOptionIndex(index));
+            setChanged();
+            return;
+        }
+    }
+}
+
+int HTMLSelectElement::nextSelectableListIndex(int startIndex)
+{
+    const Vector<HTMLElement*>& items = listItems();
+    int index = startIndex + 1;
+    while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
+        index++;
+    if ((unsigned) index == items.size())
+        return startIndex;
+    return index;
+}
+
+int HTMLSelectElement::previousSelectableListIndex(int startIndex)
+{
+    const Vector<HTMLElement*>& items = listItems();
+    if (startIndex == -1)
+        startIndex = items.size();
+    int index = startIndex - 1;
+    while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
+        index--;
+    if (index == -1)
+        return startIndex;
+    return index;
+}
+
+void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
+{
+    focus();
+    dispatchSimulatedClick(0, sendToAnyElement);
+}
+
+void HTMLSelectElement::setMultiple(bool multiple)
+{
+    setAttribute(multipleAttr, multiple ? "" : 0);
+}
+
+void HTMLSelectElement::setSize(int size)
+{
+    setAttribute(sizeAttr, String::number(size));
+}
+
+Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
+{
+    return options()->namedItem(name, caseSensitive);
+}
+
+Node* HTMLSelectElement::item(unsigned index)
+{
+    return options()->item(index);
+}
+
+void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
+{
+    ec = 0;
+    if (index > INT_MAX)
+        index = INT_MAX;
+    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 == 0) {
+        add(option, before, ec);
+        if (diff >= 0 && option->selected())
+            setSelectedIndex(index, !m_multiple);
+    }
+}
+
+void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
+{
+    ec = 0;
+    if (newLen > 10000) {
+        ec = NOT_SUPPORTED_ERR;
+        return;
+    }
+    int diff = length() - newLen;
+
+    if (diff < 0) { // add dummy elements
+        do {
+            RefPtr<Element> option = document()->createElement("option", ec);
+            if (!option)
+                break;
+            add(static_cast<HTMLElement*>(option.get()), 0, ec);
+            if (ec)
+                break;
+        } while (++diff);
+    }
+    else // remove elements
+        while (diff-- > 0)
+            remove(newLen);
+}
+
+void HTMLSelectElement::scrollToSelection()
+{
+    if (renderer() && !usesMenuList())
+        static_cast<RenderListBox*>(renderer())->selectionChanged();
+}
+
+} // namespace