Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / HTMLSelectElement.cpp
index 1f74f02..d1e6f99 100644 (file)
 #include "config.h"
 #include "core/html/HTMLSelectElement.h"
 
-#include "HTMLNames.h"
-#include "bindings/v8/ExceptionMessages.h"
-#include "bindings/v8/ExceptionState.h"
-#include "bindings/v8/ExceptionStatePlaceholder.h"
+#include "bindings/core/v8/ExceptionMessages.h"
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ExceptionStatePlaceholder.h"
+#include "core/HTMLNames.h"
 #include "core/accessibility/AXObjectCache.h"
 #include "core/dom/Attribute.h"
 #include "core/dom/ElementTraversal.h"
+#include "core/dom/NodeListsNodeData.h"
+#include "core/dom/NodeRenderStyle.h"
 #include "core/dom/NodeTraversal.h"
+#include "core/events/GestureEvent.h"
 #include "core/events/KeyboardEvent.h"
 #include "core/events/MouseEvent.h"
-#include "core/events/ThreadLocalEventNames.h"
+#include "core/frame/FrameView.h"
+#include "core/frame/LocalFrame.h"
 #include "core/html/FormDataList.h"
 #include "core/html/HTMLFormElement.h"
+#include "core/html/HTMLOptGroupElement.h"
 #include "core/html/HTMLOptionElement.h"
 #include "core/html/forms/FormController.h"
+#include "core/page/AutoscrollController.h"
 #include "core/page/EventHandler.h"
-#include "core/frame/Frame.h"
+#include "core/page/Page.h"
 #include "core/page/SpatialNavigation.h"
+#include "core/rendering/HitTestRequest.h"
+#include "core/rendering/HitTestResult.h"
 #include "core/rendering/RenderListBox.h"
 #include "core/rendering/RenderMenuList.h"
 #include "core/rendering/RenderTheme.h"
+#include "core/rendering/RenderView.h"
 #include "platform/PlatformMouseEvent.h"
 #include "platform/text/PlatformLocale.h"
 
-using namespace std;
 using namespace WTF::Unicode;
 
-namespace WebCore {
+namespace blink {
 
 using namespace HTMLNames;
 
@@ -73,18 +81,23 @@ HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
     , m_multiple(false)
     , m_activeSelectionState(false)
     , m_shouldRecalcListItems(false)
+    , m_suggestedIndex(-1)
+    , m_isAutofilledByPreview(false)
 {
-    ScriptWrappable::init(this);
 }
 
-PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
+PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
 {
-    return adoptRef(new HTMLSelectElement(document, 0));
+    RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, 0));
+    select->ensureUserAgentShadowRoot();
+    return select.release();
 }
 
-PassRefPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
+PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
 {
-    return adoptRef(new HTMLSelectElement(document, form));
+    RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, form));
+    select->ensureUserAgentShadowRoot();
+    return select.release();
 }
 
 const AtomicString& HTMLSelectElement::formControlType() const
@@ -113,7 +126,7 @@ void HTMLSelectElement::optionSelectedByUser(int optionIndex, bool fireOnChangeN
     if (optionIndex == selectedIndex())
         return;
 
-    selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchChangeEvent : 0) | UserDriven);
+    selectOption(optionIndex, DeselectOtherOptions | (fireOnChangeNow ? DispatchInputAndChangeEvent : 0) | UserDriven);
 }
 
 bool HTMLSelectElement::hasPlaceholderLabelOption() const
@@ -201,15 +214,21 @@ int HTMLSelectElement::activeSelectionEndListIndex() const
 void HTMLSelectElement::add(HTMLElement* element, HTMLElement* before, ExceptionState& exceptionState)
 {
     // Make sure the element is ref'd and deref'd so we don't leak it.
-    RefPtr<HTMLElement> protectNewChild(element);
+    RefPtrWillBeRawPtr<HTMLElement> protectNewChild(element);
 
-    if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
+    if (!element || !(isHTMLOptionElement(element) || isHTMLOptGroupElement(element) || isHTMLHRElement(element)))
         return;
 
     insertBefore(element, before, exceptionState);
     setNeedsValidityCheck();
 }
 
+void HTMLSelectElement::addBeforeOptionAtIndex(HTMLElement* element, int beforeIndex, ExceptionState& exceptionState)
+{
+    HTMLOptionElement* beforeElement = options()->item(beforeIndex);
+    add(element, beforeElement, exceptionState);
+}
+
 void HTMLSelectElement::remove(int optionIndex)
 {
     int listIndex = optionToListIndex(optionIndex);
@@ -219,46 +238,83 @@ void HTMLSelectElement::remove(int optionIndex)
     listItems()[listIndex]->remove(IGNORE_EXCEPTION);
 }
 
-void HTMLSelectElement::remove(HTMLOptionElement* option)
-{
-    if (option->ownerSelectElement() != this)
-        return;
-
-    option->remove(IGNORE_EXCEPTION);
-}
-
 String HTMLSelectElement::value() const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); i++) {
-        if (items[i]->hasLocalName(optionTag) && toHTMLOptionElement(items[i])->selected())
+        if (isHTMLOptionElement(items[i]) && toHTMLOptionElement(items[i])->selected())
             return toHTMLOptionElement(items[i])->value();
     }
     return "";
 }
 
