0

Remove display:block!important from customizable_select.css

1. Call UpdateStyleAndLayoutTree from IsAppearanceBaseButton and
   IsAppearanceBasePicker, unless no_update is set for cases which are
   calling from within a style/layout recalc.
2. Call EnsureComputedStyle when checking IsAppearanceBasePicker in
   order to get an appearance value despite the element being
   display:none due to it being a closed popover.

Since the picker might no longer have a computed style all the time,
this requires rewriting some accessibility code which was depending on
always having a computed style for the picker. I rewrote the
accessibility code to be more consistent across appearance:base-select
mode and appearance:auto mode by consistently using the UA popover
element as the MenuListPopup mapped element.

Bug: 364348901
Fixed: 364924715
Change-Id: I3fec57f2e02ce4200c95c256e5c1072b5c21ef8e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5953628
Reviewed-by: David Baron <dbaron@chromium.org>
Reviewed-by: Philip Rogers <pdr@chromium.org>
Commit-Queue: David Baron <dbaron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1399296}
This commit is contained in:
Joey Arhar
2024-12-20 11:31:50 -08:00
committed by Chromium LUCI CQ
parent 9540f36858
commit 2313906f2c
22 changed files with 338 additions and 115 deletions

@@ -198,6 +198,45 @@ class SummaryAsHeadingDumpAccessibilityTreeTest
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
}; };
class CustomizableSelectEnabledDumpAccessibilityTreeTest
: public DumpAccessibilityTreeTest {
protected:
CustomizableSelectEnabledDumpAccessibilityTreeTest() {
feature_list_.InitWithFeatures({{blink::features::kCustomizableSelect,
blink::features::kSelectParserRelaxation}},
{/* disabled_features */});
}
~CustomizableSelectEnabledDumpAccessibilityTreeTest() override {
// Ensure that the feature lists are destroyed in the same order they
// were created in.
scoped_feature_list_.Reset();
feature_list_.Reset();
}
private:
base::test::ScopedFeatureList feature_list_;
};
class CustomizableSelectDisabledDumpAccessibilityTreeTest
: public DumpAccessibilityTreeTest {
protected:
CustomizableSelectDisabledDumpAccessibilityTreeTest() {
feature_list_.InitWithFeatures({/* enabled_features */},
{{blink::features::kCustomizableSelect}});
}
~CustomizableSelectDisabledDumpAccessibilityTreeTest() override {
// Ensure that the feature lists are destroyed in the same order they
// were created in.
scoped_feature_list_.Reset();
feature_list_.Reset();
}
private:
base::test::ScopedFeatureList feature_list_;
};
// TODO(crbug.com/40925629): We need to create a way to incrementally // TODO(crbug.com/40925629): We need to create a way to incrementally
// enable and create UIA tests. // enable and create UIA tests.
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
@@ -224,6 +263,18 @@ INSTANTIATE_TEST_SUITE_P(
::testing::ValuesIn(DumpAccessibilityTestBase::TreeTestPasses()), ::testing::ValuesIn(DumpAccessibilityTestBase::TreeTestPasses()),
DumpAccessibilityTreeTestPassToString()); DumpAccessibilityTreeTestPassToString());
INSTANTIATE_TEST_SUITE_P(
All,
CustomizableSelectEnabledDumpAccessibilityTreeTest,
::testing::ValuesIn(DumpAccessibilityTestBase::TreeTestPasses()),
DumpAccessibilityTreeTestPassToString());
INSTANTIATE_TEST_SUITE_P(
All,
CustomizableSelectDisabledDumpAccessibilityTreeTest,
::testing::ValuesIn(DumpAccessibilityTestBase::TreeTestPasses()),
DumpAccessibilityTreeTestPassToString());
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSAltText) { IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSAltText) {
RunCSSTest(FILE_PATH_LITERAL("alt-text.html")); RunCSSTest(FILE_PATH_LITERAL("alt-text.html"));
} }
@@ -2156,6 +2207,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
RunHtmlTest(FILE_PATH_LITERAL("custom-select-open.html")); RunHtmlTest(FILE_PATH_LITERAL("custom-select-open.html"));
} }
IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
AccessibilityCustomSelectMixedOptions) {
RunHtmlTest(FILE_PATH_LITERAL("custom-select-mixed-options.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityDd) { IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityDd) {
RunHtmlTest(FILE_PATH_LITERAL("dd.html")); RunHtmlTest(FILE_PATH_LITERAL("dd.html"));
} }
@@ -3516,7 +3572,18 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
RunHtmlTest(FILE_PATH_LITERAL("selection-container.html")); RunHtmlTest(FILE_PATH_LITERAL("selection-container.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilitySelect) { // Times out on android: https://issues.chromium.org/issues/384959329
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_AccessibilitySelect DISABLED_AccessibiltySelect
#else
#define MAYBE_AccessibilitySelect ENABLED_AccessibiltySelect
#endif
IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
MAYBE_AccessibilitySelect) {
RunHtmlTest(FILE_PATH_LITERAL("select.html"));
}
IN_PROC_BROWSER_TEST_P(CustomizableSelectDisabledDumpAccessibilityTreeTest,
AccessibilitySelect) {
RunHtmlTest(FILE_PATH_LITERAL("select.html")); RunHtmlTest(FILE_PATH_LITERAL("select.html"));
} }
@@ -3526,7 +3593,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilitySelect) {
#else #else
#define MAYBE_AccessibilitySelectOpen AccessibilitySelectOpen #define MAYBE_AccessibilitySelectOpen AccessibilitySelectOpen
#endif #endif
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
MAYBE_AccessibilitySelectOpen) {
RunHtmlTest(FILE_PATH_LITERAL("select-open.html"));
}
IN_PROC_BROWSER_TEST_P(CustomizableSelectDisabledDumpAccessibilityTreeTest,
MAYBE_AccessibilitySelectOpen) { MAYBE_AccessibilitySelectOpen) {
RunHtmlTest(FILE_PATH_LITERAL("select-open.html")); RunHtmlTest(FILE_PATH_LITERAL("select-open.html"));
} }
@@ -3540,18 +3611,31 @@ IN_PROC_BROWSER_TEST_P(YieldingParserDumpAccessibilityTreeTest,
RunHtmlTest(FILE_PATH_LITERAL("select-in-canvas.html")); RunHtmlTest(FILE_PATH_LITERAL("select-in-canvas.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocus) {
RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus.html"));
}
IN_PROC_BROWSER_TEST_P(CustomizableSelectDisabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocus) { AccessibilitySelectFollowsFocus) {
RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus.html")); RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocusAriaSelectedFalse) {
RunHtmlTest(
FILE_PATH_LITERAL("select-follows-focus-aria-selected-false.html"));
}
IN_PROC_BROWSER_TEST_P(CustomizableSelectDisabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocusAriaSelectedFalse) { AccessibilitySelectFollowsFocusAriaSelectedFalse) {
RunHtmlTest( RunHtmlTest(
FILE_PATH_LITERAL("select-follows-focus-aria-selected-false.html")); FILE_PATH_LITERAL("select-follows-focus-aria-selected-false.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_P(CustomizableSelectEnabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocusMultiselect) {
RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus-multiselect.html"));
}
IN_PROC_BROWSER_TEST_P(CustomizableSelectDisabledDumpAccessibilityTreeTest,
AccessibilitySelectFollowsFocusMultiselect) { AccessibilitySelectFollowsFocusMultiselect) {
RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus-multiselect.html")); RunHtmlTest(FILE_PATH_LITERAL("select-follows-focus-multiselect.html"));
} }

@@ -3344,6 +3344,9 @@ data/accessibility/html/custom-select-expected-blink.txt
data/accessibility/html/custom-select-expected-mac.txt data/accessibility/html/custom-select-expected-mac.txt
data/accessibility/html/custom-select-expected-uia-win.txt data/accessibility/html/custom-select-expected-uia-win.txt
data/accessibility/html/custom-select-expected-win.txt data/accessibility/html/custom-select-expected-win.txt
data/accessibility/html/custom-select-mixed-options-expected-auralinux.txt
data/accessibility/html/custom-select-mixed-options-expected-blink.txt
data/accessibility/html/custom-select-mixed-options.html
data/accessibility/html/custom-select-open-expected-android-assist-data.txt data/accessibility/html/custom-select-open-expected-android-assist-data.txt
data/accessibility/html/custom-select-open-expected-android-external.txt data/accessibility/html/custom-select-open-expected-android-external.txt
data/accessibility/html/custom-select-open-expected-android.txt data/accessibility/html/custom-select-open-expected-android.txt

@@ -0,0 +1,18 @@
[document web]
++[section]
++++[combo box]
++++++[menu]
++++++++[menu item] name='only text' selectable selected
++++++++[menu item] name='with img ' selectable
++++[combo box]
++++++[menu]
++++++++[menu item] name='only text' selectable selected
++++++++[menu item] name='with img ' selectable
++++++++++[static] name='with img<newline> '
++++++++++[image]
++++[combo box]
++++++[menu]
++++++++[menu item] name='only text' selectable selected
++++++++[menu item] name='with img ' selectable
++++++++++[static] name='with img<newline> '
++++++++++[image]

@@ -0,0 +1,21 @@
rootWebArea
++genericContainer ignored
++++genericContainer
++++++comboBoxSelect collapsed value='only text'
++++++++menuListPopup invisible ispopup=auto
++++++++++menuListOption name='only text' selected=true
++++++++++menuListOption invisible name='with img ' selected=false
++++++comboBoxSelect collapsed value='only text'
++++++++menuListPopup invisible ispopup=auto
++++++++++menuListOption name='only text' selected=true
++++++++++menuListOption invisible name='with img ' selected=false
++++++++++++genericContainer ignored invisible
++++++++++++++staticText invisible name='with img<newline> '
++++++++++++++image invisible
++++++comboBoxSelect collapsed value='only text'
++++++++menuListPopup invisible ispopup=auto
++++++++++menuListOption name='only text' selected=true
++++++++++menuListOption invisible name='with img ' selected=false
++++++++++++genericContainer ignored invisible
++++++++++++++staticText invisible name='with img<newline> '
++++++++++++++image invisible

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<style>
.custombutton {
appearance: base-select;
}
.custompicker::picker(select) {
appearance: base-select;
}
</style>
<select>
<option>only text</option>
<option>with img
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAQklEQVQ4T2P8z8AARIQBI1GqGBhA6ohSOmogzkAfDUPc6XHgkg0wXROVsIF5gHB2AqoAqho1EHceGA1D7CEw+JMNALGaMffRFnRtAAAAAElFTkSuQmCC">
</option>
</select>
<select class=custombutton>
<option>only text</option>
<option>with img
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAQklEQVQ4T2P8z8AARIQBI1GqGBhA6ohSOmogzkAfDUPc6XHgkg0wXROVsIF5gHB2AqoAqho1EHceGA1D7CEw+JMNALGaMffRFnRtAAAAAElFTkSuQmCC">
</option>
</select>
<select class="custombutton custompicker">
<option>only text</option>
<option>with img
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAQklEQVQ4T2P8z8AARIQBI1GqGBhA6ohSOmogzkAfDUPc6XHgkg0wXROVsIF5gHB2AqoAqho1EHceGA1D7CEw+JMNALGaMffRFnRtAAAAAElFTkSuQmCC">
</option>
</select>

@@ -18,6 +18,7 @@ namespace blink {
enum class DocumentUpdateReason { enum class DocumentUpdateReason {
kAccessibility, kAccessibility,
kBaseColor, kBaseColor,
kBaseSelect,
kBeginMainFrame, kBeginMainFrame,
kCanvas, kCanvas,
kCanvasPlaceElement, kCanvasPlaceElement,

@@ -685,6 +685,7 @@ void LocalFrameUkmAggregator::EndForcedLayout(
case DocumentUpdateReason::kAccessibility: case DocumentUpdateReason::kAccessibility:
case DocumentUpdateReason::kBaseColor: case DocumentUpdateReason::kBaseColor:
case DocumentUpdateReason::kBaseSelect:
case DocumentUpdateReason::kComputedStyle: case DocumentUpdateReason::kComputedStyle:
case DocumentUpdateReason::kDisplayLock: case DocumentUpdateReason::kDisplayLock:
case DocumentUpdateReason::kViewTransition: case DocumentUpdateReason::kViewTransition:

@@ -130,7 +130,8 @@ FocusableState HTMLOptionElement::SupportsFocus(
UpdateBehavior update_behavior) const { UpdateBehavior update_behavior) const {
HTMLSelectElement* select = OwnerSelectElement(); HTMLSelectElement* select = OwnerSelectElement();
if (select && select->UsesMenuList()) { if (select && select->UsesMenuList()) {
if (select->IsAppearanceBasePicker()) { auto* popover = select->PopoverForAppearanceBase();
if (popover && popover->popoverOpen()) {
// If this option is being rendered as regular web content inside a // If this option is being rendered as regular web content inside a
// base-select <select> popover, then we need this element to be // base-select <select> popover, then we need this element to be
// focusable. // focusable.

@@ -106,7 +106,8 @@ class SelectDescendantsObserver : public MutationObserver::Delegate {
explicit SelectDescendantsObserver(HTMLSelectElement& select) explicit SelectDescendantsObserver(HTMLSelectElement& select)
: select_(select), observer_(MutationObserver::Create(this)) { : select_(select), observer_(MutationObserver::Create(this)) {
CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled()); CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled());
DCHECK(select_->IsAppearanceBasePicker()); DCHECK(select_->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle));
MutationObserverInit* init = MutationObserverInit::Create(); MutationObserverInit* init = MutationObserverInit::Create();
init->setChildList(true); init->setChildList(true);
@@ -1410,7 +1411,8 @@ void HTMLSelectElement::UpdateMutationObserver() {
if (!RuntimeEnabledFeatures::CustomizableSelectEnabled()) { if (!RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
return; return;
} }
if (UsesMenuList() && isConnected() && IsAppearanceBasePicker()) { if (UsesMenuList() && isConnected() &&
IsAppearanceBaseButton(StyleUpdateBehavior::kDontUpdateStyle)) {
if (!descendants_observer_) { if (!descendants_observer_) {
descendants_observer_ = descendants_observer_ =
MakeGarbageCollected<SelectDescendantsObserver>(*this); MakeGarbageCollected<SelectDescendantsObserver>(*this);
@@ -1962,6 +1964,14 @@ HTMLElement* HTMLSelectElement::PopoverForAppearanceBase() const {
return select_type_->PopoverForAppearanceBase(); return select_type_->PopoverForAppearanceBase();
} }
// static
bool HTMLSelectElement::IsPopoverForAppearanceBase(const Node* node) {
if (auto* element = DynamicTo<Element>(node)) {
return IsPopoverForAppearanceBase(element);
}
return false;
}
// static // static
bool HTMLSelectElement::IsPopoverForAppearanceBase(const Element* element) { bool HTMLSelectElement::IsPopoverForAppearanceBase(const Element* element) {
if (auto* root = DynamicTo<ShadowRoot>(element->parentNode())) { if (auto* root = DynamicTo<ShadowRoot>(element->parentNode())) {
@@ -1971,8 +1981,9 @@ bool HTMLSelectElement::IsPopoverForAppearanceBase(const Element* element) {
return false; return false;
} }
bool HTMLSelectElement::IsAppearanceBaseButton() const { bool HTMLSelectElement::IsAppearanceBaseButton(
return select_type_->IsAppearanceBaseButton(); StyleUpdateBehavior update_behavior) const {
return select_type_->IsAppearanceBaseButton(update_behavior);
} }
bool HTMLSelectElement::IsAppearanceBasePicker() const { bool HTMLSelectElement::IsAppearanceBasePicker() const {
@@ -2099,8 +2110,10 @@ void HTMLSelectElement::UpdateAllSelectedcontents() {
VectorOf<HTMLSelectedContentElement>(descendant_selectedcontents_)) { VectorOf<HTMLSelectedContentElement>(descendant_selectedcontents_)) {
selectedcontent->CloneContentsFromOptionElement(option); selectedcontent->CloneContentsFromOptionElement(option);
} }
if (auto* attr_selectedcontent = selectedContentElement()) { if (RuntimeEnabledFeatures::SelectedcontentelementAttributeEnabled()) {
attr_selectedcontent->CloneContentsFromOptionElement(option); if (auto* attr_selectedcontent = selectedContentElement()) {
attr_selectedcontent->CloneContentsFromOptionElement(option);
}
} }
} }

@@ -245,6 +245,7 @@ class CORE_EXPORT HTMLSelectElement final
// Returns true if the provided element is some select element's // Returns true if the provided element is some select element's
// PopoverForAppearanceBase. // PopoverForAppearanceBase.
static bool IsPopoverForAppearanceBase(const Node*);
static bool IsPopoverForAppearanceBase(const Element*); static bool IsPopoverForAppearanceBase(const Element*);
// <select> supports appearance:base-select on both the main element and // <select> supports appearance:base-select on both the main element and
@@ -255,7 +256,14 @@ class CORE_EXPORT HTMLSelectElement final
// has appearance:base-select, IsAppearanceBasePicker will return true and the // has appearance:base-select, IsAppearanceBasePicker will return true and the
// popup will be a popover element. The SelectType must also support base // popup will be a popover element. The SelectType must also support base
// appearance, which is currently only MenuListSelectType. // appearance, which is currently only MenuListSelectType.
bool IsAppearanceBaseButton() const; // IsAppearanceBaseButton should be used for code which is concerned with the
// in-page rendering of the button, and IsAppearanceBasePicker should be used
// for code which is concerned with the popup/popover and the other elements
// which are rendered in it.
// |no_update| prevents these methods from running an UpdateStyleAndLayoutTree
// which is needed in some cases to prevent nested style/layout recalc.
enum class StyleUpdateBehavior { kUpdateStyle, kDontUpdateStyle };
bool IsAppearanceBaseButton(StyleUpdateBehavior) const;
bool IsAppearanceBasePicker() const; bool IsAppearanceBasePicker() const;
void SelectedContentElementInserted( void SelectedContentElementInserted(

@@ -238,7 +238,8 @@ class MenuListSelectType final : public SelectType {
void ManuallyAssignSlots() override; void ManuallyAssignSlots() override;
HTMLButtonElement* SlottedButton() const override; HTMLButtonElement* SlottedButton() const override;
HTMLElement* PopoverForAppearanceBase() const override; HTMLElement* PopoverForAppearanceBase() const override;
bool IsAppearanceBaseButton() const override; bool IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior) const override;
bool IsAppearanceBasePicker() const override; bool IsAppearanceBasePicker() const override;
HTMLSelectElement::SelectAutofillPreviewElement* GetAutofillPreviewElement() HTMLSelectElement::SelectAutofillPreviewElement* GetAutofillPreviewElement()
const override; const override;
@@ -409,7 +410,9 @@ bool MenuListSelectType::DefaultEventHandler(const Event& event) {
// TODO(crbug.com/1511354): Reconsider making appearance:base-select affect // TODO(crbug.com/1511354): Reconsider making appearance:base-select affect
// keyboard behavior after a resolution here: // keyboard behavior after a resolution here:
// https://github.com/openui/open-ui/issues/1087 // https://github.com/openui/open-ui/issues/1087
if (IsAppearanceBaseButton() && key_code == '\r') { if (IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle) &&
key_code == '\r') {
// TODO(crbug.com/1511354): Consider making form->SubmitImplicitly work // TODO(crbug.com/1511354): Consider making form->SubmitImplicitly work
// here instead of PrepareForSubmission and combine with the subsequent // here instead of PrepareForSubmission and combine with the subsequent
// code. // code.
@@ -484,7 +487,8 @@ bool MenuListSelectType::ShouldOpenPopupForKeyDownEvent(
// TODO(crbug.com/1511354): Reconsider making appearance:base-select affect // TODO(crbug.com/1511354): Reconsider making appearance:base-select affect
// keyboard behavior after a resolution here: // keyboard behavior after a resolution here:
// https://github.com/openui/open-ui/issues/1087 // https://github.com/openui/open-ui/issues/1087
if (IsAppearanceBaseButton() && if (IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle) &&
(key == keywords::kArrowDown || key == keywords::kArrowUp || (key == keywords::kArrowDown || key == keywords::kArrowUp ||
key == keywords::kArrowLeft || key == keywords::kArrowRight)) { key == keywords::kArrowLeft || key == keywords::kArrowRight)) {
return true; return true;
@@ -505,7 +509,9 @@ bool MenuListSelectType::ShouldOpenPopupForKeyPressEvent(
// TODO(crbug.com/1511354): Reconsider making appearance:base-select affect // TODO(crbug.com/1511354): Reconsider making appearance:base-select affect
// keyboard behavior after a resolution here: // keyboard behavior after a resolution here:
// https://github.com/openui/open-ui/issues/1087 // https://github.com/openui/open-ui/issues/1087
if (IsAppearanceBaseButton() && key_code == '\r') { if (IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle) &&
key_code == '\r') {
return false; return false;
} }
@@ -648,22 +654,20 @@ HTMLButtonElement* MenuListSelectType::SlottedButton() const {
} }
HTMLElement* MenuListSelectType::PopoverForAppearanceBase() const { HTMLElement* MenuListSelectType::PopoverForAppearanceBase() const {
// LayoutFlexibleBox::IsChildAllowed needs to access popover_ even when the CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled() || !popover_);
// author doesn't put appearance:base-select on ::picker(select). In order to
// return popover_ in this case, we check IsAppearanceBaseButton instead of
// IsAppearanceBaseSelect.
if (!IsAppearanceBaseButton()) {
return nullptr;
}
return popover_; return popover_;
} }
bool MenuListSelectType::IsAppearanceBaseButton() const { bool MenuListSelectType::IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior update_behavior) const {
if (!RuntimeEnabledFeatures::CustomizableSelectEnabled()) { if (!RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
return false; return false;
} }
// TODO(crbug.com/364348901): Update style and layout here.
DCHECK(select_); DCHECK(select_);
if (update_behavior == HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle) {
select_->GetDocument().UpdateStyleAndLayoutForNode(
select_, DocumentUpdateReason::kBaseSelect);
}
if (auto* style = select_->GetComputedStyle()) { if (auto* style = select_->GetComputedStyle()) {
return style->EffectiveAppearance() == AppearanceValue::kBaseSelect; return style->EffectiveAppearance() == AppearanceValue::kBaseSelect;
} }
@@ -671,17 +675,15 @@ bool MenuListSelectType::IsAppearanceBaseButton() const {
} }
bool MenuListSelectType::IsAppearanceBasePicker() const { bool MenuListSelectType::IsAppearanceBasePicker() const {
if (!IsAppearanceBaseButton()) { if (!IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle)) {
// The author is required to put appearance:base-select on the <select> // The author is required to put appearance:base-select on the <select>
// before the ::picker is allowed to have appearance:base-select. // before the ::picker is allowed to have appearance:base-select.
return false; return false;
} }
CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled()); CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled());
// TODO(crbug.com/364348901): Consider using EnsureComputedStyle() here to get
// more reliable results, though it has the risk of causing more style
// computation, sometimes at bad times.
DCHECK(popover_); DCHECK(popover_);
if (auto* style = popover_->GetComputedStyle()) { if (auto* style = popover_->EnsureComputedStyle()) {
return style->EffectiveAppearance() == AppearanceValue::kBaseSelect; return style->EffectiveAppearance() == AppearanceValue::kBaseSelect;
} }
return false; return false;
@@ -780,11 +782,16 @@ void MenuListSelectType::PopupDidHide() {
} }
bool MenuListSelectType::PopupIsVisible() const { bool MenuListSelectType::PopupIsVisible() const {
if (IsAppearanceBasePicker()) { if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
return popover_->popoverOpen(); // This is called during style recalc, so we can't check
} else { // IsAppearanceBasePicker to determine which picker to look at.
return native_popup_is_visible_; bool popover_open = popover_->popoverOpen();
CHECK(!(popover_open && native_popup_is_visible_))
<< " The base appearance and native pickers should never both be open "
"at the same time.";
return popover_open || native_popup_is_visible_;
} }
return native_popup_is_visible_;
} }
void MenuListSelectType::SetNativePopupIsVisible(bool popup_is_visible) { void MenuListSelectType::SetNativePopupIsVisible(bool popup_is_visible) {
@@ -878,7 +885,8 @@ void MenuListSelectType::DidSetSuggestedOption(HTMLOptionElement* option) {
if (native_popup_is_visible_) { if (native_popup_is_visible_) {
popup_->UpdateFromElement(PopupMenu::kBySelectionChange); popup_->UpdateFromElement(PopupMenu::kBySelectionChange);
} }
if (IsAppearanceBaseButton()) { if (IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kUpdateStyle)) {
if (option) { if (option) {
autofill_popover_->ShowPopoverInternal(select_, &ASSERT_NO_EXCEPTION); autofill_popover_->ShowPopoverInternal(select_, &ASSERT_NO_EXCEPTION);
autofill_popover_text_->setInnerText(option->label()); autofill_popover_text_->setInnerText(option->label());
@@ -973,7 +981,8 @@ String MenuListSelectType::UpdateTextStyleInternal() {
// TODO(crbug.com/1511354): Ensure that this runs after switching appearance // TODO(crbug.com/1511354): Ensure that this runs after switching appearance
// modes or consider splitting InnerElement into two elements, one for // modes or consider splitting InnerElement into two elements, one for
// appearance:base-select and one for appearance:auto. // appearance:base-select and one for appearance:auto.
if (!IsAppearanceBaseButton()) { if (!IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle)) {
Element& inner_element = select_->InnerElement(); Element& inner_element = select_->InnerElement();
const ComputedStyle* inner_style = inner_element.GetComputedStyle(); const ComputedStyle* inner_style = inner_element.GetComputedStyle();
if (inner_style && option_style && if (inner_style && option_style &&
@@ -1040,7 +1049,9 @@ HTMLOptionElement* MenuListSelectType::OptionToBeShown() const {
return option; return option;
// In appearance:base-select mode, we don't want to reveal the suggested // In appearance:base-select mode, we don't want to reveal the suggested
// option anywhere except in autofill_popover_. // option anywhere except in autofill_popover_.
if (select_->suggested_option_ && !IsAppearanceBaseButton()) { if (select_->suggested_option_ &&
!IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle)) {
return select_->suggested_option_.Get(); return select_->suggested_option_.Get();
} }
// TODO(tkent): We should not call OptionToBeShown() in IsMultiple() case. // TODO(tkent): We should not call OptionToBeShown() in IsMultiple() case.
@@ -1164,7 +1175,8 @@ class ListBoxSelectType final : public SelectType {
void ManuallyAssignSlots() override; void ManuallyAssignSlots() override;
HTMLButtonElement* SlottedButton() const override; HTMLButtonElement* SlottedButton() const override;
HTMLElement* PopoverForAppearanceBase() const override; HTMLElement* PopoverForAppearanceBase() const override;
bool IsAppearanceBaseButton() const override; bool IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior) const override;
bool IsAppearanceBasePicker() const override; bool IsAppearanceBasePicker() const override;
HTMLSelectElement::SelectAutofillPreviewElement* GetAutofillPreviewElement() HTMLSelectElement::SelectAutofillPreviewElement* GetAutofillPreviewElement()
const override; const override;
@@ -1837,7 +1849,8 @@ HTMLElement* ListBoxSelectType::PopoverForAppearanceBase() const {
return nullptr; return nullptr;
} }
bool ListBoxSelectType::IsAppearanceBaseButton() const { bool ListBoxSelectType::IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior) const {
return false; return false;
} }

@@ -65,7 +65,8 @@ class SelectType : public GarbageCollected<SelectType> {
virtual void ManuallyAssignSlots() = 0; virtual void ManuallyAssignSlots() = 0;
virtual HTMLButtonElement* SlottedButton() const = 0; virtual HTMLButtonElement* SlottedButton() const = 0;
virtual HTMLElement* PopoverForAppearanceBase() const = 0; virtual HTMLElement* PopoverForAppearanceBase() const = 0;
virtual bool IsAppearanceBaseButton() const = 0; virtual bool IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior) const = 0;
virtual bool IsAppearanceBasePicker() const = 0; virtual bool IsAppearanceBasePicker() const = 0;
virtual HTMLSelectElement::SelectAutofillPreviewElement* virtual HTMLSelectElement::SelectAutofillPreviewElement*
GetAutofillPreviewElement() const = 0; GetAutofillPreviewElement() const = 0;

@@ -85,20 +85,6 @@ select:not(:-internal-list-box)::picker(select) {
block-start span-inline-start; block-start span-inline-start;
} }
/* This rule is here to ensure that we can get a ComputedStyle for
* ::picker(select). Without this, the [popover] UA style would make it
* display:none, which would mean that we wouldn't get a ComputedStyle, which
* would mean that we can't detect the author opting in with
* appearance:base-select on this element. This element is hidden when it isn't
* popover open via LayoutFlexibleBox::IsChildAllowed. This also means that the
* author can't force it to become visible while it is popover closed by
* putting display:block on it, but hopefully that's OK.
* TODO(crbug.com/364348901): Remove this.
*/
select::picker(select) {
display: block !important;
}
select:not(:-internal-list-box) option { select:not(:-internal-list-box) option {
/* min-size rules ensure that we meet accessibility guidelines for minimum target size. /* min-size rules ensure that we meet accessibility guidelines for minimum target size.
* https://github.com/openui/open-ui/issues/1026 * https://github.com/openui/open-ui/issues/1026

@@ -87,7 +87,8 @@ bool LayoutFlexibleBox::IsChildAllowed(LayoutObject* object,
const ComputedStyle& style) const { const ComputedStyle& style) const {
const auto* select = DynamicTo<HTMLSelectElement>(GetNode()); const auto* select = DynamicTo<HTMLSelectElement>(GetNode());
if (select && select->UsesMenuList()) [[unlikely]] { if (select && select->UsesMenuList()) [[unlikely]] {
if (select->IsAppearanceBaseButton()) { if (select->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle)) {
CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled()); CHECK(RuntimeEnabledFeatures::CustomizableSelectEnabled());
if (IsA<HTMLOptionElement>(object->GetNode()) || if (IsA<HTMLOptionElement>(object->GetNode()) ||
IsA<HTMLOptGroupElement>(object->GetNode()) || IsA<HTMLOptGroupElement>(object->GetNode()) ||
@@ -105,14 +106,6 @@ bool LayoutFlexibleBox::IsChildAllowed(LayoutObject* object,
// the InnerElement. // the InnerElement.
return false; return false;
} }
if (auto* popover = select->PopoverForAppearanceBase()) {
if (child == popover && !popover->popoverOpen()) {
// This is needed in order to keep the popover hidden after the UA
// sheet is forcing it to be display:block in order to get a computed
// style.
return false;
}
}
return true; return true;
} else { } else {
// For a size=1 appearance:auto <select>, we only render the active option // For a size=1 appearance:auto <select>, we only render the active option

@@ -23,7 +23,8 @@ static bool CanHaveGeneratedChildren(const LayoutObject& layout_object) {
if (RuntimeEnabledFeatures::CustomizableSelectEnabled() && if (RuntimeEnabledFeatures::CustomizableSelectEnabled() &&
layout_object.IsMenuList() && layout_object.IsMenuList() &&
To<HTMLSelectElement>(layout_object.GetNode()) To<HTMLSelectElement>(layout_object.GetNode())
->IsAppearanceBaseButton()) { ->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle)) {
// appearance:base-select <select>s should be allowed to have ::after etc. // appearance:base-select <select>s should be allowed to have ::after etc.
return true; return true;
} }

@@ -1211,7 +1211,8 @@ LayoutUnit LayoutBox::DefaultIntrinsicContentInlineSize() const {
const bool apply_fixed_size = StyleRef().ApplyControlFixedSize(&element); const bool apply_fixed_size = StyleRef().ApplyControlFixedSize(&element);
const auto* select = DynamicTo<HTMLSelectElement>(element); const auto* select = DynamicTo<HTMLSelectElement>(element);
if (select && select->UsesMenuList() && !select->IsAppearanceBaseButton()) if (select && select->UsesMenuList() &&
StyleRef().EffectiveAppearance() != AppearanceValue::kBaseSelect)
[[unlikely]] { [[unlikely]] {
return apply_fixed_size ? MenuListIntrinsicInlineSize(*select, *this) return apply_fixed_size ? MenuListIntrinsicInlineSize(*select, *this)
: kIndefiniteSize; : kIndefiniteSize;
@@ -1265,12 +1266,11 @@ LayoutUnit LayoutBox::DefaultIntrinsicContentBlockSize() const {
return kIndefiniteSize; return kIndefiniteSize;
} }
if (const auto* select = DynamicTo<HTMLSelectElement>(GetNode())) { if (const auto* select = DynamicTo<HTMLSelectElement>(GetNode())) {
if (!select->IsAppearanceBaseButton()) { if (!select->UsesMenuList()) {
if (select->UsesMenuList()) {
return MenuListIntrinsicBlockSize(*select, *this);
}
return ListBoxItemBlockSize(*select, *this) * select->ListBoxSize() - return ListBoxItemBlockSize(*select, *this) * select->ListBoxSize() -
ComputeLogicalScrollbars().BlockSum(); ComputeLogicalScrollbars().BlockSum();
} else if (effective_appearance != AppearanceValue::kBaseSelect) {
return MenuListIntrinsicBlockSize(*select, *this);
} }
} }
if (IsTextField()) { if (IsTextField()) {

@@ -2242,6 +2242,9 @@ ax::mojom::blink::Role AXNodeObject::NativeRoleIgnoringAria() const {
if (ParentObjectIfPresent() && ParentObjectIfPresent()->RoleValue() == if (ParentObjectIfPresent() && ParentObjectIfPresent()->RoleValue() ==
ax::mojom::blink::Role::kComboBoxSelect) { ax::mojom::blink::Role::kComboBoxSelect) {
// Only the UA popover element should get the MenuListPopup role.
CHECK(!RuntimeEnabledFeatures::CustomizableSelectEnabled() ||
HTMLSelectElement::IsPopoverForAppearanceBase(GetNode()));
return ax::mojom::blink::Role::kMenuListPopup; return ax::mojom::blink::Role::kMenuListPopup;
} }
@@ -5768,9 +5771,10 @@ void AXNodeObject::AddNodeChildren() {
void AXNodeObject::AddMenuListChildren() { void AXNodeObject::AddMenuListChildren() {
auto* select = To<HTMLSelectElement>(GetNode()); auto* select = To<HTMLSelectElement>(GetNode());
if (select->IsAppearanceBasePicker()) { if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
// In appearance: base-select (customizable select), the children of the CHECK(select->UsesMenuList());
// combobox is the displayed data list. // When CustomizableSelect is enabled, there will always be one
// MenuListPopup child which is the UA popover element.
AddNodeChild(select->PopoverForAppearanceBase()); AddNodeChild(select->PopoverForAppearanceBase());
return; return;
} }
@@ -5781,9 +5785,8 @@ void AXNodeObject::AddMenuListChildren() {
void AXNodeObject::AddMenuListPopupChildren() { void AXNodeObject::AddMenuListPopupChildren() {
auto* select = To<HTMLSelectElement>(ParentObject()->GetNode()); auto* select = To<HTMLSelectElement>(ParentObject()->GetNode());
if (select->IsAppearanceBasePicker()) { // With CustomizableSelect, MenuListPopups can have more interesting children.
// In appearance: base-select (customizable select), the children of the if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
// popup are all of the natural dom children of the <select>.
for (Node* child = NodeTraversal::FirstChild(*select); child; for (Node* child = NodeTraversal::FirstChild(*select); child;
child = NodeTraversal::NextSibling(*child)) { child = NodeTraversal::NextSibling(*child)) {
if (child == select->SlottedButton()) { if (child == select->SlottedButton()) {
@@ -5797,8 +5800,6 @@ void AXNodeObject::AddMenuListPopupChildren() {
return; return;
} }
// In appearance: auto/none, the children of the popup are the flat tree
// children of the slot associated with the popup.
AddNodeChildren(); AddNodeChildren();
} }
@@ -5847,9 +5848,18 @@ void AXNodeObject::AddChildrenImpl() {
AddValidationMessageChild(); AddValidationMessageChild();
CHECK_ATTACHED(); CHECK_ATTACHED();
if (RoleValue() == ax::mojom::blink::Role::kComboBoxSelect) { auto* select = DynamicTo<HTMLSelectElement>(GetNode());
if (RuntimeEnabledFeatures::CustomizableSelectEnabled() && select &&
select->UsesMenuList() && !select->IsMultiple()) {
// When CustomizableSelect is enabled, then we need to enforce our custom AX
// tree structure for select elements even if the author changed the select
// element's role by setting the role attribute.
AddMenuListChildren(); AddMenuListChildren();
} else if (RoleValue() == ax::mojom::blink::Role::kMenuListPopup) { } else if (RoleValue() == ax::mojom::blink::Role::kMenuListPopup) {
if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
// Only the UA popover element should have the MenuListPopup mapping.
CHECK(HTMLSelectElement::IsPopoverForAppearanceBase(GetNode()));
}
AddMenuListPopupChildren(); AddMenuListPopupChildren();
} else if (HasValidHTMLTableStructureAndLayout()) { } else if (HasValidHTMLTableStructureAndLayout()) {
AddTableChildren(); AddTableChildren();
@@ -6099,8 +6109,14 @@ bool AXNodeObject::CanHaveChildren() const {
// and improving stability in Blink. // and improving stability in Blink.
bool result = !GetElement() || AXObject::CanHaveChildren(*GetElement()); bool result = !GetElement() || AXObject::CanHaveChildren(*GetElement());
switch (native_role_) { switch (native_role_) {
case ax::mojom::blink::Role::kCheckBox:
case ax::mojom::blink::Role::kListBoxOption: case ax::mojom::blink::Role::kListBoxOption:
if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
// When CustomizableSelect is enabled, then options are allowed to have
// children as per the new content model.
break;
}
[[fallthrough]];
case ax::mojom::blink::Role::kCheckBox:
case ax::mojom::blink::Role::kMenuItem: case ax::mojom::blink::Role::kMenuItem:
case ax::mojom::blink::Role::kMenuItemCheckBox: case ax::mojom::blink::Role::kMenuItemCheckBox:
case ax::mojom::blink::Role::kMenuItemRadio: case ax::mojom::blink::Role::kMenuItemRadio:

@@ -978,6 +978,30 @@ Node* AXObject::GetParentNodeForComputeParent(AXObjectCacheImpl& cache,
return node->OwnerShadowHost(); return node->OwnerShadowHost();
} }
Node* parent = nullptr;
// Select elements have a complex architecture with two different slot
// elements which `node` may be slotted into. `node` might also not be slotted
// into anything at all (which is why we are doing this before using
// LayoutTreeBuilderTraversal). Despite this, we always want to expose a
// consistent structure for the select element with a MenuList and
// MenuListPopup with all the children exposed in the MenuListPopup. For
// consistency, we will use the select's PopoverForAppearanceBase element as
// the MenuListPopup and make it the parent of all the nodes inside the
// select.
if (RuntimeEnabledFeatures::CustomizableSelectEnabled()) {
if (auto* select = DynamicTo<HTMLSelectElement>(node->parentNode())) {
if (select->UsesMenuList()) {
if (node == select->SlottedButton()) {
// <select>'s author provided <button> should not have any
// accessibility mappings.
return nullptr;
}
parent = select->PopoverForAppearanceBase();
}
}
}
// Use LayoutTreeBuilderTraversal::Parent(), which handles pseudo content. // Use LayoutTreeBuilderTraversal::Parent(), which handles pseudo content.
// This can return nullptr for a node that is never visited by // This can return nullptr for a node that is never visited by
// LayoutTreeBuilderTraversal's child traversal. For example, while an element // LayoutTreeBuilderTraversal's child traversal. For example, while an element
@@ -988,35 +1012,8 @@ Node* AXObject::GetParentNodeForComputeParent(AXObjectCacheImpl& cache,
// Whenever null is returned from this function, then a parent cannot be // Whenever null is returned from this function, then a parent cannot be
// computed, and when a parent is not provided or computed, the accessible // computed, and when a parent is not provided or computed, the accessible
// object will not be created. // object will not be created.
Node* parent = LayoutTreeBuilderTraversal::Parent(*node); if (!parent) {
parent = LayoutTreeBuilderTraversal::Parent(*node);
// The parent of a customizable select's popup is the select.
if (IsA<HTMLDataListElement>(node)) {
if (auto* select = DynamicTo<HTMLSelectElement>(node->OwnerShadowHost())) {
if (node == select->PopoverForAppearanceBase()) {
return select;
}
}
}
// For the content of a customizable select, the parent must be the element
// assigned the role of kMenuListPopup. To accomplish this, it is necessary to
// adapt to unusual DOM structure. If no parent, or the parent has a <select>
// shadow host, then the actual parent should be the <select>.
// TODO(aleventhal, jarhar): try to simplify this code. @jarhar wrote in code
// review: "I don't think that a UA <slot> will ever get returned by
// LayoutTreeBuilderTraversal::Parent. In this case, I think
// LayoutTreeBuilderTraversal::Parent should just return the <select>."
HTMLSelectElement* owner_select = nullptr;
if (IsA<HTMLSlotElement>(parent) && parent->IsInUserAgentShadowRoot()) {
owner_select = DynamicTo<HTMLSelectElement>(parent->OwnerShadowHost());
} else if (!parent) {
owner_select = DynamicTo<HTMLSelectElement>(NodeTraversal::Parent(*node));
}
if (owner_select && owner_select->IsAppearanceBasePicker()) {
// Return the popup's <datalist> element.
return owner_select->PopoverForAppearanceBase();
} }
// No parent: this can occur when elements are not assigned a slot. // No parent: this can occur when elements are not assigned a slot.
@@ -1119,9 +1116,17 @@ bool AXObject::CanHaveChildren(Element& element) {
// For consistency with the past, options with a single text child are leaves. // For consistency with the past, options with a single text child are leaves.
// However, options can now sometimes have interesting children, for // However, options can now sometimes have interesting children, for
// a <select> menulist that uses appearance:base-select. // a <select> menulist that uses appearance:base-select.
// This code looks at IsAppearanceBaseButton instead of IsAppearanceBasePicker
// in order to have the same result whether the picker is open or closed.
// Ideally we would check to see if the picker is opted in to base appearance,
// but without a style update that would change based on whether the picker is
// open or not. When the button is opted in then the user will likely be able
// to see the child node structure of the option element in the button, so we
// can use that as a signal to allow options to have children.
if (auto* option = DynamicTo<HTMLOptionElement>(element)) { if (auto* option = DynamicTo<HTMLOptionElement>(element)) {
return option->OwnerSelectElement() && return option->OwnerSelectElement() &&
option->OwnerSelectElement()->IsAppearanceBasePicker() && option->OwnerSelectElement()->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle) &&
!option->HasOneTextChild(); !option->HasOneTextChild();
} }
@@ -7337,7 +7342,8 @@ bool AXObject::OnNativeClickAction() {
// Forward default action on custom select to its button. // Forward default action on custom select to its button.
if (auto* select = DynamicTo<HTMLSelectElement>(GetNode())) { if (auto* select = DynamicTo<HTMLSelectElement>(GetNode())) {
if (select->IsAppearanceBaseButton()) { if (select->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle)) {
if (auto* button = select->SlottedButton()) { if (auto* button = select->SlottedButton()) {
element = button; element = button;
} }

@@ -165,7 +165,9 @@ Node* RetargetInput(Node* node) {
possible_select = NodeTraversal::Parent(*node); possible_select = NodeTraversal::Parent(*node);
} }
if (auto* select = DynamicTo<HTMLSelectElement>(possible_select)) { if (auto* select = DynamicTo<HTMLSelectElement>(possible_select)) {
if (select->IsAppearanceBaseButton() && node == select->SlottedButton()) { if (select->IsAppearanceBaseButton(
HTMLSelectElement::StyleUpdateBehavior::kDontUpdateStyle) &&
node == select->SlottedButton()) {
return select; return select;
} }
} }
@@ -1225,7 +1227,8 @@ bool AXObjectCacheImpl::IsRelevantSlotElement(const HTMLSlotElement& slot) {
DCHECK(AXObject::CanSafelyUseFlatTreeTraversalNow(slot.GetDocument())); DCHECK(AXObject::CanSafelyUseFlatTreeTraversalNow(slot.GetDocument()));
DCHECK(slot.SupportsAssignment()); DCHECK(slot.SupportsAssignment());
if (slot.IsInUserAgentShadowRoot() && if (!RuntimeEnabledFeatures::CustomizableSelectEnabled() &&
slot.IsInUserAgentShadowRoot() &&
IsA<HTMLSelectElement>(slot.OwnerShadowHost())) { IsA<HTMLSelectElement>(slot.OwnerShadowHost())) {
return slot.GetIdAttribute() == shadow_element_names::kSelectOptions; return slot.GetIdAttribute() == shadow_element_names::kSelectOptions;
} }

@@ -3,6 +3,8 @@
<link rel=help href="https://issues.chromium.org/issues/361129480"> <link rel=help href="https://issues.chromium.org/issues/361129480">
<script src="../../../../resources/testharness.js"></script> <script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script> <script src="../../../../resources/testharnessreport.js"></script>
<script src="../../../../resources/testdriver.js"></script>
<script src="../../../../resources/testdriver-vendor.js"></script>
<select> <select>
<option>option</option> <option>option</option>
@@ -49,7 +51,13 @@ promise_test(async () => {
await new Promise(requestAnimationFrame); await new Promise(requestAnimationFrame);
assert_true(internals.isUseCounted(document, buttonUseCounterId), assert_true(internals.isUseCounted(document, buttonUseCounterId),
'Button use counter should be hit with base-select on button and picker.'); 'Button use counter should be hit with base-select on button and picker.');
assert_false(internals.isUseCounted(document, pickerUseCounterId),
"Picker use counter should not be hit if picker isn't open.");
await test_driver.click(select);
await new Promise(requestAnimationFrame);
await new Promise(requestAnimationFrame);
assert_true(internals.isUseCounted(document, pickerUseCounterId), assert_true(internals.isUseCounted(document, pickerUseCounterId),
'Picker use counter should be hit with base-select on button and picker.'); 'Picker use counter should be hit after picker is opened.');
}, 'UseCounters for base-select on button and picker should work.'); }, 'UseCounters for base-select on button and picker should work.');
</script> </script>

@@ -423,7 +423,6 @@ RootWebArea
none none
*combobox *combobox
MenuListPopup MenuListPopup
MenuListPopup
{ {
childIds : <object> childIds : <object>
chromeRole : { chromeRole : {

@@ -0,0 +1,17 @@
This is a testharness.js-based test.
[FAIL] Basic functionality of select picker with appearance:auto
assert_equals: expected "none" but got "base-select"
[FAIL] Basic functionality of select picker with appearance:none
assert_equals: expected "none" but got "base-select"
[FAIL] Switching appearance in popover-open should close the picker
assert_equals: expected "none" but got "base-select"
[FAIL] Switching appearance in JS after picker is open should close the picker
assert_equals: expected "none" but got "base-select"
[FAIL] The select picker is closed if the <select> appearance value is changed via CSS while the picker is open
assert_false: picker should get closed when the appearance value changes expected false got true
[FAIL] The select picker is closed if the ::picker() appearance value is changed via CSS while the picker is open
assert_false: setup expected false got true
[FAIL] The select picker is closed if the <select> inline appearance value is changed while the picker is open
assert_false: setup expected false got true
Harness: the test ran to completion.