summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc')
-rw-r--r--chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc1211
1 files changed, 250 insertions, 961 deletions
diff --git a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc
index a02e8b059ac..a5f4eb0657c 100644
--- a/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/chromium/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
#include "build/build_config.h"
+#include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "third_party/blink/renderer/bindings/core/v8/html_element_or_long.h"
@@ -39,45 +40,38 @@
#include "third_party/blink/renderer/core/dom/attribute.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
-#include "third_party/blink/renderer/core/dom/mutation_observer.h"
-#include "third_party/blink/renderer/core/dom/mutation_observer_init.h"
-#include "third_party/blink/renderer/core/dom/mutation_record.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
-#include "third_party/blink/renderer/core/events/gesture_event.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
-#include "third_party/blink/renderer/core/events/mouse_event.h"
-#include "third_party/blink/renderer/core/frame/local_dom_window.h"
-#include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/forms/form_controller.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h"
#include "third_party/blink/renderer/core/html/forms/html_option_element.h"
-#include "third_party/blink/renderer/core/html/forms/popup_menu.h"
+#include "third_party/blink/renderer/core/html/forms/menu_list_inner_element.h"
+#include "third_party/blink/renderer/core/html/forms/select_type.h"
#include "third_party/blink/renderer/core/html/html_hr_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html_names.h"
-#include "third_party/blink/renderer/core/input/event_handler.h"
-#include "third_party/blink/renderer/core/input/input_device_capabilities.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/hit_test_request.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
-#include "third_party/blink/renderer/core/layout/layout_list_box.h"
-#include "third_party/blink/renderer/core/layout/layout_menu_list.h"
+#include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/layout_object_factory.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
-#include "third_party/blink/renderer/core/page/autoscroll_controller.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/spatial_navigation.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
+#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
+#include "ui/base/ui_base_features.h"
namespace blink {
@@ -86,18 +80,21 @@ namespace blink {
// signed.
static const unsigned kMaxListItems = INT_MAX;
+// Default size when the multiple attribute is present but size attribute is
+// absent.
+const int kDefaultListBoxSize = 4;
+
HTMLSelectElement::HTMLSelectElement(Document& document)
: HTMLFormControlElementWithState(html_names::kSelectTag, document),
type_ahead_(this),
size_(0),
last_on_change_option_(nullptr),
is_multiple_(false),
- is_in_non_contiguous_selection_(false),
- active_selection_state_(false),
should_recalc_list_items_(false),
is_autofilled_by_preview_(false),
- index_to_select_on_cancel_(-1),
- popup_is_visible_(false) {
+ index_to_select_on_cancel_(-1) {
+ // Make sure SelectType is created after initializing |uses_menu_list_|.
+ select_type_ = SelectType::Create(*this);
SetHasCustomStyleCallbacks();
EnsureUserAgentShadowRoot();
}
@@ -106,7 +103,7 @@ HTMLSelectElement::~HTMLSelectElement() = default;
// static
bool HTMLSelectElement::CanAssignToSelectSlot(const Node& node) {
- // Even if options/optgroups are not rendered as children of LayoutMenuList,
+ // Even if options/optgroups are not rendered as children of menulist SELECT,
// we still need to add them to the flat tree through slotting since we need
// their ComputedStyle for popup rendering.
return node.HasTagName(html_names::kOptionTag) ||
@@ -163,9 +160,6 @@ String HTMLSelectElement::validationMessage() const {
}
bool HTMLSelectElement::ValueMissing() const {
- if (!willValidate())
- return false;
-
if (!IsRequired())
return false;
@@ -187,21 +181,48 @@ void HTMLSelectElement::SelectMultipleOptionsByPopup(
const Vector<int>& list_indices) {
DCHECK(UsesMenuList());
DCHECK(IsMultiple());
- for (wtf_size_t i = 0; i < list_indices.size(); ++i) {
- bool add_selection_if_not_first = i > 0;
- if (HTMLOptionElement* option = OptionAtListIndex(list_indices[i]))
- UpdateSelectedState(option, add_selection_if_not_first, false);
+
+ HeapHashSet<Member<HTMLOptionElement>> old_selection;
+ for (auto* option : GetOptionList()) {
+ if (option->Selected()) {
+ old_selection.insert(option);
+ option->SetSelectedState(false);
+ }
}
+
+ bool has_new_selection = false;
+ for (int list_index : list_indices) {
+ if (auto* option = OptionAtListIndex(list_index)) {
+ option->SetSelectedState(true);
+ option->SetDirty(true);
+ auto iter = old_selection.find(option);
+ if (iter != old_selection.end())
+ old_selection.erase(iter);
+ else
+ has_new_selection = true;
+ }
+ }
+
SetNeedsValidityCheck();
- // TODO(tkent): Using listBoxOnChange() is very confusing.
- ListBoxOnChange();
+ if (has_new_selection || !old_selection.IsEmpty()) {
+ DispatchInputEvent();
+ DispatchChangeEvent();
+ }
}
-bool HTMLSelectElement::UsesMenuList() const {
- if (LayoutTheme::GetTheme().DelegatesMenuListRendering())
- return true;
+unsigned HTMLSelectElement::ListBoxSize() const {
+ DCHECK(!UsesMenuList());
+ const unsigned specified_size = size();
+ if (specified_size >= 1)
+ return specified_size;
+ return kDefaultListBoxSize;
+}
- return !is_multiple_ && size_ <= 1;
+void HTMLSelectElement::UpdateUsesMenuList() {
+ if (LayoutTheme::GetTheme().DelegatesMenuListRendering())
+ uses_menu_list_ = true;
+ else
+ uses_menu_list_ = !is_multiple_ && size_ <= 1;
}
int HTMLSelectElement::ActiveSelectionEndListIndex() const {
@@ -269,8 +290,8 @@ void HTMLSelectElement::setValue(const String& value, bool send_events) {
flags |= kDispatchInputAndChangeEventFlag;
SelectOption(option, flags);
- if (send_events && previous_selected_option != option && !UsesMenuList())
- ListBoxOnChange();
+ if (send_events && previous_selected_option != option)
+ select_type_->ListBoxOnChange();
}
String HTMLSelectElement::SuggestedValue() const {
@@ -314,9 +335,10 @@ void HTMLSelectElement::ParseAttribute(
SetNeedsValidityCheck();
if (size_ != old_size) {
ChangeRendering();
+ UpdateUserAgentShadowTree(*UserAgentShadowRoot());
ResetToDefaultSelection();
- if (!UsesMenuList())
- SaveListboxActiveSelection();
+ select_type_->UpdateTextStyleAndContent();
+ select_type_->SaveListboxActiveSelection();
}
} else if (params.name == html_names::kMultipleAttr) {
ParseMultipleAttribute(params.new_value);
@@ -332,15 +354,29 @@ bool HTMLSelectElement::MayTriggerVirtualKeyboard() const {
return true;
}
+bool HTMLSelectElement::ShouldHaveFocusAppearance() const {
+ // For FormControlsRefresh don't draw focus ring for a select that has its
+ // popup open.
+ if (::features::IsFormControlsRefreshEnabled() && PopupIsVisible())
+ return false;
+
+ return HTMLFormControlElementWithState::ShouldHaveFocusAppearance();
+}
+
bool HTMLSelectElement::CanSelectAll() const {
return !UsesMenuList();
}
-LayoutObject* HTMLSelectElement::CreateLayoutObject(const ComputedStyle&,
- LegacyLayout) {
+bool HTMLSelectElement::TypeShouldForceLegacyLayout() const {
+ return UsesMenuList();
+}
+
+LayoutObject* HTMLSelectElement::CreateLayoutObject(
+ const ComputedStyle& style,
+ LegacyLayout legacy_layout) {
if (UsesMenuList())
- return new LayoutMenuList(this);
- return new LayoutListBox(this);
+ return LayoutObjectFactory::CreateFlexibleBox(*this, style, legacy_layout);
+ return LayoutObjectFactory::CreateBlockFlow(*this, style, legacy_layout);
}
HTMLCollection* HTMLSelectElement::selectedOptions() {
@@ -355,9 +391,9 @@ void HTMLSelectElement::OptionElementChildrenChanged(
const HTMLOptionElement& option) {
SetNeedsValidityCheck();
+ if (option.Selected())
+ select_type_->UpdateTextStyleAndContent();
if (GetLayoutObject()) {
- if (option.Selected() && UsesMenuList())
- GetLayoutObject()->UpdateFromElement();
if (AXObjectCache* cache =
GetLayoutObject()->GetDocument().ExistingAXObjectCache())
cache->ChildrenChanged(this);
@@ -385,7 +421,7 @@ void HTMLSelectElement::SetOption(unsigned index,
// We should check |index >= maxListItems| first to avoid integer overflow.
if (index >= kMaxListItems ||
GetListItems().size() + diff + 1 > kMaxListItems) {
- GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
String::Format("Blocked to expand the option list and set an option at "
@@ -418,7 +454,7 @@ void HTMLSelectElement::setLength(unsigned new_len,
// We should check |newLen > maxListItems| first to avoid integer overflow.
if (new_len > kMaxListItems ||
GetListItems().size() + new_len - length() > kMaxListItems) {
- GetDocument().AddConsoleMessage(ConsoleMessage::Create(
+ GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
String::Format("Blocked to expand the option list to %u items. The "
@@ -469,246 +505,19 @@ HTMLOptionElement* HTMLSelectElement::OptionAtListIndex(int list_index) const {
return DynamicTo<HTMLOptionElement>(items[list_index].Get());
}
-// Returns the 1st valid OPTION |skip| items from |listIndex| in direction
-// |direction| if there is one.
-// Otherwise, it returns the valid OPTION closest to that boundary which is past
-// |listIndex| if there is one.
-// Otherwise, it returns nullptr.
-// Valid means that it is enabled and visible.
-HTMLOptionElement* HTMLSelectElement::NextValidOption(int list_index,
- SkipDirection direction,
- int skip) const {
- DCHECK(direction == kSkipBackwards || direction == kSkipForwards);
- const ListItems& list_items = GetListItems();
- HTMLOptionElement* last_good_option = nullptr;
- int size = list_items.size();
- for (list_index += direction; list_index >= 0 && list_index < size;
- list_index += direction) {
- --skip;
- HTMLElement* element = list_items[list_index];
- auto* option_element = DynamicTo<HTMLOptionElement>(element);
- if (!option_element)
- continue;
- if (option_element->IsDisplayNone())
- continue;
- if (element->IsDisabledFormControl())
- continue;
- if (!UsesMenuList() && !element->GetLayoutObject())
- continue;
- last_good_option = option_element;
- if (skip <= 0)
- break;
- }
- return last_good_option;
-}
-
-HTMLOptionElement* HTMLSelectElement::NextSelectableOption(
- HTMLOptionElement* start_option) const {
- return NextValidOption(start_option ? start_option->ListIndex() : -1,
- kSkipForwards, 1);
-}
-
-HTMLOptionElement* HTMLSelectElement::PreviousSelectableOption(
- HTMLOptionElement* start_option) const {
- return NextValidOption(
- start_option ? start_option->ListIndex() : GetListItems().size(),
- kSkipBackwards, 1);
-}
-
-HTMLOptionElement* HTMLSelectElement::FirstSelectableOption() const {
- // TODO(tkent): This is not efficient. nextSlectableOption(nullptr) is
- // faster.
- return NextValidOption(GetListItems().size(), kSkipBackwards, INT_MAX);
-}
-
-HTMLOptionElement* HTMLSelectElement::LastSelectableOption() const {
- // TODO(tkent): This is not efficient. previousSlectableOption(nullptr) is
- // faster.
- return NextValidOption(-1, kSkipForwards, INT_MAX);
-}
-
-// Returns the index of the next valid item one page away from |startIndex| in
-// direction |direction|.
-HTMLOptionElement* HTMLSelectElement::NextSelectableOptionPageAway(
- HTMLOptionElement* start_option,
- SkipDirection direction) const {
- const ListItems& items = GetListItems();
- // Can't use size_ because LayoutObject forces a minimum size.
- int page_size = 0;
- if (GetLayoutObject()->IsListBox()) {
- // -1 so we still show context.
- page_size = ToLayoutListBox(GetLayoutObject())->size() - 1;
- }
-
- // One page away, but not outside valid bounds.
- // If there is a valid option item one page away, the index is chosen.
- // If there is no exact one page away valid option, returns startIndex or
- // the most far index.
- int start_index = start_option ? start_option->ListIndex() : -1;
- int edge_index = (direction == kSkipForwards) ? 0 : (items.size() - 1);
- int skip_amount =
- page_size +
- ((direction == kSkipForwards) ? start_index : (edge_index - start_index));
- return NextValidOption(edge_index, direction, skip_amount);
-}
-
void HTMLSelectElement::SelectAll() {
- DCHECK(!UsesMenuList());
- if (!GetLayoutObject() || !is_multiple_)
- return;
-
- // Save the selection so it can be compared to the new selectAll selection
- // when dispatching change events.
- SaveLastSelection();
-
- active_selection_state_ = true;
- SetActiveSelectionAnchor(NextSelectableOption(nullptr));
- SetActiveSelectionEnd(PreviousSelectableOption(nullptr));
-
- UpdateListBoxSelection(false, false);
- ListBoxOnChange();
- SetNeedsValidityCheck();
-}
-
-void HTMLSelectElement::SaveLastSelection() {
- if (UsesMenuList()) {
- last_on_change_option_ = SelectedOption();
- return;
- }
-
- last_on_change_selection_.clear();
- for (auto& element : GetListItems()) {
- auto* option_element = DynamicTo<HTMLOptionElement>(element.Get());
- last_on_change_selection_.push_back(option_element &&
- option_element->Selected());
- }
+ select_type_->SelectAll();
}
void HTMLSelectElement::SetActiveSelectionAnchor(HTMLOptionElement* option) {
active_selection_anchor_ = option;
- if (!UsesMenuList())
- SaveListboxActiveSelection();
-}
-
-void HTMLSelectElement::SaveListboxActiveSelection() {
- // Cache the selection state so we can restore the old selection as the new
- // selection pivots around this anchor index.
- // Example:
- // 1. Press the mouse button on the second OPTION
- // active_selection_anchor_ points the second OPTION.
- // 2. Drag the mouse pointer onto the fifth OPTION
- // active_selection_end_ points the fifth OPTION, OPTIONs at 1-4 indices
- // are selected.
- // 3. Drag the mouse pointer onto the fourth OPTION
- // active_selection_end_ points the fourth OPTION, OPTIONs at 1-3 indices
- // are selected.
- // UpdateListBoxSelection needs to clear selection of the fifth OPTION.
- cached_state_for_active_selection_.resize(0);
- for (auto* const option : GetOptionList()) {
- cached_state_for_active_selection_.push_back(option->Selected());
- }
+ select_type_->SaveListboxActiveSelection();
}
void HTMLSelectElement::SetActiveSelectionEnd(HTMLOptionElement* option) {
active_selection_end_ = option;
}
-void HTMLSelectElement::UpdateListBoxSelection(bool deselect_other_options,
- bool scroll) {
- DCHECK(GetLayoutObject());
- DCHECK(GetLayoutObject()->IsListBox() || is_multiple_);
-
- int active_selection_anchor_index =
- active_selection_anchor_ ? active_selection_anchor_->index() : -1;
- int active_selection_end_index =
- active_selection_end_ ? active_selection_end_->index() : -1;
- int start =
- std::min(active_selection_anchor_index, active_selection_end_index);
- int end = std::max(active_selection_anchor_index, active_selection_end_index);
-
- int i = 0;
- for (auto* const option : GetOptionList()) {
- if (option->IsDisabledFormControl() || !option->GetLayoutObject()) {
- ++i;
- continue;
- }
- if (i >= start && i <= end) {
- option->SetSelectedState(active_selection_state_);
- option->SetDirty(true);
- } else if (deselect_other_options ||
- i >= static_cast<int>(
- cached_state_for_active_selection_.size())) {
- option->SetSelectedState(false);
- option->SetDirty(true);
- } else {
- option->SetSelectedState(cached_state_for_active_selection_[i]);
- }
- ++i;
- }
-
- UpdateMultiSelectListBoxFocus();
- SetNeedsValidityCheck();
- if (scroll)
- ScrollToSelection();
- NotifyFormStateChanged();
-}
-
-void HTMLSelectElement::ListBoxOnChange() {
- DCHECK(!UsesMenuList() || is_multiple_);
-
- const ListItems& items = GetListItems();
-
- // If the cached selection list is empty, or the size has changed, then fire
- // dispatchFormControlChangeEvent, and return early.
- // FIXME: Why? This looks unreasonable.
- if (last_on_change_selection_.IsEmpty() ||
- last_on_change_selection_.size() != items.size()) {
- DispatchChangeEvent();
- return;
- }
-
- // Update last_on_change_selection_ and fire a 'change' event.
- bool fire_on_change = false;
- for (unsigned i = 0; i < items.size(); ++i) {
- HTMLElement* element = items[i];
- auto* option_element = DynamicTo<HTMLOptionElement>(element);
- bool selected = option_element && option_element->Selected();
- if (selected != last_on_change_selection_[i])
- fire_on_change = true;
- last_on_change_selection_[i] = selected;
- }
-
- if (fire_on_change) {
- DispatchInputEvent();
- DispatchChangeEvent();
- }
-}
-
-void HTMLSelectElement::UpdateMultiSelectListBoxFocus() {
- if (!is_multiple_)
- return;
-
- for (auto* const option : GetOptionList()) {
- if (option->IsDisabledFormControl() || !option->GetLayoutObject())
- continue;
- bool is_focused =
- (option == active_selection_end_) && is_in_non_contiguous_selection_;
- option->SetMultiSelectFocusedState(is_focused);
- }
- ScrollToSelection();
-}
-
-void HTMLSelectElement::DispatchInputAndChangeEventForMenuList() {
- DCHECK(UsesMenuList());
-
- HTMLOptionElement* selected_option = SelectedOption();
- if (last_on_change_option_.Get() != selected_option) {
- last_on_change_option_ = selected_option;
- DispatchInputEvent();
- DispatchChangeEvent();
- }
-}
-
void HTMLSelectElement::ScrollToSelection() {
if (!IsFinishedParsingChildren())
return;
@@ -719,16 +528,6 @@ void HTMLSelectElement::ScrollToSelection() {
cache->ListboxActiveIndexChanged(this);
}
-void HTMLSelectElement::SetOptionsChangedOnLayoutObject() {
- if (LayoutObject* layout_object = GetLayoutObject()) {
- if (!UsesMenuList())
- return;
- ToLayoutMenuList(layout_object)
- ->SetNeedsLayoutAndPrefWidthsRecalc(
- layout_invalidation_reason::kMenuOptionsChanged);
- }
-}
-
const HTMLSelectElement::ListItems& HTMLSelectElement::GetListItems() const {
if (should_recalc_list_items_) {
RecalcListItems();
@@ -755,7 +554,7 @@ void HTMLSelectElement::SetRecalcListItems() {
should_recalc_list_items_ = true;
- SetOptionsChangedOnLayoutObject();
+ select_type_->MaximumOptionWidthMightBeChanged();
if (!isConnected()) {
if (HTMLOptionsCollection* collection =
CachedCollection<HTMLOptionsCollection>(kSelectOptions))
@@ -903,12 +702,7 @@ void HTMLSelectElement::SetSuggestedOption(HTMLOptionElement* option) {
return;
suggested_option_ = option;
- if (LayoutObject* layout_object = GetLayoutObject()) {
- layout_object->UpdateFromElement();
- ScrollToOption(option);
- }
- if (PopupIsVisible())
- popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
+ select_type_->DidSetSuggestedOption(option);
}
void HTMLSelectElement::ScrollToOption(HTMLOptionElement* option) {
@@ -936,11 +730,23 @@ void HTMLSelectElement::ScrollToOptionTask() {
// OptionRemoved() makes sure option_to_scroll_to_ doesn't have an option with
// another owner.
DCHECK_EQ(option->OwnerSelectElement(), this);
- GetDocument().UpdateStyleAndLayout();
- if (!GetLayoutObject() || !GetLayoutObject()->IsListBox())
+ GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kScroll);
+ if (!GetLayoutObject() || UsesMenuList())
return;
PhysicalRect bounds = option->BoundingBoxForScrollIntoView();
- ToLayoutListBox(GetLayoutObject())->ScrollToRect(bounds);
+
+ // The following code will not scroll parent boxes unlike ScrollRectToVisible.
+ auto* box = GetLayoutBox();
+ if (!box->HasOverflowClip())
+ return;
+ DCHECK(box->Layer());
+ DCHECK(box->Layer()->GetScrollableArea());
+ box->Layer()->GetScrollableArea()->ScrollIntoView(
+ bounds,
+ ScrollAlignment::CreateScrollIntoViewParams(
+ ScrollAlignment::ToEdgeIfNeeded(), ScrollAlignment::ToEdgeIfNeeded(),
+ mojom::blink::ScrollType::kProgrammatic, false,
+ mojom::blink::ScrollBehavior::kInstant));
}
void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option,
@@ -954,6 +760,41 @@ void HTMLSelectElement::OptionSelectionStateChanged(HTMLOptionElement* option,
ResetToDefaultSelection();
}
+void HTMLSelectElement::ChildrenChanged(const ChildrenChange& change) {
+ HTMLFormControlElementWithState::ChildrenChanged(change);
+ if (change.type == ChildrenChangeType::kElementInserted) {
+ if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) {
+ OptionInserted(*option, option->Selected());
+ } else if (auto* optgroup =
+ DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) {
+ for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+ OptionInserted(option, option.Selected());
+ }
+ } else if (change.type == ChildrenChangeType::kElementRemoved) {
+ if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) {
+ OptionRemoved(*option);
+ } else if (auto* optgroup =
+ DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) {
+ for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+ OptionRemoved(option);
+ }
+ } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) {
+ DCHECK(change.removed_nodes);
+ for (Node* node : *change.removed_nodes) {
+ if (auto* option = DynamicTo<HTMLOptionElement>(node)) {
+ OptionRemoved(*option);
+ } else if (auto* optgroup = DynamicTo<HTMLOptGroupElement>(node)) {
+ for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+ OptionRemoved(option);
+ }
+ }
+ }
+}
+
+bool HTMLSelectElement::ChildrenChangedAllChildrenRemovedNeedsList() const {
+ return true;
+}
+
void HTMLSelectElement::OptionInserted(HTMLOptionElement& option,
bool option_is_selected) {
DCHECK_EQ(option.OwnerSelectElement(), this);
@@ -966,7 +807,7 @@ void HTMLSelectElement::OptionInserted(HTMLOptionElement& option,
ResetToDefaultSelection();
}
SetNeedsValidityCheck();
- last_on_change_selection_.clear();
+ select_type_->ClearLastOnChangeSelection();
if (!GetDocument().IsActive())
return;
@@ -997,7 +838,7 @@ void HTMLSelectElement::OptionRemoved(HTMLOptionElement& option) {
if (option.Selected())
SetAutofillState(WebAutofillState::kNotFilled);
SetNeedsValidityCheck();
- last_on_change_selection_.clear();
+ select_type_->ClearLastOnChangeSelection();
if (!GetDocument().IsActive())
return;
@@ -1013,12 +854,12 @@ void HTMLSelectElement::OptGroupInsertedOrRemoved(
HTMLOptGroupElement& optgroup) {
SetRecalcListItems();
SetNeedsValidityCheck();
- last_on_change_selection_.clear();
+ select_type_->ClearLastOnChangeSelection();
}
void HTMLSelectElement::HrInsertedOrRemoved(HTMLHRElement& hr) {
SetRecalcListItems();
- last_on_change_selection_.clear();
+ select_type_->ClearLastOnChangeSelection();
}
// TODO(tkent): This function is not efficient. It contains multiple O(N)
@@ -1057,40 +898,7 @@ void HTMLSelectElement::SelectOption(HTMLOptionElement* element,
SetActiveSelectionEnd(element);
}
- // Need to update last_on_change_option_ before
- // LayoutMenuList::UpdateFromElement.
- bool should_dispatch_events = false;
- if (UsesMenuList()) {
- should_dispatch_events = (flags & kDispatchInputAndChangeEventFlag) &&
- last_on_change_option_ != element;
- last_on_change_option_ = element;
- }
-
- // For the menu list case, this is what makes the selected element appear.
- if (LayoutObject* layout_object = GetLayoutObject())
- layout_object->UpdateFromElement();
- // PopupMenu::UpdateFromElement() posts an O(N) task.
- if (PopupIsVisible() && should_update_popup)
- popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
-
- ScrollToSelection();
- SetNeedsValidityCheck();
-
- if (UsesMenuList()) {
- if (should_dispatch_events) {
- DispatchInputEvent();
- DispatchChangeEvent();
- }
- if (LayoutObject* layout_object = GetLayoutObject()) {
- // Need to check UsesMenuList() again because event handlers might
- // change the status.
- if (UsesMenuList()) {
- // DidSelectOption() is O(N) because of HTMLOptionElement::index().
- ToLayoutMenuList(layout_object)->DidSelectOption(element);
- }
- }
- }
-
+ select_type_->DidSelectOption(element, flags, should_update_popup);
NotifyFormStateChanged();
if (LocalFrame::HasTransientUserActivation(GetDocument().GetFrame()) &&
@@ -1104,29 +912,22 @@ void HTMLSelectElement::SelectOption(HTMLOptionElement* element,
void HTMLSelectElement::DispatchFocusEvent(
Element* old_focused_element,
- WebFocusType type,
+ mojom::blink::FocusType type,
InputDeviceCapabilities* source_capabilities) {
// Save the selection so it can be compared to the new selection when
// dispatching change events during blur event dispatch.
if (UsesMenuList())
- SaveLastSelection();
+ select_type_->SaveLastSelection();
HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type,
source_capabilities);
}
void HTMLSelectElement::DispatchBlurEvent(
Element* new_focused_element,
- WebFocusType type,
+ mojom::blink::FocusType type,
InputDeviceCapabilities* source_capabilities) {
type_ahead_.ResetSession();
- // We only need to fire change events here for menu lists, because we fire
- // change events for list boxes whenever the selection change is actually
- // made. This matches other browsers' behavior.
- if (UsesMenuList())
- DispatchInputAndChangeEventForMenuList();
- last_on_change_selection_.clear();
- if (PopupIsVisible())
- HidePopup();
+ select_type_->DidBlur();
HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type,
source_capabilities);
}
@@ -1240,6 +1041,7 @@ void HTMLSelectElement::RestoreFormControlState(const FormControlState& state) {
}
SetNeedsValidityCheck();
+ select_type_->UpdateTextStyleAndContent();
}
void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) {
@@ -1248,6 +1050,7 @@ void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) {
is_multiple_ = !value.IsNull();
SetNeedsValidityCheck();
ChangeRendering();
+ UpdateUserAgentShadowTree(*UserAgentShadowRoot());
// Restore selectedIndex after changing the multiple flag to preserve
// selection as single-line and multi-line has different defaults.
if (old_multiple != is_multiple_) {
@@ -1259,6 +1062,7 @@ void HTMLSelectElement::ParseMultipleAttribute(const AtomicString& value) {
else
ResetToDefaultSelection();
}
+ select_type_->UpdateTextStyleAndContent();
}
void HTMLSelectElement::AppendToFormData(FormData& form_data) {
@@ -1279,228 +1083,12 @@ void HTMLSelectElement::ResetImpl() {
option->SetDirty(false);
}
ResetToDefaultSelection();
+ select_type_->UpdateTextStyleAndContent();
SetNeedsValidityCheck();
}
-void HTMLSelectElement::HandlePopupOpenKeyboardEvent(Event& event) {
- focus();
- // Calling focus() may cause us to lose our layoutObject. Return true so
- // that our caller doesn't process the event further, but don't set
- // the event as handled.
- if (!GetLayoutObject() || !GetLayoutObject()->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
- // selectOptionByPopup, which gets called after the user makes a selection
- // from the menu.
- SaveLastSelection();
- ShowPopup();
- event.SetDefaultHandled();
- return;
-}
-
-bool HTMLSelectElement::ShouldOpenPopupForKeyDownEvent(
- const KeyboardEvent& key_event) {
- const String& key = key_event.key();
- LayoutTheme& layout_theme = LayoutTheme::GetTheme();
-
- if (IsSpatialNavigationEnabled(GetDocument().GetFrame()))
- return false;
-
- return ((layout_theme.PopsMenuByArrowKeys() &&
- (key == "ArrowDown" || key == "ArrowUp")) ||
- (layout_theme.PopsMenuByAltDownUpOrF4Key() &&
- (key == "ArrowDown" || key == "ArrowUp") && key_event.altKey()) ||
- (layout_theme.PopsMenuByAltDownUpOrF4Key() &&
- (!key_event.altKey() && !key_event.ctrlKey() && key == "F4")));
-}
-
-bool HTMLSelectElement::ShouldOpenPopupForKeyPressEvent(
- const KeyboardEvent& event) {
- LayoutTheme& layout_theme = LayoutTheme::GetTheme();
- int key_code = event.keyCode();
-
- return ((layout_theme.PopsMenuBySpaceKey() && key_code == ' ' &&
- !type_ahead_.HasActiveSession(event)) ||
- (layout_theme.PopsMenuByReturnKey() && key_code == '\r'));
-}
-
-void HTMLSelectElement::MenuListDefaultEventHandler(Event& event) {
- // We need to make the layout tree up-to-date to have GetLayoutObject() give
- // the correct result below. An author event handler may have set display to
- // some element to none which will cause a layout tree detach.
- GetDocument().UpdateStyleAndLayoutTree();
-
- if (event.type() == event_type_names::kKeydown) {
- if (!GetLayoutObject() || !event.IsKeyboardEvent())
- return;
-
- auto& key_event = ToKeyboardEvent(event);
- if (ShouldOpenPopupForKeyDownEvent(key_event)) {
- 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,
- // instead of changing the selection.
- if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) {
- if (!active_selection_state_)
- return;
- }
-
- // The key handling below shouldn't be used for non spatial navigation
- // mode Mac
- if (LayoutTheme::GetTheme().PopsMenuByArrowKeys() &&
- !IsSpatialNavigationEnabled(GetDocument().GetFrame()))
- return;
-
- int ignore_modifiers = WebInputEvent::kShiftKey |
- WebInputEvent::kControlKey | WebInputEvent::kAltKey |
- WebInputEvent::kMetaKey;
- if (key_event.GetModifiers() & ignore_modifiers)
- return;
-
- const String& key = key_event.key();
- bool handled = true;
- const ListItems& list_items = GetListItems();
- HTMLOptionElement* option = SelectedOption();
- int list_index = option ? option->ListIndex() : -1;
-
- if (key == "ArrowDown" || key == "ArrowRight")
- option = NextValidOption(list_index, kSkipForwards, 1);
- else if (key == "ArrowUp" || key == "ArrowLeft")
- option = NextValidOption(list_index, kSkipBackwards, 1);
- else if (key == "PageDown")
- option = NextValidOption(list_index, kSkipForwards, 3);
- else if (key == "PageUp")
- option = NextValidOption(list_index, kSkipBackwards, 3);
- else if (key == "Home")
- option = NextValidOption(-1, kSkipForwards, 1);
- else if (key == "End")
- option = NextValidOption(list_items.size(), kSkipBackwards, 1);
- else
- handled = false;
-
- if (handled && option) {
- SelectOption(option, kDeselectOtherOptionsFlag | kMakeOptionDirtyFlag |
- kDispatchInputAndChangeEventFlag);
- }
-
- if (handled)
- event.SetDefaultHandled();
- }
-
- if (event.type() == event_type_names::kKeypress) {
- if (!GetLayoutObject() || !event.IsKeyboardEvent())
- return;
-
- int key_code = ToKeyboardEvent(event).keyCode();
- if (key_code == ' ' &&
- IsSpatialNavigationEnabled(GetDocument().GetFrame())) {
- // Use space to toggle arrow key handling for selection change or
- // spatial navigation.
- active_selection_state_ = !active_selection_state_;
- event.SetDefaultHandled();
- return;
- }
-
- auto& key_event = ToKeyboardEvent(event);
- if (ShouldOpenPopupForKeyPressEvent(key_event)) {
- HandlePopupOpenKeyboardEvent(event);
- return;
- }
-
- if (!LayoutTheme::GetTheme().PopsMenuByReturnKey() && key_code == '\r') {
- if (Form())
- Form()->SubmitImplicitly(event, false);
- DispatchInputAndChangeEventForMenuList();
- event.SetDefaultHandled();
- }
- }
-
- if (event.type() == event_type_names::kMousedown && event.IsMouseEvent() &&
- ToMouseEvent(event).button() ==
- static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
- InputDeviceCapabilities* source_capabilities =
- GetDocument()
- .domWindow()
- ->GetInputDeviceCapabilities()
- ->FiresTouchEvents(ToMouseEvent(event).FromTouch());
- focus(FocusParams(SelectionBehaviorOnFocus::kRestore, kWebFocusTypeNone,
- source_capabilities));
- if (GetLayoutObject() && GetLayoutObject()->IsMenuList() &&
- !IsDisabledFormControl()) {
- if (PopupIsVisible()) {
- HidePopup();
- } else {
- // Save the selection so it can be compared to the new selection
- // when we call onChange during selectOption, which gets called
- // from selectOptionByPopup, which gets called after the user
- // makes a selection from the menu.
- SaveLastSelection();
- // TODO(lanwei): Will check if we need to add
- // InputDeviceCapabilities here when select menu list gets
- // focus, see https://crbug.com/476530.
- ShowPopup();
- }
- }
- event.SetDefaultHandled();
- }
-}
-
-void HTMLSelectElement::UpdateSelectedState(HTMLOptionElement* clicked_option,
- bool multi,
- bool shift) {
- DCHECK(clicked_option);
- // Save the selection so it can be compared to the new selection when
- // dispatching change events during mouseup, or after autoscroll finishes.
- SaveLastSelection();
-
- active_selection_state_ = true;
-
- bool shift_select = is_multiple_ && shift;
- bool multi_select = is_multiple_ && multi && !shift;
-
- // Keep track of whether an active selection (like during drag selection),
- // should select or deselect.
- if (clicked_option->Selected() && multi_select) {
- active_selection_state_ = false;
- clicked_option->SetSelectedState(false);
- clicked_option->SetDirty(true);
- }
-
- // 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 (!shift_select && !multi_select)
- DeselectItemsWithoutValidation(clicked_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 (!active_selection_anchor_ && !multi_select)
- SetActiveSelectionAnchor(SelectedOption());
-
- // Set the selection state of the clicked option.
- if (!clicked_option->IsDisabledFormControl()) {
- clicked_option->SetSelectedState(true);
- clicked_option->SetDirty(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 (!active_selection_anchor_ || !shift_select)
- SetActiveSelectionAnchor(clicked_option);
-
- SetActiveSelectionEnd(clicked_option);
- UpdateListBoxSelection(!multi_select);
-}
-
-HTMLOptionElement* HTMLSelectElement::EventTargetOption(const Event& event) {
- return DynamicTo<HTMLOptionElement>(event.target()->ToNode());
+bool HTMLSelectElement::PopupIsVisible() const {
+ return select_type_->PopupIsVisible();
}
int HTMLSelectElement::ListIndexForOption(const HTMLOptionElement& option) {
@@ -1519,259 +1107,13 @@ AutoscrollController* HTMLSelectElement::GetAutoscrollController() const {
return nullptr;
}
-void HTMLSelectElement::HandleMouseRelease() {
- // We didn't start this click/drag on any options.
- if (last_on_change_selection_.IsEmpty())
- return;
- ListBoxOnChange();
-}
-
-void HTMLSelectElement::ListBoxDefaultEventHandler(Event& event) {
- if (event.type() == event_type_names::kGesturetap && event.IsGestureEvent()) {
- focus();
- // Calling focus() may cause us to lose our layoutObject or change the
- // layoutObject type, in which case do not want to handle the event.
- if (!GetLayoutObject() || !GetLayoutObject()->IsListBox())
- return;
-
- // Convert to coords relative to the list box if needed.
- auto& gesture_event = ToGestureEvent(event);
- if (HTMLOptionElement* option = EventTargetOption(gesture_event)) {
- if (!IsDisabledFormControl()) {
- UpdateSelectedState(option, true, gesture_event.shiftKey());
- ListBoxOnChange();
- }
- event.SetDefaultHandled();
- }
-
- } else if (event.type() == event_type_names::kMousedown &&
- event.IsMouseEvent() &&
- ToMouseEvent(event).button() ==
- static_cast<int16_t>(WebPointerProperties::Button::kLeft)) {
- focus();
- // Calling focus() may cause us to lose our layoutObject, in which case
- // do not want to handle the event.
- if (!GetLayoutObject() || !GetLayoutObject()->IsListBox() ||
- IsDisabledFormControl())
- return;
-
- // Convert to coords relative to the list box if needed.
- auto& mouse_event = ToMouseEvent(event);
- if (HTMLOptionElement* option = EventTargetOption(mouse_event)) {
- if (!option->IsDisabledFormControl()) {
-#if defined(OS_MACOSX)
- UpdateSelectedState(option, mouse_event.metaKey(),
- mouse_event.shiftKey());
-#else
- UpdateSelectedState(option, mouse_event.ctrlKey(),
- mouse_event.shiftKey());
-#endif
- }
- if (LocalFrame* frame = GetDocument().GetFrame())
- frame->GetEventHandler().SetMouseDownMayStartAutoscroll();
-
- event.SetDefaultHandled();
- }
-
- } else if (event.type() == event_type_names::kMousemove &&
- event.IsMouseEvent()) {
- auto& mouse_event = ToMouseEvent(event);
- if (mouse_event.button() !=
- static_cast<int16_t>(WebPointerProperties::Button::kLeft) ||
- !mouse_event.ButtonDown())
- return;
-
- LayoutObject* layout_object = GetLayoutObject();
- if (layout_object) {
- layout_object->GetFrameView()->UpdateAllLifecyclePhasesExceptPaint();
-
- if (Page* page = GetDocument().GetPage()) {
- page->GetAutoscrollController().StartAutoscrollForSelection(
- layout_object);
- }
- }
- // Mousedown didn't happen in this element.
- if (last_on_change_selection_.IsEmpty())
- return;
-
- if (HTMLOptionElement* option = EventTargetOption(mouse_event)) {
- if (!IsDisabledFormControl()) {
- if (is_multiple_) {
- // Only extend selection if there is something selected.
- if (!active_selection_anchor_)
- return;
-
- SetActiveSelectionEnd(option);
- UpdateListBoxSelection(false);
- } else {
- SetActiveSelectionAnchor(option);
- SetActiveSelectionEnd(option);
- UpdateListBoxSelection(true);
- }
- }
- }
-
- } else if (event.type() == event_type_names::kMouseup &&
- event.IsMouseEvent() &&
- ToMouseEvent(event).button() ==
- static_cast<int16_t>(WebPointerProperties::Button::kLeft) &&
- GetLayoutObject()) {
- if (GetDocument().GetPage() &&
- GetDocument()
- .GetPage()
- ->GetAutoscrollController()
- .AutoscrollInProgressFor(ToLayoutBox(GetLayoutObject())))
- GetDocument().GetPage()->GetAutoscrollController().StopAutoscroll();
- else
- HandleMouseRelease();
-
- } else if (event.type() == event_type_names::kKeydown) {
- if (!event.IsKeyboardEvent())
- return;
- const String& key = ToKeyboardEvent(event).key();
-
- bool handled = false;
- HTMLOptionElement* end_option = nullptr;
- if (!active_selection_end_) {
- // Initialize the end index
- if (key == "ArrowDown" || key == "PageDown") {
- HTMLOptionElement* start_option = LastSelectedOption();
- handled = true;
- if (key == "ArrowDown") {
- end_option = NextSelectableOption(start_option);
- } else {
- end_option =
- NextSelectableOptionPageAway(start_option, kSkipForwards);
- }
- } else if (key == "ArrowUp" || key == "PageUp") {
- HTMLOptionElement* start_option = SelectedOption();
- handled = true;
- if (key == "ArrowUp") {
- end_option = PreviousSelectableOption(start_option);
- } else {
- end_option =
- NextSelectableOptionPageAway(start_option, kSkipBackwards);
- }
- }
- } else {
- // Set the end index based on the current end index.
- if (key == "ArrowDown") {
- end_option = NextSelectableOption(active_selection_end_.Get());
- handled = true;
- } else if (key == "ArrowUp") {
- end_option = PreviousSelectableOption(active_selection_end_.Get());
- handled = true;
- } else if (key == "PageDown") {
- end_option = NextSelectableOptionPageAway(active_selection_end_.Get(),
- kSkipForwards);
- handled = true;
- } else if (key == "PageUp") {
- end_option = NextSelectableOptionPageAway(active_selection_end_.Get(),
- kSkipBackwards);
- handled = true;
- }
- }
- if (key == "Home") {
- end_option = FirstSelectableOption();
- handled = true;
- } else if (key == "End") {
- end_option = LastSelectableOption();
- handled = true;
- }
-
- if (IsSpatialNavigationEnabled(GetDocument().GetFrame())) {
- // Check if the selection moves to the boundary.
- if (key == "ArrowLeft" || key == "ArrowRight" ||
- ((key == "ArrowDown" || key == "ArrowUp") &&
- end_option == active_selection_end_))
- return;
- }
-
- bool is_control_key = false;
-#if defined(OS_MACOSX)
- is_control_key = ToKeyboardEvent(event).metaKey();
-#else
- is_control_key = ToKeyboardEvent(event).ctrlKey();
-#endif
-
- if (is_multiple_ && ToKeyboardEvent(event).keyCode() == ' ' &&
- is_control_key && active_selection_end_) {
- // Use ctrl+space to toggle selection change.
- ToggleSelection(*active_selection_end_);
- event.SetDefaultHandled();
- return;
- }
-
- if (end_option && handled) {
- // Save the selection so it can be compared to the new selection
- // when dispatching change events immediately after making the new
- // selection.
- SaveLastSelection();
-
- SetActiveSelectionEnd(end_option);
-
- is_in_non_contiguous_selection_ = is_multiple_ && is_control_key;
- bool select_new_item =
- !is_multiple_ || ToKeyboardEvent(event).shiftKey() ||
- (!IsSpatialNavigationEnabled(GetDocument().GetFrame()) &&
- !is_in_non_contiguous_selection_);
- if (select_new_item)
- active_selection_state_ = true;
- // If the anchor is uninitialized, or if we're going to deselect all
- // other options, then set the anchor index equal to the end index.
- bool deselect_others =
- !is_multiple_ ||
- (!ToKeyboardEvent(event).shiftKey() && select_new_item);
- if (!active_selection_anchor_ || deselect_others) {
- if (deselect_others)
- DeselectItemsWithoutValidation();
- SetActiveSelectionAnchor(active_selection_end_.Get());
- }
-
- ScrollToOption(end_option);
- if (select_new_item || is_in_non_contiguous_selection_) {
- if (select_new_item) {
- UpdateListBoxSelection(deselect_others);
- ListBoxOnChange();
- }
- UpdateMultiSelectListBoxFocus();
- } else {
- ScrollToSelection();
- }
-
- event.SetDefaultHandled();
- }
-
- } else if (event.type() == event_type_names::kKeypress) {
- if (!event.IsKeyboardEvent())
- return;
- int key_code = ToKeyboardEvent(event).keyCode();
-
- if (key_code == '\r') {
- if (Form())
- Form()->SubmitImplicitly(event, false);
- event.SetDefaultHandled();
- } else if (is_multiple_ && key_code == ' ' &&
- (IsSpatialNavigationEnabled(GetDocument().GetFrame()) ||
- is_in_non_contiguous_selection_)) {
- HTMLOptionElement* option = active_selection_end_;
- // If there's no active selection,
- // act as if "ArrowDown" had been pressed.
- if (!option)
- option = NextSelectableOption(LastSelectedOption());
- if (option) {
- // Use space to toggle selection change.
- ToggleSelection(*option);
- event.SetDefaultHandled();
- }
- }
- }
+LayoutBox* HTMLSelectElement::AutoscrollBox() {
+ return !UsesMenuList() ? GetLayoutBox() : nullptr;
}
-void HTMLSelectElement::ToggleSelection(HTMLOptionElement& option) {
- active_selection_state_ = !active_selection_state_;
- UpdateSelectedState(&option, true /*multi*/, false /*shift*/);
- ListBoxOnChange();
+void HTMLSelectElement::StopAutoscroll() {
+ if (!IsDisabledFormControl())
+ select_type_->HandleMouseRelease();
}
void HTMLSelectElement::DefaultEventHandler(Event& event) {
@@ -1788,19 +1130,17 @@ void HTMLSelectElement::DefaultEventHandler(Event& event) {
return;
}
- if (UsesMenuList())
- MenuListDefaultEventHandler(event);
- else
- ListBoxDefaultEventHandler(event);
- if (event.DefaultHandled())
+ if (select_type_->DefaultEventHandler(event)) {
+ event.SetDefaultHandled();
return;
+ }
- if (event.type() == event_type_names::kKeypress && event.IsKeyboardEvent()) {
- auto& keyboard_event = ToKeyboardEvent(event);
- if (!keyboard_event.ctrlKey() && !keyboard_event.altKey() &&
- !keyboard_event.metaKey() &&
- WTF::unicode::IsPrintableChar(keyboard_event.charCode())) {
- TypeAheadFind(keyboard_event);
+ auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
+ if (event.type() == event_type_names::kKeypress && keyboard_event) {
+ if (!keyboard_event->ctrlKey() && !keyboard_event->altKey() &&
+ !keyboard_event->metaKey() &&
+ WTF::unicode::IsPrintableChar(keyboard_event->charCode())) {
+ TypeAheadFind(*keyboard_event);
event.SetDefaultHandled();
return;
}
@@ -1843,8 +1183,7 @@ void HTMLSelectElement::TypeAheadFind(const KeyboardEvent& event) {
SelectOption(OptionAtListIndex(index), kDeselectOtherOptionsFlag |
kMakeOptionDirtyFlag |
kDispatchInputAndChangeEventFlag);
- if (!UsesMenuList())
- ListBoxOnChange();
+ select_type_->ListBoxOnChange();
}
void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) {
@@ -1870,7 +1209,7 @@ void HTMLSelectElement::SelectOptionByAccessKey(HTMLOptionElement* option) {
option->SetDirty(true);
if (UsesMenuList())
return;
- ListBoxOnChange();
+ select_type_->ListBoxOnChange();
ScrollToSelection();
}
@@ -1892,16 +1231,16 @@ void HTMLSelectElement::FinishParsingChildren() {
cache->ListboxActiveIndexChanged(this);
}
-bool HTMLSelectElement::AnonymousIndexedSetter(
+IndexedPropertySetterResult HTMLSelectElement::AnonymousIndexedSetter(
unsigned index,
HTMLOptionElement* value,
ExceptionState& exception_state) {
if (!value) { // undefined or null
remove(index);
- return true;
+ return IndexedPropertySetterResult::kIntercepted;
}
SetOption(index, value, exception_state);
- return true;
+ return IndexedPropertySetterResult::kIntercepted;
}
bool HTMLSelectElement::IsInteractiveContent() const {
@@ -1915,14 +1254,47 @@ void HTMLSelectElement::Trace(Visitor* visitor) {
visitor->Trace(active_selection_end_);
visitor->Trace(option_to_scroll_to_);
visitor->Trace(suggested_option_);
- visitor->Trace(popup_);
- visitor->Trace(popup_updater_);
+ visitor->Trace(select_type_);
HTMLFormControlElementWithState::Trace(visitor);
}
void HTMLSelectElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
+ // Even if UsesMenuList(), the <slot> is necessary to have ComputedStyles
+ // for <option>s. LayoutFlexibleBox::IsChildAllowed() rejects all of
+ // LayoutObject children except for MenuListInnerElement's.
root.AppendChild(
HTMLSlotElement::CreateUserAgentCustomAssignSlot(GetDocument()));
+ UpdateUserAgentShadowTree(root);
+ select_type_->UpdateTextStyleAndContent();
+}
+
+void HTMLSelectElement::UpdateUserAgentShadowTree(ShadowRoot& root) {
+ // Remove all children of the ShadowRoot except for <slot>.
+ Node* node = root.firstChild();
+ while (node) {
+ if (IsA<HTMLSlotElement>(node)) {
+ node = node->nextSibling();
+ } else {
+ auto* will_be_removed = node;
+ node = node->nextSibling();
+ will_be_removed->remove();
+ }
+ }
+ if (UsesMenuList()) {
+ Element* inner_element =
+ MakeGarbageCollected<MenuListInnerElement>(GetDocument());
+ inner_element->setAttribute(html_names::kAriaHiddenAttr, "true");
+ // Make sure InnerElement() always has a Text node.
+ inner_element->appendChild(Text::Create(GetDocument(), g_empty_string));
+ root.insertBefore(inner_element, root.firstChild());
+ }
+}
+
+Element& HTMLSelectElement::InnerElement() const {
+ DCHECK(UsesMenuList());
+ auto* inner_element = DynamicTo<Element>(UserAgentShadowRoot()->firstChild());
+ DCHECK(inner_element);
+ return *inner_element;
}
HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() {
@@ -1930,7 +1302,7 @@ HTMLOptionElement* HTMLSelectElement::SpatialNavigationFocusedOption() {
return nullptr;
HTMLOptionElement* focused_option = ActiveSelectionEnd();
if (!focused_option)
- focused_option = FirstSelectableOption();
+ focused_option = select_type_->FirstSelectableOption();
return focused_option;
}
@@ -1960,42 +1332,44 @@ const ComputedStyle* HTMLSelectElement::ItemComputedStyle(
}
LayoutUnit HTMLSelectElement::ClientPaddingLeft() const {
- if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
- return ToLayoutMenuList(GetLayoutObject())->ClientPaddingLeft();
- return LayoutUnit();
+ DCHECK(UsesMenuList());
+ auto* this_box = GetLayoutBox();
+ if (!this_box || !InnerElement().GetLayoutBox())
+ return LayoutUnit();
+ LayoutTheme& theme = LayoutTheme::GetTheme();
+ const ComputedStyle& style = this_box->StyleRef();
+ int inner_padding =
+ style.IsLeftToRightDirection()
+ ? theme.PopupInternalPaddingStart(style)
+ : theme.PopupInternalPaddingEnd(GetDocument().GetFrame(), style);
+ return this_box->PaddingLeft() + inner_padding;
}
LayoutUnit HTMLSelectElement::ClientPaddingRight() const {
- if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
- return ToLayoutMenuList(GetLayoutObject())->ClientPaddingRight();
- return LayoutUnit();
+ DCHECK(UsesMenuList());
+ auto* this_box = GetLayoutBox();
+ if (!this_box || !InnerElement().GetLayoutBox())
+ return LayoutUnit();
+ LayoutTheme& theme = LayoutTheme::GetTheme();
+ const ComputedStyle& style = this_box->StyleRef();
+ int inner_padding =
+ style.IsLeftToRightDirection()
+ ? theme.PopupInternalPaddingEnd(GetDocument().GetFrame(), style)
+ : theme.PopupInternalPaddingStart(style);
+ return this_box->PaddingRight() + inner_padding;
}
void HTMLSelectElement::PopupDidHide() {
- popup_is_visible_ = false;
- UnobserveTreeMutation();
- if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) {
- if (GetLayoutObject() && GetLayoutObject()->IsMenuList())
- cache->DidHideMenuListPopup(ToLayoutMenuList(GetLayoutObject()));
- }
+ select_type_->PopupDidHide();
}
void HTMLSelectElement::SetIndexToSelectOnCancel(int list_index) {
index_to_select_on_cancel_ = list_index;
- if (GetLayoutObject())
- GetLayoutObject()->UpdateFromElement();
+ select_type_->UpdateTextStyleAndContent();
}
-HTMLOptionElement* HTMLSelectElement::OptionToBeShown() const {
- if (HTMLOptionElement* option = OptionAtListIndex(index_to_select_on_cancel_))
- return option;
- if (suggested_option_)
- return suggested_option_;
- // TODO(tkent): We should not call optionToBeShown() in isMultiple() case.
- if (IsMultiple())
- return SelectedOption();
- DCHECK_EQ(SelectedOption(), last_on_change_option_);
- return last_on_change_option_;
+HTMLOptionElement* HTMLSelectElement::OptionToBeShownForTesting() const {
+ return select_type_->OptionToBeShown();
}
void HTMLSelectElement::SelectOptionByPopup(int list_index) {
@@ -2031,44 +1405,28 @@ void HTMLSelectElement::ProvisionalSelectionChanged(unsigned list_index) {
}
void HTMLSelectElement::ShowPopup() {
- if (PopupIsVisible())
- return;
- if (GetDocument().GetPage()->GetChromeClient().HasOpenedPopup())
- return;
- if (!GetLayoutObject() || !GetLayoutObject()->IsMenuList())
- return;
- if (VisibleBoundsInVisualViewport().IsEmpty())
- return;
-
- if (!popup_) {
- popup_ = GetDocument().GetPage()->GetChromeClient().OpenPopupMenu(
- *GetDocument().GetFrame(), *this);
- }
- if (!popup_)
- return;
-
- popup_is_visible_ = true;
- ObserveTreeMutation();
-
- LayoutMenuList* menu_list = ToLayoutMenuList(GetLayoutObject());
- popup_->Show();
- if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
- cache->DidShowMenuListPopup(menu_list);
+ select_type_->ShowPopup();
}
void HTMLSelectElement::HidePopup() {
- if (popup_)
- popup_->Hide();
+ select_type_->HidePopup();
+}
+
+PopupMenu* HTMLSelectElement::PopupForTesting() const {
+ return select_type_->PopupForTesting();
}
void HTMLSelectElement::DidRecalcStyle(const StyleRecalcChange change) {
HTMLFormControlElementWithState::DidRecalcStyle(change);
- if (!change.ReattachLayoutTree() && PopupIsVisible())
- popup_->UpdateFromElement(PopupMenu::kByStyleChange);
+ select_type_->DidRecalcStyle(change);
}
void HTMLSelectElement::AttachLayoutTree(AttachContext& context) {
HTMLFormControlElementWithState::AttachLayoutTree(context);
+ // The call to UpdateTextStyle() needs to go after the call through
+ // to the base class's AttachLayoutTree() because that can sometimes do a
+ // close on the LayoutObject.
+ select_type_->UpdateTextStyle();
if (const ComputedStyle* style = GetComputedStyle()) {
if (style->Visibility() != EVisibility::kHidden) {
@@ -2082,90 +1440,13 @@ void HTMLSelectElement::AttachLayoutTree(AttachContext& context) {
void HTMLSelectElement::DetachLayoutTree(bool performing_reattach) {
HTMLFormControlElementWithState::DetachLayoutTree(performing_reattach);
- if (popup_)
- popup_->DisconnectClient();
- popup_is_visible_ = false;
- popup_ = nullptr;
- UnobserveTreeMutation();
+ select_type_->DidDetachLayoutTree();
}
void HTMLSelectElement::ResetTypeAheadSessionForTesting() {
type_ahead_.ResetSession();
}
-// PopupUpdater notifies updates of the specified SELECT element subtree to
-// a PopupMenu object.
-class HTMLSelectElement::PopupUpdater : public MutationObserver::Delegate {
- public:
- explicit PopupUpdater(HTMLSelectElement& select)
- : select_(select), observer_(MutationObserver::Create(this)) {
- MutationObserverInit* init = MutationObserverInit::Create();
- init->setAttributeOldValue(true);
- init->setAttributes(true);
- // Observe only attributes which affect popup content.
- init->setAttributeFilter({"disabled", "label", "selected", "value"});
- init->setCharacterData(true);
- init->setCharacterDataOldValue(true);
- init->setChildList(true);
- init->setSubtree(true);
- observer_->observe(select_, init, ASSERT_NO_EXCEPTION);
- }
-
- ExecutionContext* GetExecutionContext() const override {
- return &select_->GetDocument();
- }
-
- void Deliver(const MutationRecordVector& records,
- MutationObserver&) override {
- // We disconnect the MutationObserver when a popup is closed. However
- // MutationObserver can call back after disconnection.
- if (!select_->PopupIsVisible())
- return;
- for (const auto& record : records) {
- if (record->type() == "attributes") {
- const auto& element = *To<Element>(record->target());
- if (record->oldValue() == element.getAttribute(record->attributeName()))
- continue;
- } else if (record->type() == "characterData") {
- if (record->oldValue() == record->target()->nodeValue())
- continue;
- }
- select_->DidMutateSubtree();
- return;
- }
- }
-
- void Dispose() { observer_->disconnect(); }
-
- void Trace(Visitor* visitor) override {
- visitor->Trace(select_);
- visitor->Trace(observer_);
- MutationObserver::Delegate::Trace(visitor);
- }
-
- private:
- Member<HTMLSelectElement> select_;
- Member<MutationObserver> observer_;
-};
-
-void HTMLSelectElement::ObserveTreeMutation() {
- DCHECK(!popup_updater_);
- popup_updater_ = MakeGarbageCollected<PopupUpdater>(*this);
-}
-
-void HTMLSelectElement::UnobserveTreeMutation() {
- if (!popup_updater_)
- return;
- popup_updater_->Dispose();
- popup_updater_ = nullptr;
-}
-
-void HTMLSelectElement::DidMutateSubtree() {
- DCHECK(PopupIsVisible());
- DCHECK(popup_);
- popup_->UpdateFromElement(PopupMenu::kByDOMChange);
-}
-
void HTMLSelectElement::CloneNonAttributePropertiesFrom(
const Element& source,
CloneChildrenFlag flag) {
@@ -2175,6 +1456,10 @@ void HTMLSelectElement::CloneNonAttributePropertiesFrom(
}
void HTMLSelectElement::ChangeRendering() {
+ select_type_->DidDetachLayoutTree();
+ UpdateUsesMenuList();
+ select_type_->WillBeDestroyed();
+ select_type_ = SelectType::Create(*this);
if (!InActiveDocument())
return;
// TODO(futhark): SetForceReattachLayoutTree() should be the correct way to
@@ -2185,4 +1470,8 @@ void HTMLSelectElement::ChangeRendering() {
style_change_reason::kControl));
}
+const ComputedStyle* HTMLSelectElement::OptionStyle() const {
+ return select_type_->OptionStyle();
+}
+
} // namespace blink