-void HTMLSelectElement::setValue(const String &value)
+void HTMLSelectElement::setValue(const String &value, bool sendEvents)
 {
     // We clear the previously selected option(s) when needed, to guarantee calling setSelectedIndex() only once.
+    int optionIndex = 0;
+    if (value.isNull()) {
+        optionIndex = -1;
+    } else {
+        // Find the option with value() matching the given parameter and make it the current selection.
+        const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
+        for (unsigned i = 0; i < items.size(); i++) {
+            if (isHTMLOptionElement(items[i])) {
+                if (toHTMLOptionElement(items[i])->value() == value)
+                    break;
+                optionIndex++;
+            }
+        }
+        if (optionIndex >= static_cast<int>(items.size()))
+            optionIndex = -1;
+    }
+
+    int previousSelectedIndex = selectedIndex();
+    setSuggestedIndex(-1);
+    if (m_isAutofilledByPreview)
+        setAutofilled(false);
+    setSelectedIndex(optionIndex);
+
+    if (sendEvents && previousSelectedIndex != selectedIndex()) {
+        if (usesMenuList())
+            dispatchInputAndChangeEventForMenuList(false);
+        else
+            listBoxOnChange();
+    }
+}
+
+String HTMLSelectElement::suggestedValue() const
+{
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
+    for (unsigned i = 0; i < items.size(); ++i) {
+        if (isHTMLOptionElement(items[i]) && m_suggestedIndex >= 0) {
+            if (i == static_cast<unsigned>(m_suggestedIndex))
+                return toHTMLOptionElement(items[i])->value();
+        }
+    }
+    return "";
+}
+
+void HTMLSelectElement::setSuggestedValue(const String& value)
+{
     if (value.isNull()) {
-        setSelectedIndex(-1);
+        setSuggestedIndex(-1);
         return;
     }
 
-    // Find the option with value() matching the given parameter and make it the current selection.
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     unsigned optionIndex = 0;
-    for (unsigned i = 0; i < items.size(); i++) {
-        if (items[i]->hasLocalName(optionTag)) {
+    for (unsigned i = 0; i < items.size(); ++i) {
+        if (isHTMLOptionElement(items[i])) {
             if (toHTMLOptionElement(items[i])->value() == value) {
-                setSelectedIndex(optionIndex);
+                setSuggestedIndex(optionIndex);
+                m_isAutofilledByPreview = true;
                 return;
             }
             optionIndex++;
         }
     }
 
-    setSelectedIndex(-1);
+    setSuggestedIndex(-1);
 }
 
 bool HTMLSelectElement::isPresentationAttribute(const QualifiedName& name) const
@@ -282,10 +338,10 @@ void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicSt
         AtomicString attrSize = AtomicString::number(size);
         if (attrSize != value) {
             // FIXME: This is horribly factored.
-            if (Attribute* sizeAttribute = ensureUniqueElementData()->getAttributeItem(sizeAttr))
+            if (Attribute* sizeAttribute = ensureUniqueElementData().attributes().find(sizeAttr))
                 sizeAttribute->setValue(attrSize);
         }
-        size = max(size, 1);
+        size = std::max(size, 0);
 
         // Ensure that we've determined selectedness of the items at least once prior to changing the size.
         if (oldSize != size)
@@ -302,6 +358,15 @@ void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicSt
     else if (name == accesskeyAttr) {
         // FIXME: ignore for the moment.
         //
+    } else if (name == disabledAttr) {
+        HTMLFormControlElementWithState::parseAttribute(name, value);
+        if (renderer() && renderer()->isMenuList()) {
+            if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
+                if (menuList->popupIsVisible())
+                    menuList->hidePopup();
+            }
+        }
+
     } else
         HTMLFormControlElementWithState::parseAttribute(name, value);
 }
@@ -323,15 +388,15 @@ RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
     return new RenderListBox(this);
 }
 
-PassRefPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
+PassRefPtrWillBeRawPtr<HTMLCollection> HTMLSelectElement::selectedOptions()
 {
     updateListItemSelectedStates();
-    return ensureCachedHTMLCollection(SelectedOptions);
+    return ensureCachedCollection<HTMLCollection>(SelectedOptions);
 }
 
-PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
+PassRefPtrWillBeRawPtr<HTMLOptionsCollection> HTMLSelectElement::options()
 {
-    return static_cast<HTMLOptionsCollection*>(ensureCachedHTMLCollection(SelectOptions).get());
+    return ensureCachedCollection<HTMLOptionsCollection>(SelectOptions);
 }
 
 void HTMLSelectElement::updateListItemSelectedStates()
@@ -342,13 +407,13 @@ void HTMLSelectElement::updateListItemSelectedStates()
     setNeedsValidityCheck();
 }
 
-void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
+void HTMLSelectElement::childrenChanged(const ChildrenChange& change)
 {
     setRecalcListItems();
     setNeedsValidityCheck();
     m_lastOnChangeSelection.clear();
 
-    HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
+    HTMLFormControlElementWithState::childrenChanged(change);
 }
 
 void HTMLSelectElement::optionElementChildrenChanged()
@@ -390,7 +455,7 @@ Element* HTMLSelectElement::namedItem(const AtomicString& name)
     return options()->namedItem(name);
 }
 
-Element* HTMLSelectElement::item(unsigned index)
+HTMLOptionElement* HTMLSelectElement::item(unsigned index)
 {
     return options()->item(index);
 }
@@ -400,13 +465,13 @@ void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, Exc
     if (index > maxSelectItems - 1)
         index = maxSelectItems - 1;
     int diff = index - length();
-    RefPtr<HTMLElement> before = 0;
+    RefPtrWillBeRawPtr<HTMLOptionElement> before = nullptr;
     // Out of array bounds? First insert empty dummies.
     if (diff > 0) {
         setLength(index, exceptionState);
         // Replace an existing entry?
     } else if (diff < 0) {
-        before = toHTMLElement(options()->item(index+1));
+        before = options()->item(index + 1);
         remove(index);
     }
     // Finally add the new element.
@@ -425,22 +490,22 @@ void HTMLSelectElement::setLength(unsigned newLen, ExceptionState& exceptionStat
 
     if (diff < 0) { // Add dummy elements.
         do {
-            RefPtr<Element> option = document().createElement(optionTag, false);
+            RefPtrWillBeRawPtr<Element> option = document().createElement(optionTag, false);
             ASSERT(option);
             add(toHTMLElement(option), 0, exceptionState);
             if (exceptionState.hadException())
                 break;
         } while (++diff);
     } else {
-        const Vector<HTMLElement*>& items = listItems();
+        const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& 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;
+        WillBeHeapVector<RefPtrWillBeMember<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) {
+            if (isHTMLOptionElement(items[i]) && optionIndex++ >= newLen) {
                 ASSERT(item->parentNode());
                 itemsToRemove.append(item);
             }
@@ -467,16 +532,23 @@ bool HTMLSelectElement::isRequiredFormControl() const
 int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, int skip) const
 {
     ASSERT(direction == -1 || direction == 1);
-    const Vector<HTMLElement*>& listItems = this->listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
     int lastGoodIndex = listIndex;
     int size = listItems.size();
     for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
         --skip;
-        if (!listItems[listIndex]->isDisabledFormControl() && listItems[listIndex]->hasTagName(optionTag)) {
-            lastGoodIndex = listIndex;
-            if (skip <= 0)
-                break;
-        }
+        HTMLElement* element = listItems[listIndex];
+        if (!isHTMLOptionElement(*element))
+            continue;
+        if (toHTMLOptionElement(*element).isDisplayNone())
+            continue;
+        if (element->isDisabledFormControl())
+            continue;
+        if (!usesMenuList() && !element->renderer())
+            continue;
+        lastGoodIndex = listIndex;
+        if (skip <= 0)
+            break;
     }
     return lastGoodIndex;
 }
