Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(750)

Unified Diff: Source/core/html/HTMLSelectElement.cpp

Issue 347773002: Implement select listbox using shadow DOM (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: White background Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/core/html/HTMLSelectElement.cpp
diff --git a/Source/core/html/HTMLSelectElement.cpp b/Source/core/html/HTMLSelectElement.cpp
index 3b7744d2667d8d9a983bc53df0ad8de19c479a83..8141ef9ccbb460400d5b84a34448ec06de525d79 100644
--- a/Source/core/html/HTMLSelectElement.cpp
+++ b/Source/core/html/HTMLSelectElement.cpp
@@ -39,16 +39,23 @@
#include "core/events/GestureEvent.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/MouseEvent.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/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"
@@ -80,12 +87,16 @@ HTMLSelectElement::HTMLSelectElement(Document& document, HTMLFormElement* form)
PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document)
{
- return adoptRefWillBeNoop(new HTMLSelectElement(document, 0));
+ RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, 0));
+ select->ensureUserAgentShadowRoot();
+ return select.release();
}
PassRefPtrWillBeRawPtr<HTMLSelectElement> HTMLSelectElement::create(Document& document, HTMLFormElement* form)
{
- return adoptRefWillBeNoop(new HTMLSelectElement(document, form));
+ RefPtrWillBeRawPtr<HTMLSelectElement> select = adoptRefWillBeNoop(new HTMLSelectElement(document, form));
+ select->ensureUserAgentShadowRoot();
+ return select.release();
}
const AtomicString& HTMLSelectElement::formControlType() const
@@ -334,7 +345,7 @@ void HTMLSelectElement::parseAttribute(const QualifiedName& name, const AtomicSt
m_size = size;
setNeedsValidityCheck();
- if (m_size != oldSize && inActiveDocument()) {
+ if (m_size != oldSize && document().isActive() && inDocument()) {
tkent 2014/07/08 04:43:46 This change looks unnecessary.
keishi 2014/07/10 09:48:03 Done.
lazyReattachIfAttached();
setRecalcListItems();
}
@@ -366,7 +377,7 @@ bool HTMLSelectElement::canSelectAll() const
return !usesMenuList();
}
-RenderObject* HTMLSelectElement::createRenderer(RenderStyle*)
+RenderObject* HTMLSelectElement::createRenderer(RenderStyle* style)
tkent 2014/07/08 02:20:59 This change looks unnecessary.
keishi 2014/07/10 09:48:03 Done.
{
if (usesMenuList())
return new RenderMenuList(this);
@@ -396,7 +407,6 @@ void HTMLSelectElement::childrenChanged(const ChildrenChange& change)
{
setRecalcListItems();
setNeedsValidityCheck();
- m_lastOnChangeSelection.clear();
tkent 2014/07/08 04:43:46 Why do you remove this?
keishi 2014/07/10 09:48:03 Reverted. See comment for HTMLSelectElement::listB
HTMLFormControlElementWithState::childrenChanged(change);
}
@@ -523,8 +533,15 @@ int HTMLSelectElement::nextValidIndex(int listIndex, SkipDirection direction, in
for (listIndex += direction; listIndex >= 0 && listIndex < size; listIndex += direction) {
--skip;
HTMLElement* element = listItems[listIndex];
- if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || toHTMLOptionElement(element)->isDisplayNone())
+ if (isHTMLOptionElement(*element)) {
+ const HTMLOptionElement& option = toHTMLOptionElement(*element);
tkent 2014/07/08 04:43:46 This cast is unnecessary. isDisabledFormControl an
keishi 2014/07/10 09:48:03 Done.
+ if (option.isDisabledFormControl())
+ continue;
+ if (!usesMenuList() && !option.renderer())
+ continue;
+ } else {
continue;
tkent 2014/07/08 04:43:46 We prefer early continue. if (!isHTMLOptionElemen
keishi 2014/07/10 09:48:03 Done.
+ }
lastGoodIndex = listIndex;
if (skip <= 0)
break;
@@ -626,7 +643,10 @@ void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
void HTMLSelectElement::setActiveSelectionEndIndex(int index)
{
+ if (index == m_activeSelectionEndIndex)
+ return;
m_activeSelectionEndIndex = index;
+ setNeedsStyleRecalc(SubtreeStyleChange);
}
void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
@@ -640,7 +660,7 @@ void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
- if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || toHTMLOptionElement(element)->isDisplayNone())
+ if (!isHTMLOptionElement(*element) || toHTMLOptionElement(element)->isDisabledFormControl() || !toHTMLOptionElement(element)->renderer())
continue;
if (i >= start && i <= end)
@@ -651,8 +671,8 @@ void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
toHTMLOptionElement(element)->setSelectedState(m_cachedStateForActiveSelection[i]);
}
- scrollToSelection();
setNeedsValidityCheck();
+ scrollToSelection();
notifyFormStateChanged();
}
@@ -662,25 +682,24 @@ void HTMLSelectElement::listBoxOnChange()
const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
- // If the cached selection list is empty, or the size has changed, then fire
- // dispatchFormControlChangeEvent, and return early.
- // FIXME: Why? This looks unreasonable.
- if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
- dispatchFormControlChangeEvent();
- return;
- }
-
// Update m_lastOnChangeSelection and fire dispatchFormControlChangeEvent.
bool fireOnChange = false;
for (unsigned i = 0; i < items.size(); ++i) {
HTMLElement* element = items[i];
bool selected = isHTMLOptionElement(*element) && toHTMLOptionElement(element)->selected();
- if (selected != m_lastOnChangeSelection[i])
+ if (i < m_lastOnChangeSelection.size()) {
+ if (selected != m_lastOnChangeSelection[i]) {
+ fireOnChange = true;
+ break;
+ }
+ } else if (selected) {
fireOnChange = true;
- m_lastOnChangeSelection[i] = selected;
+ break;
+ }
}
if (fireOnChange) {
+ saveLastSelection();
RefPtrWillBeRawPtr<HTMLSelectElement> protector(this);
dispatchInputEvent();
dispatchFormControlChangeEvent();
@@ -703,11 +722,13 @@ void HTMLSelectElement::dispatchInputAndChangeEventForMenuList(bool requiresUser
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()
@@ -715,8 +736,6 @@ void HTMLSelectElement::setOptionsChangedOnRenderer()
if (RenderObject* renderer = this->renderer()) {
if (usesMenuList())
toRenderMenuList(renderer)->setOptionsChanged(true);
- else
- toRenderListBox(renderer)->setOptionsChanged(true);
}
}
@@ -858,11 +877,21 @@ void HTMLSelectElement::setSuggestedIndex(int suggestedIndex)
if (RenderObject* renderer = this->renderer()) {
renderer->updateFromElement();
- if (renderer->isListBox())
- toRenderListBox(renderer)->scrollToRevealElementAtListIndex(suggestedIndex);
+ scrollTo(suggestedIndex);
}
}
+void HTMLSelectElement::scrollTo(int listIndex)
+{
+ if (listIndex < 0)
+ return;
+ const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& items = listItems();
+ int listSize = static_cast<int>(items.size());
+ if (listIndex >= listSize)
+ return;
+ items[listIndex]->scrollIntoViewIfNeeded(false);
+}
+
void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, bool optionIsSelected)
{
ASSERT(option->ownerSelectElement() == this);
@@ -874,6 +903,17 @@ void HTMLSelectElement::optionSelectionStateChanged(HTMLOptionElement* option, b
selectOption(nextSelectableListIndex(-1));
}
+void HTMLSelectElement::optionRemoved(HTMLOptionElement* option)
tkent 2014/07/08 04:43:46 probably the argument type should be |const HTMLOp
keishi 2014/07/10 09:48:03 Done.
keishi 2014/07/10 09:48:03 Done.
+{
+ 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--;
+}
+
void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
{
bool shouldDeselect = !m_multiple || (flags & DeselectOtherOptions);
@@ -909,10 +949,12 @@ void HTMLSelectElement::selectOption(int optionIndex, SelectOptionFlags flags)
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);
+ }
}
}
@@ -1053,11 +1095,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)
@@ -1335,6 +1376,32 @@ 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] == &option)
+ return i;
+ }
+ return -1;
+}
+
+AutoscrollController* HTMLSelectElement::autoscrollController() const
+{
+ if (Page* page = document().page())
+ return &page->autoscrollController();
+ return 0;
+}
+
void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
{
const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = this->listItems();
@@ -1346,8 +1413,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
// Convert to coords relative to the list box if needed.
GestureEvent& gestureEvent = toGestureEvent(*event);
- IntPoint localOffset = roundedIntPoint(renderer()->absoluteToLocal(gestureEvent.absoluteLocation(), UseTransforms));
- int listIndex = toRenderListBox(renderer())->listIndexAtOffset(toIntSize(localOffset));
+ int listIndex = listIndexForEventTargetOption(gestureEvent);
if (listIndex >= 0) {
if (!isDisabledFormControl())
updateSelectedState(listIndex, true, gestureEvent.shiftKey());
@@ -1359,10 +1425,12 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
if (!renderer() || !renderer()->isListBox() || isDisabledFormControl())
return;
+ if (Page* page = document().page())
+ page->autoscrollController().startAutoscrollForSelection(renderer());
+
// 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)
@@ -1376,13 +1444,11 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
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));
+ int listIndex = listIndexForEventTargetOption(*mouseEvent);
if (listIndex >= 0) {
if (!isDisabledFormControl()) {
if (m_multiple) {
@@ -1399,7 +1465,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
}
}
}
- } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer() && !toRenderBox(renderer())->autoscrollInProgress()) {
tkent 2014/07/08 04:43:46 Is it safe to remove autoscrollInProgress check?
keishi 2014/07/10 09:48:03 I added back this check and added a call to stopAu
+ } else if (event->type() == EventTypeNames::mouseup && event->isMouseEvent() && toMouseEvent(event)->button() == LeftButton && renderer()) {
// We didn't start this click/drag on any options.
if (m_lastOnChangeSelection.isEmpty())
return;
@@ -1478,7 +1544,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event* event)
setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
}
- toRenderListBox(renderer())->scrollToRevealElementAtListIndex(endIndex);
+ scrollTo(endIndex);
if (selectNewItem) {
updateListBoxSelection(deselectOthers);
listBoxOnChange();
@@ -1629,6 +1695,7 @@ void HTMLSelectElement::finishParsingChildren()
{
HTMLFormControlElementWithState::finishParsingChildren();
updateListItemSelectedStates();
+ scrollToSelection();
}
bool HTMLSelectElement::anonymousIndexedSetter(unsigned index, PassRefPtrWillBeRawPtr<HTMLOptionElement> value, ExceptionState& exceptionState)
@@ -1664,4 +1731,24 @@ void HTMLSelectElement::trace(Visitor* visitor)
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

Powered by Google App Engine
This is Rietveld 408576698