diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc index c5525aca34b83e..6c5cf63bd498d9 100644 --- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc @@ -203,6 +203,22 @@ class DumpAccessibilityTreeTest : public DumpAccessibilityTestBase { } }; +// Subclass of DumpAccessibilityTreeTest that exposes ignored nodes. +class DumpAccessibilityTreeTestWithIgnoredNodes + : public DumpAccessibilityTreeTest { + protected: + // Override from DumpAccessibilityTreeTest. + void ChooseFeatures(std::vector* enabled_features, + std::vector* disabled_features) override { + // http://crbug.com/1063155 - temporary until this is enabled + // everywhere. + enabled_features->emplace_back( + features::kEnableAccessibilityExposeIgnoredNodes); + DumpAccessibilityTreeTest::ChooseFeatures(enabled_features, + disabled_features); + } +}; + void DumpAccessibilityTreeTest::AddDefaultFilters( std::vector* property_filters) { AddPropertyFilter(property_filters, "value='*'"); @@ -243,6 +259,13 @@ INSTANTIATE_TEST_SUITE_P( AccessibilityTreeFormatter::GetTestPasses().size()), DumpAccessibilityTreeTestPassToString()); +INSTANTIATE_TEST_SUITE_P( + All, + DumpAccessibilityTreeTestWithIgnoredNodes, + ::testing::Range(size_t{0}, + AccessibilityTreeFormatter::GetTestPasses().size()), + DumpAccessibilityTreeTestPassToString()); + IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSColor) { RunCSSTest(FILE_PATH_LITERAL("color.html")); } @@ -1829,7 +1852,7 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityInputRadio) { RunHtmlTest(FILE_PATH_LITERAL("input-radio.html")); } -IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, +IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTestWithIgnoredNodes, AccessibilityInputRadioCheckboxLabel) { RunHtmlTest(FILE_PATH_LITERAL("input-radio-checkbox-label.html")); } diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc index 6cc4621039d74f..4bf20cb6fbb962 100644 --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc @@ -256,6 +256,8 @@ void SetRuntimeFeaturesFromChromiumFeatures() { features::kEnableAccessibilityExposeDisplayNone}, {wf::EnableAccessibilityExposeHTMLElement, features::kEnableAccessibilityExposeHTMLElement}, + {wf::EnableAccessibilityExposeIgnoredNodes, + features::kEnableAccessibilityExposeIgnoredNodes}, {wf::EnableAllowSyncXHRInPageDismissal, blink::features::kAllowSyncXHRInPageDismissal}, {wf::EnableAutoplayIgnoresWebAudio, media::kAutoplayIgnoreWebAudio}, diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-android.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-android.txt index a52959116c26f6..3f7e937cd3a239 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-android.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-android.txt @@ -5,8 +5,6 @@ android.webkit.WebView focusable focused scrollable ++++android.view.View focusable name='label exposed for radio button ' ++++++android.widget.TextView name='label exposed for radio button ' ++++++android.widget.RadioButton role_description='radio button' checkable clickable focusable name='label exposed for radio button' item_index=1 row_index=1 -++++++android.widget.TextView name=' ' ++++android.view.View focusable name='label exposed for checkbox ' ++++++android.widget.TextView name='label exposed for checkbox ' -++++++android.widget.CheckBox role_description='checkbox' checkable clickable focusable name='label exposed for checkbox' -++++++android.widget.TextView name=' ' \ No newline at end of file +++++++android.widget.CheckBox role_description='checkbox' checkable clickable focusable name='label exposed for checkbox' \ No newline at end of file diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-auralinux.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-auralinux.txt index 3bd42bbf8e74a9..13dfdeb7101fc2 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-auralinux.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-auralinux.txt @@ -1,12 +1,10 @@ [document web] ++[section] -++++[radio button] name='label ignored for radio button' checkable:true -++++[check box] name='label ignored for checkbox' checkable:true +++++[radio button] name='label ignored for radio button' labelled-by checkable:true +++++[check box] name='label ignored for checkbox' labelled-by checkable:true ++++[label] name='label exposed for radio button ' label-for ++++++[static] name='label exposed for radio button ' ++++++[radio button] name='label exposed for radio button' labelled-by checkable:true -++++++[static] name=' ' ++++[label] name='label exposed for checkbox ' label-for ++++++[static] name='label exposed for checkbox ' -++++++[check box] name='label exposed for checkbox' labelled-by checkable:true -++++++[static] name=' ' +++++++[check box] name='label exposed for checkbox' labelled-by checkable:true \ No newline at end of file diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-blink.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-blink.txt index 70970717761e8d..dcc273591515fe 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-blink.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-blink.txt @@ -1,17 +1,20 @@ rootWebArea ++genericContainer ignored ++++genericContainer -++++++radioButton name='label ignored for radio button' checkedState=false -++++++checkBox name='label ignored for checkbox' checkedState=false +++++++labelText ignored +++++++++staticText ignored name='label ignored for radio button ' +++++++++++inlineTextBox ignored name='label ignored for radio button ' +++++++++radioButton name='label ignored for radio button' checkedState=false +++++++labelText ignored +++++++++staticText ignored name='label ignored for checkbox ' +++++++++++inlineTextBox ignored name='label ignored for checkbox ' +++++++++checkBox name='label ignored for checkbox' checkedState=false ++++++labelText name='label exposed for radio button ' ++++++++staticText name='label exposed for radio button ' ++++++++++inlineTextBox name='label exposed for radio button ' ++++++++radioButton name='label exposed for radio button' checkedState=false -++++++++staticText name=' ' -++++++++++inlineTextBox name=' ' ++++++labelText name='label exposed for checkbox ' ++++++++staticText name='label exposed for checkbox ' ++++++++++inlineTextBox name='label exposed for ' ++++++++++inlineTextBox name='checkbox ' ++++++++checkBox name='label exposed for checkbox' checkedState=false -++++++++staticText name=' ' diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-mac.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-mac.txt index ca4940c8c30061..84b28c44181419 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-mac.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-mac.txt @@ -5,8 +5,6 @@ AXWebArea ++++AXGroup AXTitle='label exposed for radio button ' ++++++AXStaticText AXValue='label exposed for radio button ' ++++++AXRadioButton AXTitleUIElement=:5 AXValue=0 -++++++AXStaticText AXValue=' ' ++++AXGroup AXTitle='label exposed for checkbox ' ++++++AXStaticText AXValue='label exposed for checkbox ' -++++++AXCheckBox AXTitleUIElement=:9 AXValue=0 -++++++AXStaticText AXValue=' ' +++++++AXCheckBox AXTitleUIElement=:8 AXValue=0 \ No newline at end of file diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt index 8eede08280ec1b..7621949ed53d61 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-uia-win.txt @@ -5,8 +5,6 @@ Document ++++Text Name='label exposed for radio button ' ++++++Text Name='label exposed for radio button ' ++++++RadioButton Name='label exposed for radio button' SelectionItem.IsSelected=false -++++++Text Name=' ' ++++Text Name='label exposed for checkbox ' ++++++Text Name='label exposed for checkbox ' -++++++CheckBox Name='label exposed for checkbox' Toggle.ToggleState='Off' -++++++Text Name=' ' +++++++CheckBox Name='label exposed for checkbox' Toggle.ToggleState='Off' \ No newline at end of file diff --git a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-win.txt b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-win.txt index 3d1c4020e77445..3ba2cd0eb2f94f 100644 --- a/content/test/data/accessibility/html/input-radio-checkbox-label-expected-win.txt +++ b/content/test/data/accessibility/html/input-radio-checkbox-label-expected-win.txt @@ -5,8 +5,6 @@ ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE ++++IA2_ROLE_LABEL name='label exposed for radio button ' FOCUSABLE ++++++ROLE_SYSTEM_STATICTEXT name='label exposed for radio button ' ++++++ROLE_SYSTEM_RADIOBUTTON name='label exposed for radio button' FOCUSABLE IA2_STATE_CHECKABLE checkable:true -++++++ROLE_SYSTEM_STATICTEXT name=' ' ++++IA2_ROLE_LABEL name='label exposed for checkbox ' FOCUSABLE ++++++ROLE_SYSTEM_STATICTEXT name='label exposed for checkbox ' -++++++ROLE_SYSTEM_CHECKBUTTON name='label exposed for checkbox' FOCUSABLE IA2_STATE_CHECKABLE checkable:true -++++++ROLE_SYSTEM_STATICTEXT name=' ' +++++++ROLE_SYSTEM_CHECKBUTTON name='label exposed for checkbox' FOCUSABLE IA2_STATE_CHECKABLE checkable:true \ No newline at end of file diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h index 77f99305b603b3..a36dcbb1657f52 100644 --- a/third_party/blink/public/platform/web_runtime_features.h +++ b/third_party/blink/public/platform/web_runtime_features.h @@ -74,6 +74,7 @@ class WebRuntimeFeatures { BLINK_PLATFORM_EXPORT static void EnableAccelerated2dCanvas(bool); BLINK_PLATFORM_EXPORT static void EnableAccessibilityExposeDisplayNone(bool); BLINK_PLATFORM_EXPORT static void EnableAccessibilityExposeHTMLElement(bool); + BLINK_PLATFORM_EXPORT static void EnableAccessibilityExposeIgnoredNodes(bool); BLINK_PLATFORM_EXPORT static void EnableAccessibilityObjectModel(bool); BLINK_PLATFORM_EXPORT static void EnableAdTagging(bool); BLINK_PLATFORM_EXPORT static void EnableAllowActivationDelegationAttr(bool); diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc index aada1fae52ef96..71e8e2b70f0038 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc @@ -812,8 +812,8 @@ bool AXLayoutObject::CanIgnoreSpaceNextTo(LayoutObject* layout, } bool AXLayoutObject::CanIgnoreTextAsEmpty() const { - DCHECK(layout_object_->IsText()); - DCHECK(layout_object_->Parent()); + if (!layout_object_ || !layout_object_->IsText() || !layout_object_->Parent()) + return false; LayoutText* layout_text = ToLayoutText(layout_object_); diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h index 7517cae747deb6..b00fde59d91179 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h +++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h @@ -102,6 +102,7 @@ class MODULES_EXPORT AXLayoutObject : public AXNodeObject { AXObjectInclusion DefaultObjectInclusion( IgnoredReasons* = nullptr) const override; bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override; + bool CanIgnoreTextAsEmpty() const override; // Properties of static elements. ax::mojom::blink::ListStyle GetListStyle() const final; @@ -192,7 +193,6 @@ class MODULES_EXPORT AXLayoutObject : public AXNodeObject { bool FindAllTableCellsWithRole(ax::mojom::blink::Role, AXObjectVector&) const; LayoutRect ComputeElementRect() const; - bool CanIgnoreTextAsEmpty() const; bool CanIgnoreSpaceNextTo(LayoutObject*, bool is_after) const; bool HasAriaCellRole(Element*) const; bool IsPlaceholder() const; diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc index 0a0c8c7a04c744..394034d38ea5c1 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc @@ -505,6 +505,14 @@ bool AXNodeObject::ComputeAccessibilityIsIgnored( return true; } +bool AXNodeObject::CanIgnoreTextAsEmpty() const { + // Note: it's safe to call AXNodeObject::ComputeAccessibilityIsIgnored, + // since that has just the logic we need - but note that + // AXLayoutObject::ComputeAccessibilityIsIgnored calls CanIgnoreTextAsEmpty + // so that'd create a loop. + return ComputeAccessibilityIsIgnored(); +} + static bool IsListElement(Node* node) { return IsA(*node) || IsA(*node) || IsA(*node); @@ -3396,10 +3404,23 @@ void AXNodeObject::AddChildren() { child = LayoutTreeBuilderTraversal::NextSibling(*child)) { if (child->IsMarkerPseudoElement() && AccessibilityIsIgnored()) continue; - AddChild(AXObjectCache().GetOrCreate(child)); + AXObject* child_obj = AXObjectCache().GetOrCreate(child); + + if (RuntimeEnabledFeatures::AccessibilityExposeIgnoredNodesEnabled() && + child_obj && + child_obj->RoleValue() == ax::mojom::blink::Role::kStaticText && + child_obj->CanIgnoreTextAsEmpty()) + continue; + + AddChild(child_obj); } } else { for (AXObject* obj = RawFirstChild(); obj; obj = obj->RawNextSibling()) { + if (RuntimeEnabledFeatures::AccessibilityExposeIgnoredNodesEnabled() && + obj && obj->RoleValue() == ax::mojom::blink::Role::kStaticText && + obj->CanIgnoreTextAsEmpty()) + continue; + AddChild(obj); } } diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.h b/third_party/blink/renderer/modules/accessibility/ax_node_object.h index 3220246b4f47f4..83a56a9edc8943 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_node_object.h +++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.h @@ -60,6 +60,7 @@ class MODULES_EXPORT AXNodeObject : public AXObject { AXObjectInclusion ShouldIncludeBasedOnSemantics( IgnoredReasons* = nullptr) const; bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override; + bool CanIgnoreTextAsEmpty() const override; const AXObject* InheritsPresentationalRoleFrom() const override; ax::mojom::blink::Role DetermineTableSectionRole() const; ax::mojom::blink::Role DetermineTableCellRole() const; diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc index 6e60ddd23f40b0..dd15c70c899f6a 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc @@ -1740,6 +1740,9 @@ const AXObject* AXObject::DisabledAncestor() const { } bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const { + if (RuntimeEnabledFeatures::AccessibilityExposeIgnoredNodesEnabled()) + return true; + if (AXObjectCache().IsAriaOwned(this)) { // Always include an aria-owned object. It must be a child of the // element with aria-owns. diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h index d9d858536b807f..66dc60336dbdde 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object.h +++ b/third_party/blink/renderer/modules/accessibility/ax_object.h @@ -526,6 +526,7 @@ class MODULES_EXPORT AXObject : public GarbageCollected { virtual bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const { return true; } + virtual bool CanIgnoreTextAsEmpty() const { return false; } bool AccessibilityIsIgnoredByDefault(IgnoredReasons* = nullptr) const; virtual AXObjectInclusion DefaultObjectInclusion( IgnoredReasons* = nullptr) const; diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc index 05cf9f644e7e5b..a0eb13af7cbf63 100644 --- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc +++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc @@ -121,6 +121,10 @@ void WebRuntimeFeatures::EnableAccessibilityExposeHTMLElement(bool enable) { RuntimeEnabledFeatures::SetAccessibilityExposeHTMLElementEnabled(enable); } +void WebRuntimeFeatures::EnableAccessibilityExposeIgnoredNodes(bool enable) { + RuntimeEnabledFeatures::SetAccessibilityExposeIgnoredNodesEnabled(enable); +} + void WebRuntimeFeatures::EnableAccessibilityObjectModel(bool enable) { RuntimeEnabledFeatures::SetAccessibilityObjectModelEnabled(enable); } diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 7409180188cc10..daba5e72f93ee6 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 @@ -122,6 +122,9 @@ { name: "AccessibilityExposeHTMLElement", }, + { + name: "AccessibilityExposeIgnoredNodes", + }, { name: "AccessibilityObjectModel", status: "experimental", diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc index 403fc1805ae30c..acc79edce250df 100644 --- a/ui/accessibility/accessibility_features.cc +++ b/ui/accessibility/accessibility_features.cc @@ -28,6 +28,18 @@ bool IsAccessibilityExposeHTMLElementEnabled() { ::features::kEnableAccessibilityExposeHTMLElement); } +// Enable exposing ignored nodes from Blink to the browser process AXTree. +// This will allow us to simplify logic by eliminating the distiction between +// "ignored and included in the tree" from "ignored and not included in the +// tree". +const base::Feature kEnableAccessibilityExposeIgnoredNodes{ + "AccessibilityExposeIgnoredNodes", base::FEATURE_DISABLED_BY_DEFAULT}; + +bool IsAccessibilityExposeIgnoredNodesEnabled() { + return base::FeatureList::IsEnabled( + ::features::kEnableAccessibilityExposeIgnoredNodes); +} + // Enable language detection to determine language used in page text, exposed // on the browser process AXTree. const base::Feature kEnableAccessibilityLanguageDetection{ diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h index 167fd293e0a1ac..39a637fd88a514 100644 --- a/ui/accessibility/accessibility_features.h +++ b/ui/accessibility/accessibility_features.h @@ -24,6 +24,13 @@ AX_BASE_EXPORT extern const base::Feature kEnableAccessibilityExposeHTMLElement; // browser process AXTree (as an ignored node). AX_BASE_EXPORT bool IsAccessibilityExposeHTMLElementEnabled(); +AX_BASE_EXPORT extern const base::Feature + kEnableAccessibilityExposeIgnoredNodes; + +// Returns true if all ignored nodes are exposed by Blink in the +// accessibility tree. +AX_BASE_EXPORT bool IsAccessibilityExposeIgnoredNodesEnabled(); + AX_BASE_EXPORT extern const base::Feature kEnableAccessibilityLanguageDetection; // Return true if language detection should be used to determine the language