@@ -495,7 +567,7 @@ int HTMLSelectElement::previousSelectableListIndex(int startIndex) const
 
 int HTMLSelectElement::firstSelectableListIndex() const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     int index = nextValidIndex(items.size(), SkipBackwards, INT_MAX);
     if (static_cast<size_t>(index) == items.size())
         return -1;
@@ -510,7 +582,7 @@ int HTMLSelectElement::lastSelectableListIndex() const
 // Returns the index of the next valid item one page away from |startIndex| in direction |direction|.
 int HTMLSelectElement::nextSelectableListIndexPageAway(int startIndex, SkipDirection direction) const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     // Can't use m_size because renderer forces a minimum size.
     int pageSize = 0;
     if (renderer()->isListBox())
@@ -538,7 +610,7 @@ void HTMLSelectElement::selectAll()
     setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
     setActiveSelectionEndIndex(previousSelectableListIndex(-1));
 
-    updateListBoxSelection(false);
+    updateListBoxSelection(false, false);
     listBoxOnChange();
     setNeedsValidityCheck();
 }
@@ -551,10 +623,10 @@ void HTMLSelectElement::saveLastSelection()
     }
 
     m_lastOnChangeSelection.clear();
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        m_lastOnChangeSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
+        m_lastOnChangeSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
     }
 }
 
@@ -566,30 +638,33 @@ void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
     // selection pivots around this anchor index.
     m_cachedStateForActiveSelection.clear();
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        m_cachedStateForActiveSelection.append(element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected());
+        m_cachedStateForActiveSelection.append(isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected());
     }
 }
 
 void HTMLSelectElement::setActiveSelectionEndIndex(int index)
 {
+    if (index == m_activeSelectionEndIndex)
+        return;
     m_activeSelectionEndIndex = index;
+    setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Control));
 }
 
-void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
+void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions, bool scroll)
 {
     ASSERT(renderer() && (renderer()->isListBox() || m_multiple));
     ASSERT(!listItems().size() || m_activeSelectionAnchorIndex >= 0);
 
-    unsigned start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
-    unsigned end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
+    unsigned start = std::min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
+    unsigned end = std::max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
+        if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || !toHTMLOptionElement(element)->renderer())
             continue;
 
         if (i >= start && i <= end)
@@ -600,8 +675,9 @@ void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
             toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
     }
 
-    scrollToSelection();
     setNeedsValidityCheck();
+    if (scroll)
+        scrollToSelection();
     notifyFormStateChanged();
 }
 
@@ -609,7 +685,7 @@ void HTMLSelectElement::listBoxOnChange()
 {
     ASSERT(!usesMenuList() || m_multiple);
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
 
     // If the cached selection list is empty, or the size has changed, then fire
     // dispatchFormControlChangeEvent, and return early.
@@ -623,35 +699,42 @@ void HTMLSelectElement::listBoxOnChange()
     bool fireOnChange = false;
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        bool selected = element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected();
+        bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
         if (selected != m_lastOnChangeSelection[i])
             fireOnChange = true;
         m_lastOnChangeSelection[i] = selected;
     }
 
-    if (fireOnChange)
+    if (fireOnChange) {
+        RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
+        dispatchInputEvent();
         dispatchFormControlChangeEvent();
+    }
 }
 
-void HTMLSelectElement::dispatchChangeEventForMenuList()
+void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUserGesture)
 {
     ASSERT(usesMenuList());
 
     int selected = selectedIndex();
-    if (m_lastOnChangeIndex != selected && m_isProcessingUserDrivenChange) {
+    if (m_lastOnChangeIndex != selected && (!requiresUserGesture || m_isProcessingUserDrivenChange)) {
         m_lastOnChangeIndex = selected;
         m_isProcessingUserDrivenChange = false;
+        RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
+        dispatchInputEvent();
         dispatchFormControlChangeEvent();
     }
 }
 
 void HTMLSelectElement::scrollToSelection()
 {
+    if (!isFinishedParsingChildren())
+        return;
     if (usesMenuList())
         return;
-
-    if (RenderObject* renderer = this->renderer())
-        toRenderListBox(renderer)->selectionChanged();
+    scrollTo(activeSelectionEndListIndex());
+    if (AXObjectCache* cache = document().existingAXObjectCache())
+        cache->selectedChildrenChanged(this);
 }
 
 void HTMLSelectElement::setOptionsChangedOnRenderer()
@@ -659,18 +742,16 @@ void HTMLSelectElement::setOptionsChangedOnRenderer()
     if (RenderObject* renderer = this->renderer()) {
         if (usesMenuList())
             toRenderMenuList(renderer)->setOptionsChanged(true);
-        else
-            toRenderListBox(renderer)->setOptionsChanged(true);
     }
 }
 
-const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
+const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& HTMLSelectElement::listItems() const
 {
     if (m_shouldRecalcListItems)
         recalcListItems();
     else {
-#if !ASSERT_DISABLED
-        Vector<HTMLElement*> items = m_listItems;
+#if ENABLE(ASSERT)
+        WillBeHeapVector<RawPtrWillBeMember<HTMLElement>> items = m_listItems;
         recalcListItems(false);
         ASSERT(items == m_listItems);
 #endif
@@ -681,7 +762,7 @@ const Vector<HTMLElement*>& HTMLSelectElement::listItems() const
 
 void HTMLSelectElement::invalidateSelectedItems()
 {
-    if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
+    if (HTMLCollection* collection = cachedCollection<HTMLCollection>(SelectedOptions))
         collection->invalidateCache();
 }
 
@@ -694,9 +775,9 @@ void HTMLSelectElement::setRecalcListItems()
     // Manual selection anchor is reset when manipulating the select programmatically.
     m_activeSelectionAnchorIndex = -1;
     setOptionsChangedOnRenderer();
-    setNeedsStyleRecalc(SubtreeStyleChange);
+    setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
     if (!inDocument()) {
-        if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
+        if (HTMLOptionsCollection* collection = cachedCollection<HTMLOptionsCollection>(SelectOptions))
             collection->invalidateCache();
     }
     if (!inDocument())
@@ -726,7 +807,7 @@ void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
         // 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 (current.hasTagName(optgroupTag)) {
+        if (isHTMLOptGroupElement(current)) {
             m_listItems.append(&current);
             if (Element* nextElement = ElementTraversal::firstWithin(current)) {
                 currentElement = nextElement;
@@ -734,7 +815,7 @@ void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
             }
         }
 
-        if (current.hasTagName(optionTag)) {
+        if (isHTMLOptionElement(current)) {
             m_listItems.append(&current);
 
             if (updateSelectedStates && !m_multiple) {
@@ -752,7 +833,7 @@ void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
             }
         }
 
-        if (current.hasTagName(hrTag))
+        if (isHTMLHRElement(current))
             m_listItems.append(&current);
 
         // In conforming HTML code, only <optgroup> and <option> will be found
@@ -773,11 +854,11 @@ int HTMLSelectElement::selectedIndex() const
     unsigned index = 0;
 
     // Return the number of the first option selected.
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (size_t i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        if (element->hasTagName(optionTag)) {
-            if (toHTMLOptionElement(element)->selected())
+        if (isHTMLOptionElement(*element)) {
+            if (toHTMLOptionElement(*element).selected())
                 return index;
             ++index;
         }
@@ -791,6 +872,38 @@ void HTMLSelectElement::setSelectedIndex(int index)
     selectOption(index, DeselectOtherOptions);
 }
 
+int HTMLSelectElement::suggestedIndex() const
+{
+    return m_suggestedIndex;
+}
+
+void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
+{
+    m_suggestedIndex = suggestedIndex;
+
+    if (RenderObject* renderer = this->renderer())  {
+        renderer->updateFromElement();
+        scrollTo(suggestedIndex);
+    }
+}
+
+void HTMLSelectElement::scrollTo(int listIndex)
+{
+    if (listIndex < 0)
+        return;
+    if (usesMenuList())
+        return;
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
+    int listSize = static_cast<int>(items.size());
+    if (listIndex >= listSize)
+        return;
+    document().updateLayoutIgnorePendingStylesheets();
+    if (!renderer() || !renderer()->isListBox())
+        return;
+    LayoutRect bounds = items[listIndex]->boundingBox();
+    toRenderListBox(renderer())->scrollToRect(bounds);
+}
+
 void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
 {
     ASSERT(option->ownerSelectElement() == this);
@@ -802,22 +915,38 @@ void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, b
         selectOption(nextSelectableListIndex(-1));
 }
 
+void HTMLSelectElement::optionRemoved(const HTMLOptionElement& option)
+{
+    if (m_activeSelectionAnchorIndex < 0 && m_activeSelectionEndIndex < 0)
+        return;
+    int listIndex = optionToListIndex(option.index());
+    if (listIndex <= m_activeSelectionAnchorIndex)
+        m_activeSelectionAnchorIndex--;
+    if (listIndex <= m_activeSelectionEndIndex)
+        m_activeSelectionEndIndex--;
+    if (listIndex == selectedIndex())
+        setAutofilled(false);
+}
+
 void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
 {
     bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     int listIndex = optionToListIndex(optionIndex);
 
+    if (selectedIndex() != optionIndex && isAutofilled())
+        setAutofilled(false);
+
     HTMLElement* element = 0;
     if (listIndex >= 0) {
         element = items[listIndex];
-        if (element->hasTagName(optionTag)) {
+        if (isHTMLOptionElement(*element)) {
             if (m_activeSelectionAnchorIndex < 0 || shouldDeselect)
                 setActiveSelectionAnchorIndex(listIndex);
             if (m_activeSelectionEndIndex < 0 || shouldDeselect)
                 setActiveSelectionEndIndex(listIndex);
-            toHTMLOptionElement(element)->setSelectedState(true);
+            toHTMLOptionElement(*element).setSelectedState(true);
         }
     }
 
@@ -830,32 +959,35 @@ void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
 
     scrollToSelection();
 
+    setNeedsValidityCheck();
+
     if (usesMenuList()) {
         m_isProcessingUserDrivenChange = flags & UserDriven;
-        if (flags & DispatchChangeEvent)
-            dispatchChangeEventForMenuList();
+        if (flags & DispatchInputAndChangeEvent)
+            dispatchInputAndChangeEventForMenuList();
         if (RenderObject* renderer = this->renderer()) {
-            if (usesMenuList())
+            if (usesMenuList()) {
                 toRenderMenuList(renderer)->didSetSelectedIndex(listIndex);
-            else if (renderer->isListBox())
-                toRenderListBox(renderer)->selectionChanged();
+            } else if (renderer->isListBox()) {
+                if (AXObjectCache* cache = document().existingAXObjectCache())
+                    cache->selectedChildrenChanged(this);
+            }
         }
     }
 
-    setNeedsValidityCheck();
     notifyFormStateChanged();
 }
 
 int HTMLSelectElement::optionToListIndex(int optionIndex) const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     int listSize = static_cast<int>(items.size());
     if (optionIndex < 0 || optionIndex >= listSize)
         return -1;
 
     int optionIndex2 = -1;
     for (int listIndex = 0; listIndex < listSize; ++listIndex) {
-        if (items[listIndex]->hasTagName(optionTag)) {
+        if (isHTMLOptionElement(*items[listIndex])) {
             ++optionIndex2;
             if (optionIndex2 == optionIndex)
                 return listIndex;
@@ -867,14 +999,14 @@ int HTMLSelectElement::optionToListIndex(int optionIndex) const
 
 int HTMLSelectElement::listToOptionIndex(int listIndex) const
 {
-    const Vector<HTMLElement*>& items = listItems();
-    if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !items[listIndex]->hasTagName(optionTag))
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
+    if (listIndex < 0 || listIndex >= static_cast<int>(items.size()) || !isHTMLOptionElement(*items[listIndex]))
         return -1;
 
     // Actual index of option not counting OPTGROUP entries that may be in list.
     int optionIndex = 0;
     for (int i = 0; i < listIndex; ++i) {
-        if (items[i]->hasTagName(optionTag))
+        if (isHTMLOptionElement(*items[i]))
             ++optionIndex;
     }
 
@@ -896,32 +1028,33 @@ void HTMLSelectElement::dispatchBlurEvent(Element* newFocusedElement)
     // change events for list boxes whenever the selection change is actually made.
     // This matches other browsers' behavior.
     if (usesMenuList())
-        dispatchChangeEventForMenuList();
+        dispatchInputAndChangeEventForMenuList();
     HTMLFormControlElementWithState::dispatchBlurEvent(newFocusedElement);
 }
 
 void HTMLSelectElement::deselectItemsWithoutValidation(HTMLElement* excludeElement)
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        if (element != excludeElement && element->hasTagName(optionTag))
+        if (element != excludeElement && isHTMLOptionElement(*element))
             toHTMLOptionElement(element)->setSelectedState(false);
     }
 }
 
 FormControlState HTMLSelectElement::saveFormControlState() const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     size_t length = items.size();
     FormControlState state;
     for (unsigned i = 0; i < length; ++i) {
-        if (!items[i]->hasTagName(optionTag))
+        if (!isHTMLOptionElement(*items[i]))
             continue;
         HTMLOptionElement* option = toHTMLOptionElement(items[i]);
         if (!option->selected())
             continue;
         state.append(option->value());
+        state.append(String::number(i));
         if (!multiple())
             break;
     }
@@ -930,10 +1063,10 @@ FormControlState HTMLSelectElement::saveFormControlState() const
 
 size_t HTMLSelectElement::searchOptionsForValue(const String& value, size_t listIndexStart, size_t listIndexEnd) const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     size_t loopEndIndex = std::min(items.size(), listIndexEnd);
     for (size_t i = listIndexStart; i < loopEndIndex; ++i) {
-        if (!items[i]->hasLocalName(optionTag))
+        if (!isHTMLOptionElement(items[i]))
             continue;
         if (toHTMLOptionElement(items[i])->value() == value)
             return i;
@@ -945,32 +1078,45 @@ void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
 {
     recalcListItems();
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     size_t itemsSize = items.size();
     if (!itemsSize)
         return;
 
     for (size_t i = 0; i < itemsSize; ++i) {
-        if (!items[i]->hasLocalName(optionTag))
+        if (!isHTMLOptionElement(items[i]))
             continue;
         toHTMLOptionElement(items[i])->setSelectedState(false);
     }
 
+    // The saved state should have at least one value and an index.
+    ASSERT(state.valueSize() >= 2);
     if (!multiple()) {
-        size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
-        if (foundIndex != kNotFound)
-            toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
+        size_t index = state[1].toUInt();
+        if (index < itemsSize && isHTMLOptionElement(items[index]) && toHTMLOptionElement(items[index])->value() == state[0]) {
+            toHTMLOptionElement(items[index])->setSelectedState(true);
+        } else {
+            size_t foundIndex = searchOptionsForValue(state[0], 0, itemsSize);
+            if (foundIndex != kNotFound)
+                toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
+        }
     } else {
         size_t startIndex = 0;
-        for (size_t i = 0; i < state.valueSize(); ++i) {
+        for (size_t i = 0; i < state.valueSize(); i+= 2) {
             const String& value = state[i];
-            size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
-            if (foundIndex == kNotFound)
-                foundIndex = searchOptionsForValue(value, 0, startIndex);
-            if (foundIndex == kNotFound)
-                continue;
-            toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
-            startIndex = foundIndex + 1;
+            const size_t index = state[i + 1].toUInt();
+            if (index < itemsSize && isHTMLOptionElement(items[index]) && toHTMLOptionElement(items[index])->value() == value) {
+                toHTMLOptionElement(items[index])->setSelectedState(true);
+                startIndex = index + 1;
+            } else {
+                size_t foundIndex = searchOptionsForValue(value, startIndex, itemsSize);
+                if (foundIndex == kNotFound)
+                    foundIndex = searchOptionsForValue(value, 0, startIndex);
+                if (foundIndex == kNotFound)
+                    continue;
+                toHTMLOptionElement(items[foundIndex])->setSelectedState(true);
+                startIndex = foundIndex + 1;
+            }
         }
     }
 
@@ -980,11 +1126,10 @@ void HTMLSelectElement::restoreFormControlState(const FormControlState& state)
 
 void HTMLSelectElement::parseMultipleAttribute(const AtomicString& value)
 {
-    bool oldUsesMenuList = usesMenuList();
     m_multiple = !value.isNull();
     setNeedsValidityCheck();
-    if (oldUsesMenuList != usesMenuList())
-        lazyReattachIfAttached();
+
+    lazyReattachIfAttached();
 }
 
 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
@@ -994,12 +1139,12 @@ bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
         return false;
 
     bool successful = false;
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
 
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected() && !toHTMLOptionElement(element)->isDisabledFormControl()) {
-            list.appendData(name, toHTMLOptionElement(element)->value());
+        if (isHTMLOptionElement(*element) && toHTMLOptionElement(*element).selected() && !toHTMLOptionElement(*element).isDisabledFormControl()) {
+            list.appendData(name, toHTMLOptionElement(*element).value());
             successful = true;
         }
     }
@@ -1015,10 +1160,10 @@ void HTMLSelectElement::resetImpl()
     HTMLOptionElement* firstOption = 0;
     HTMLOptionElement* selectedOption = 0;
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
         HTMLElement* element = items[i];
-        if (!element->hasTagName(optionTag))
+        if (!isHTMLOptionElement(*element))
             continue;
 
         if (items[i]->fastHasAttribute(selectedAttr)) {
@@ -1037,51 +1182,66 @@ void HTMLSelectElement::resetImpl()
         firstOption->setSelectedState(true);
 
     setOptionsChangedOnRenderer();
-    setNeedsStyleRecalc(SubtreeStyleChange);
+    setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
     setNeedsValidityCheck();
 }
 
-#if !OS(WIN)
-bool HTMLSelectElement::platformHandleKeydownEvent(KeyboardEvent* event)
+void HTMLSelectElement::handlePopupOpenKeyboardEvent(Event* event)
 {
-    if (!RenderTheme::theme().popsMenuByArrowKeys())
-        return false;
+    focus();
+    // Calling focus() may cause us to lose our renderer. Return true so
+    // that our caller doesn't process the event further, but don't set
+    // the event as handled.
+    if (!renderer() || !renderer()->isMenuList() || isDisabledFormControl())
+        return;
+    // Save the selection so it can be compared to the new selection
+    // when dispatching change events during selectOption, which
+    // gets called from RenderMenuList::valueChanged, which gets called
+    // after the user makes a selection from the menu.
+    saveLastSelection();
+    if (RenderMenuList* menuList = toRenderMenuList(renderer()))
+        menuList->showPopup();
+    int index = selectedIndex();
+    ASSERT(index >= 0);
+    ASSERT_WITH_SECURITY_IMPLICATION(index < static_cast<int>(listItems().size()));
+    setSelectedIndex(index);
+    event->setDefaultHandled();
+    return;
+}
 
-    if (!isSpatialNavigationEnabled(document().frame())) {
-        if (event->keyIdentifier() == "Down" || event->keyIdentifier() == "Up") {
-            focus();
-            // Calling focus() may cause us to lose our renderer. Return true so
-            // that our caller doesn't process the event further, but don't set
-            // the event as handled.
-            if (!renderer())
-                return true;
+bool HTMLSelectElement::shouldOpenPopupForKeyDownEvent(KeyboardEvent* keyEvent)
+{
+    const String& keyIdentifier = keyEvent->keyIdentifier();
+    RenderTheme& renderTheme = RenderTheme::theme();
 
-            // Save the selection so it can be compared to the new selection
-            // when dispatching change events during selectOption, which
-            // gets called from RenderMenuList::valueChanged, which gets called
-            // after the user makes a selection from the menu.
-            saveLastSelection();
-            if (RenderMenuList* menuList = toRenderMenuList(renderer()))
-                menuList->showPopup();
-            event->setDefaultHandled();
-        }
-        return true;
-    }
+    if (isSpatialNavigationEnabled(document().frame()))
+        return false;
 
-    return false;
+    return ((renderTheme.popsMenuByArrowKeys() &&  (keyIdentifier == "Down" || keyIdentifier == "Up"))
+        || (renderTheme.popsMenuByAltDownUpOrF4Key() && (keyIdentifier == "Down" || keyIdentifier == "Up") && keyEvent->altKey())
+        || (renderTheme.popsMenuByAltDownUpOrF4Key() && (!keyEvent->altKey() && !keyEvent->ctrlKey() && keyIdentifier == "F4")));
 }
-#endif
 
-void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
+bool HTMLSelectElement::shouldOpenPopupForKeyPressEvent(KeyboardEvent *event)
 {
     RenderTheme& renderTheme = RenderTheme::theme();
+    int keyCode = event->keyCode();
+
+    return ((renderTheme.popsMenuBySpaceKey() && event->keyCode() == ' ')
+        || (renderTheme.popsMenuByReturnKey() && keyCode == '\r'));
+}
 
+void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
+{
     if (event->type() == EventTypeNames::keydown) {
         if (!renderer() || !event->isKeyboardEvent())
             return;
 
-        if (platformHandleKeydownEvent(toKeyboardEvent(event)))
+        KeyboardEvent* keyEvent = toKeyboardEvent(event);
+        if (shouldOpenPopupForKeyDownEvent(keyEvent)) {
+            handlePopupOpenKeyboardEvent(event);
             return;
+        }
 
         // 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,
@@ -1091,9 +1251,13 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
                 return;
         }
 
-        const String& keyIdentifier = toKeyboardEvent(event)->keyIdentifier();
+        // The key handling below shouldn't be used for non spatial navigation mode Mac
+        if (RenderTheme::theme().popsMenuByArrowKeys() && !isSpatialNavigationEnabled(document().frame()))
+            return;
+
+        const String& keyIdentifier = keyEvent->keyIdentifier();
         bool handled = true;
-        const Vector<HTMLElement*>& listItems = this->listItems();
+        const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
         int listIndex = optionToListIndex(selectedIndex());
 
         if (keyIdentifier == "Down" || keyIdentifier == "Right")
@@ -1112,21 +1276,17 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
             handled = false;
 
         if (handled && static_cast<size_t>(listIndex) < listItems.size())
-            selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
+            selectOption(listToOptionIndex(listIndex), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
 
         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() == EventTypeNames::keypress) {
         if (!renderer() || !event->isKeyboardEvent())
             return;
 
         int keyCode = toKeyboardEvent(event)->keyCode();
-        bool handled = false;
-
         if (keyCode == ' ' && isSpatialNavigationEnabled(document().frame())) {
             // Use space to toggle arrow key handling for selection change or spatial navigation.
             m_activeSelectionState = !m_activeSelectionState;
@@ -1134,56 +1294,23 @@ void HTMLSelectElement::menuListDefaultEventHandler(Event* event)
             return;
         }
 
-        if (renderTheme.popsMenuBySpaceOrReturn()) {
-            if (keyCode == ' ' || keyCode == '\r') {
-                focus();
-
-                // Calling focus() may remove the renderer or change the
-                // renderer type.
-                if (!renderer() || !renderer()->isMenuList())
-                    return;
-
-                // Save the selection so it can be compared to the new selection
-                // when dispatching change events during selectOption, which
-                // gets called from RenderMenuList::valueChanged, which gets called
-                // after the user makes a selection from the menu.
-                saveLastSelection();
-                if (RenderMenuList* menuList = toRenderMenuList(renderer()))
-                    menuList->showPopup();
-                handled = true;
-            }
-        } else if (renderTheme.popsMenuByArrowKeys()) {
-            if (keyCode == ' ') {
-                focus();
-
-                // Calling focus() may remove the renderer or change the
-                // renderer type.
-                if (!renderer() || !renderer()->isMenuList())
-                    return;
-
-                // Save the selection so it can be compared to the new selection
-                // when dispatching change events during selectOption, which
-                // gets called from RenderMenuList::valueChanged, which gets called
-                // after the user makes a selection from the menu.
-                saveLastSelection();
-                if (RenderMenuList* menuList = toRenderMenuList(renderer()))
-                    menuList->showPopup();
-                handled = true;
-            } else if (keyCode == '\r') {
-                if (form())
-                    form()->submitImplicitly(event, false);
-                dispatchChangeEventForMenuList();
-                handled = true;
-            }
+        KeyboardEvent* keyEvent = toKeyboardEvent(event);
+        if (shouldOpenPopupForKeyPressEvent(keyEvent)) {
+            handlePopupOpenKeyboardEvent(event);
+            return;
         }
 
-        if (handled)
+        if (!RenderTheme::theme().popsMenuByReturnKey() && keyCode == '\r') {
+            if (form())
+                form()->submitImplicitly(event, false);
+            dispatchInputAndChangeEventForMenuList();
             event->setDefaultHandled();
+        }
     }
 
     if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
         focus();
-        if (renderer() && renderer()->isMenuList()) {
+        if (renderer() && renderer()->isMenuList() && !isDisabledFormControl()) {
             if (RenderMenuList* menuList = toRenderMenuList(renderer())) {
                 if (menuList->popupIsVisible())
                     menuList->hidePopup();
@@ -1213,6 +1340,11 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif
 {
     ASSERT(listIndex >= 0);
 
+    HTMLElement* clickedElement = listItems()[listIndex];
+    ASSERT(clickedElement);
+    if (isHTMLOptGroupElement(clickedElement))
+        return;
+
     // Save the selection so it can be compared to the new selection when
     // dispatching change events during mouseup, or after autoscroll finishes.
     saveLastSelection();
@@ -1222,14 +1354,13 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif
     bool shiftSelect = m_multiple && shift;
     bool multiSelect = m_multiple && multi && !shift;
 
-    HTMLElement* clickedElement = listItems()[listIndex];
-    if (clickedElement->hasTagName(optionTag)) {
+    if (isHTMLOptionElement(*clickedElement)) {
         // Keep track of whether an active selection (like during drag
         // selection), should select or deselect.
-        if (toHTMLOptionElement(clickedElement)->selected() && multiSelect)
+        if (toHTMLOptionElement(*clickedElement).selected() && multiSelect)
             m_activeSelectionState = false;
         if (!m_activeSelectionState)
-            toHTMLOptionElement(clickedElement)->setSelectedState(false);
+            toHTMLOptionElement(*clickedElement).setSelectedState(false);
     }
 
     // If we're not in any special multiple selection mode, then deselect all
@@ -1244,8 +1375,8 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif
         setActiveSelectionAnchorIndex(selectedIndex());
 
     // Set the selection state of the clicked option.
-    if (clickedElement->hasTagName(optionTag) && !toHTMLOptionElement(clickedElement)->isDisabledFormControl())
-        toHTMLOptionElement(clickedElement)->setSelectedState(true);
+    if (isHTMLOptionElement(*clickedElement) && !toHTMLOptionElement(*clickedElement).isDisabledFormControl())
+        toHTMLOptionElement(*clickedElement).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
@@ -1258,20 +1389,68 @@ void HTMLSelectElement::updateSelectedState(int listIndex, bool multi, bool shif
     updateListBoxSelection(!multiSelect);
 }
 
+int HTMLSelectElement::listIndexForEventTargetOption(const Event& event)
+{
+    Node* targetNode = event.target()->toNode();
+    if (!targetNode || !isHTMLOptionElement(*targetNode))
+        return -1;
+    return listIndexForOption(toHTMLOptionElement(*targetNode));
+}
+
+int HTMLSelectElement::listIndexForOption(const HTMLOptionElement& option)
+{
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = this->listItems();
+    size_t length = items.size();
+    for (size_t i = 0; i < length; ++i) {
+        if (items[i].get() == &option)
+            return i;
+    }
+    return -1;
+}
+
+AutoscrollController* HTMLSelectElement::autoscrollController() const
+{
+    if (Page* page = document().page())
+        return &page->autoscrollController();
+    return 0;
+}
+
+void HTMLSelectElement::handleMouseRelease()
+{
+    // We didn't start this click/drag on any options.
+    if (m_lastOnChangeSelection.isEmpty())
+        return;
+    listBoxOnChange();
+}
+
 void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
 {
-    const Vector<HTMLElement*>& listItems = this->listItems();
-    bool dragSelection = false;
-    if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = this->listItems();
+    if (event->type() == EventTypeNames::gesturetap && event->isGestureEvent()) {
+        focus();
+        // Calling focus() may cause us to lose our renderer or change the render type, in which case do not want to handle the event.
+        if (!renderer() || !renderer()->isListBox())
+            return;
+
+        // Convert to coords relative to the list box if needed.
+        GestureEvent& gestureEvent = toGestureEvent(*event);
+        int listIndex = listIndexForEventTargetOption(gestureEvent);
+        if (listIndex >= 0) {
+            if (!isDisabledFormControl()) {
+                updateSelectedState(listIndex, true, gestureEvent.shiftKey());
+                listBoxOnChange();
+            }
+            event->setDefaultHandled();
+        }
+    } else if (event->type() == EventTypeNames::mousedown && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton) {
         focus();
         // Calling focus() may cause us to lose our renderer, in which case do not want to handle the event.
-        if (!renderer())
+        if (!renderer() || !renderer()->isListBox() || isDisabledFormControl())
             return;
 
         // Convert to coords relative to the list box if needed.
         MouseEvent* mouseEvent = toMouseEvent(event);
-        IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
-        int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
+        int listIndex = listIndexForEventTargetOption(*mouseEvent);
         if (listIndex >= 0) {
             if (!isDisabledFormControl()) {
 #if OS(MACOSX)
@@ -1280,18 +1459,20 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
                 updateSelectedState(listIndex, mouseEvent->ctrlKey(), mouseEvent->shiftKey());
 #endif
             }
-            if (Frame* frame = document().frame())
+            if (LocalFrame* frame = document().frame())
                 frame->eventHandler().setMouseDownMayStartAutoscroll();
 
             event->setDefaultHandled();
         }
-    } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent() && !toRenderBox(renderer())->canBeScrolledAndHasScrollableArea()) {
+    } else if (event->type() == EventTypeNames::mousemove && event->isMouseEvent()) {
         MouseEvent* mouseEvent = toMouseEvent(event);
         if (mouseEvent->button() != LeftButton || !mouseEvent->buttonDown())
             return;
 
-        IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), UseTransforms));
-        int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
+        if (Page* page = document().page())
+            page->autoscrollController().startAutoscrollForSelection(renderer());
+
+        int listIndex = listIndexForEventTargetOption(*mouseEvent);
         if (listIndex >= 0) {
             if (!isDisabledFormControl()) {
                 if (m_multiple) {
@@ -1307,18 +1488,12 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
                     updateListBoxSelection(true);
                 }
             }
-            dragSelection = true;
-        }
-    } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
-        // We didn't start this click/drag on any options.
-        if (m_lastOnChangeSelection.isEmpty())
-            return;
-        // This makes sure we fire dispatchFormControlChangeEvent for a single
-        // click. For drag selection, onChange will fire when the autoscroll
-        // timer stops.
-        if (!dragSelection) {
-            listBoxOnChange();
         }
+    } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer()) {
+        if (document().page() && document().page()->autoscrollController().autoscrollInProgress(toRenderBox(renderer())))
+            document().page()->autoscrollController().stopAutoscroll();
+        else
+            handleMouseRelease();
     } else if (event->type() == EventTypeNames::keydown) {
         if (!event->isKeyboardEvent())
             return;
@@ -1393,7 +1568,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
             }
 
-            toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
+            scrollTo(endIndex);
             if (selectNewItem) {
                 updateListBoxSelection(deselectOthers);
                 listBoxOnChange();
@@ -1451,10 +1626,10 @@ void HTMLSelectElement::defaultEventHandler(Event* event)
 
 int HTMLSelectElement::lastSelectedListIndex() const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (size_t i = items.size(); i;) {
         HTMLElement* element = items[--i];
-        if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
+        if (isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected())
             return i;
     }
     return -1;
@@ -1472,10 +1647,10 @@ int HTMLSelectElement::optionCount() const
 
 String HTMLSelectElement::optionAtIndex(int index) const
 {
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
 
     HTMLElement* element = items[index];
-    if (!element->hasTagName(optionTag) || toHTMLOptionElement(element)->isDisabledFormControl())
+    if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl())
         return String();
     return toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
 }
@@ -1485,7 +1660,7 @@ void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
     int index = m_typeAhead.handleEvent(event, TypeAhead::MatchPrefix | TypeAhead::CycleFirstChar);
     if (index < 0)
         return;
-    selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchChangeEvent | UserDriven);
+    selectOption(listToOptionIndex(index), DeselectOtherOptions | DispatchInputAndChangeEvent | UserDriven);
     if (!usesMenuList())
         listBoxOnChange();
 }
@@ -1507,20 +1682,20 @@ void HTMLSelectElement::accessKeySetSelectedIndex(int index)
         accessKeyAction(false);
 
     // If this index is already selected, unselect. otherwise update the selected index.
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     int listIndex = optionToListIndex(index);
     if (listIndex >= 0) {
         HTMLElement* element = items[listIndex];
-        if (element->hasTagName(optionTag)) {
-            if (toHTMLOptionElement(element)->selected())
-                toHTMLOptionElement(element)->setSelectedState(false);
+        if (isHTMLOptionElement(*element)) {
+            if (toHTMLOptionElement(*element).selected())
+                toHTMLOptionElement(*element).setSelectedState(false);
             else
-                selectOption(index, DispatchChangeEvent | UserDriven);
+                selectOption(index, DispatchInputAndChangeEvent | UserDriven);
         }
     }
 
     if (usesMenuList())
-        dispatchChangeEventForMenuList();
+        dispatchInputAndChangeEventForMenuList();
     else
         listBoxOnChange();
 
@@ -1531,9 +1706,9 @@ unsigned HTMLSelectElement::length() const
 {
     unsigned options = 0;
 
-    const Vector<HTMLElement*>& items = listItems();
+    const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& items = listItems();
     for (unsigned i = 0; i < items.size(); ++i) {
-        if (items[i]->hasTagName(optionTag))
+        if (isHTMLOptionElement(*items[i]))
             ++options;
     }
 
@@ -1544,9 +1719,11 @@ void HTMLSelectElement::finishParsingChildren()
 {
     HTMLFormControlElementWithState::finishParsingChildren();
     updateListItemSelectedStates();
+    if (!usesMenuList())
+        scrollToSelection();
 }
 
-bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
+bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtrWillBeRawPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
 {
     if (!value) { // undefined or null
         remove(index);
@@ -1566,4 +1743,37 @@ bool HTMLSelectElement::supportsAutofocus() const
     return true;
 }
 
+void HTMLSelectElement::updateListOnRenderer()
+{
+    setOptionsChangedOnRenderer();
+}
+
+void HTMLSelectElement::trace(Visitor* visitor)
+{
+#if ENABLE(OILPAN)
+    visitor->trace(m_listItems);
+#endif
+    HTMLFormControlElementWithState::trace(visitor);
+}
+
+void HTMLSelectElement::didAddUserAgentShadowRoot(ShadowRoot& root)
+{
+    RefPtrWillBeRawPtr<HTMLContentElement> content = HTMLContentElement::create(document());
+    content->setAttribute(selectAttr, "option,optgroup");
+    root.appendChild(content);
+}
+
+HTMLOptionElement* HTMLSelectElement::spatialNavigationFocusedOption()
+{
+    if (!isSpatialNavigationEnabled(document().frame()))
+        return nullptr;
+    int focusedIndex = activeSelectionEndListIndex();
+    if (focusedIndex < 0)
+        focusedIndex = firstSelectableListIndex();
+    if (focusedIndex < 0)
+        return nullptr;
+    HTMLElement* focused = listItems()[focusedIndex];
+    return isHTMLOptionElement(focused) ? toHTMLOptionElement(focused) : nullptr;
+}
+
 } // namespace