From 742a07cf3fc6570aae0d46aa229c6712fc93e8d8 Mon Sep 17 00:00:00 2001 From: Aaron Leventhal Date: Mon, 19 Dec 2022 18:55:39 +0000 Subject: [PATCH] [A11y] Stable ids for AXObjects with DOM nodes Use the DOM node for the AXID for any AXObject with a DOM node. Other AXObjects will still generate their AXID, but in their own numerical namespace. For now, any AXID < 0 will be a generated ID. Role changes and alerts required some new browser-side code, because rather than the object being destroyed and created with a new id, the ID now stays the same. In some cases, test results have improved, e.g. rather than an object being destroyed and created with a different ID, it retains the same ID and a change event is fired for it. Benefits: - Removes over 100 lines of implementation code - Use less memory: removes ids_in_use_ (set) and node_object_mapping_ (map). - Reduce map lookups when an AXObject is removed -- based on the ID value, we can determine whether it has a DOM node and if it doesn't, we already know it cannot be in some of the maps - Make it easier for screen readers to maintain the user's point of regard within content, even when the layout or role changes. Users sometimes lose their place, as the screen reader may move the user to the top. A hope is that this will reduce the occurrences of that happening. - Enable the follow-ups listed below. Follow-up work: - Refactor content-visibility: auto change handling in a11y (crbug.com/1380449). - See if the dom_node_id field in AXNodeData is needed anymore now that any id >0 is the same as the dom node id. - Look at simplifying or removing reparenting computations in AXTreeSerializer. Maybe we can get rid of expensive AnyDescendantIsReparented(). Bug: none Change-Id: I321320b02c5608cf5a2748c0b36eead1c67a7f05 Cq-Do-Not-Cancel-Tryjobs: true Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4027071 Reviewed-by: Daniel Hosseinian Commit-Queue: Aaron Leventhal Reviewed-by: Benjamin Beaudry Reviewed-by: David Tseng Reviewed-by: Mason Freed Reviewed-by: Kinuko Yasuda Cr-Commit-Position: refs/heads/main@{#1085033} --- .../common/cursors/recovery_strategy_test.js | 19 +- .../automation/tests/tabs/force_layout.js | 6 +- .../pdf/renderer/pdf_accessibility_tree.cc | 8 +- ..._node_textrangeprovider_win_browsertest.cc | 5 +- .../accessibility/browser_accessibility.cc | 3 + .../accessibility/browser_accessibility.h | 3 + ...browser_accessibility_cocoa_browsertest.mm | 2 +- .../browser_accessibility_manager_android.cc | 3 +- ...browser_accessibility_manager_auralinux.cc | 16 +- .../browser_accessibility_manager_win.cc | 1 + .../dump_accessibility_events_browsertest.cc | 5 + .../WebContentsAccessibilityEventsTest.java | 8 +- .../public/renderer/render_accessibility.h | 1 + .../render_accessibility_impl.cc | 16 +- .../accessibility/render_accessibility_impl.h | 1 + .../event/add-alert-content-expected-mac.txt | 2 +- ...rt-with-role-change-expected-auralinux.txt | 2 + .../add-hidden-attribute-expected-win.txt | 2 +- ...-hidden-attribute-subtree-expected-win.txt | 2 +- ...d-changes-button-role-expected-android.txt | 1 + ...changes-button-role-expected-auralinux.txt | 8 +- ...essed-changes-button-role-expected-mac.txt | 2 + ...d-changes-button-role-expected-uia-win.txt | 12 +- ...essed-changes-button-role-expected-win.txt | 14 +- ...box-children-change-expected-auralinux.txt | 3 +- ...a-textbox-children-change-expected-win.txt | 2 - .../css-display-descendants-expected-win.txt | 2 +- .../event/css-display-expected-android.txt | 1 + .../event/css-display-expected-win.txt | 2 +- ...gnored-but-included-expected-auralinux.txt | 3 - .../menu-opened-closed-expected-auralinux.txt | 4 +- .../event/menu-opened-closed-expected-mac.txt | 4 +- .../event/menu-opened-closed-expected-win.txt | 4 +- ...bar-show-hide-menus-expected-auralinux.txt | 10 +- ...nubar-show-hide-menus-expected-uia-win.txt | 8 +- .../menubar-show-hide-menus-expected-win.txt | 7 +- ...h-active-descendant-expected-auralinux.txt | 5 +- ...nt-with-active-descendant-expected-win.txt | 8 +- .../event/role-changed-expected-android.txt | 1 + .../event/role-changed-expected-auralinux.txt | 3 + .../event/role-changed-expected-mac.txt | 1 + .../event/role-changed-expected-uia-win.txt | 2 + .../event/role-changed-expected-win.txt | 1 + .../accessibility/event/role-changed.html | 11 + ...side-hidden-element-expected-auralinux.txt | 7 +- .../attributes/chrome-ax-node-id-expected.txt | 2 +- third_party/blink/public/web/web_ax_context.h | 2 + .../core/accessibility/ax_object_cache.h | 5 +- .../blink/renderer/core/accessibility/axid.h | 2 +- .../core/aom/computed_accessible_node.cc | 4 +- .../blink/renderer/core/dom/dom_node_ids.h | 6 + .../renderer/core/dom/weak_identifier_map.h | 6 +- .../modules/accessibility/ax_node_object.cc | 20 +- .../accessibility/ax_object_cache_impl.cc | 627 +++++++----------- .../accessibility/ax_object_cache_impl.h | 48 +- .../inspector_accessibility_agent.cc | 2 +- .../modules/exported/web_ax_context.cc | 47 +- .../FlagExpectations/disable-layout-ng | 5 +- .../aom-computed-accessible-node.html | 9 +- .../accessibility/canvas-select-row.html | 1 + .../accessibility/notification-listeners.html | 28 +- .../table-header-column-row.html | 4 +- ui/accessibility/ax_event_generator.cc | 3 +- ui/accessibility/ax_tree.cc | 3 +- ui/accessibility/platform/ax_platform_node.cc | 2 +- .../platform/ax_platform_node_auralinux.cc | 6 + .../platform/ax_platform_node_win.cc | 1 + ui/accessibility/platform/ax_unique_id.h | 2 + 68 files changed, 492 insertions(+), 574 deletions(-) create mode 100644 content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt create mode 100644 content/test/data/accessibility/event/css-display-expected-android.txt create mode 100644 content/test/data/accessibility/event/role-changed-expected-android.txt create mode 100644 content/test/data/accessibility/event/role-changed-expected-auralinux.txt create mode 100644 content/test/data/accessibility/event/role-changed-expected-mac.txt create mode 100644 content/test/data/accessibility/event/role-changed-expected-uia-win.txt create mode 100644 content/test/data/accessibility/event/role-changed-expected-win.txt create mode 100644 content/test/data/accessibility/event/role-changed.html diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js index 66adb0e146ed7..5a29869c28bdc 100644 --- a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js +++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js @@ -24,6 +24,8 @@ AccessibilityExtensionRecoveryStrategyTest = class extends CommonE2ETestBase { }; +// TODO(https://issuetracker.google.com/issues/263127143) Recovery can likely be +// simplified now that most ids are stable. AX_TEST_F( 'AccessibilityExtensionRecoveryStrategyTest', 'ReparentedRecovery', async function() { @@ -54,30 +56,21 @@ AX_TEST_F( assertFalse( bAncestryRecovery.requiresRecovery(), 'bAncestryRecovery.requiresRecovery'); - assertTrue( + assertFalse( pAncestryRecovery.requiresRecovery(), 'pAncestryRecovery.requiresRecovery()'); - assertTrue( + assertFalse( sAncestryRecovery.requiresRecovery(), 'sAncestryRecovery.requiresRecovery()'); assertFalse( bTreePathRecovery.requiresRecovery(), 'bTreePathRecovery.requiresRecovery()'); - assertTrue( + assertFalse( pTreePathRecovery.requiresRecovery(), 'pTreePathRecovery.requiresRecovery()'); - assertTrue( + assertFalse( sTreePathRecovery.requiresRecovery(), 'sTreePathRecovery.requiresRecovery()'); - - assertEquals(RoleType.BUTTON, bAncestryRecovery.node.role); - assertEquals(root, pAncestryRecovery.node); - assertEquals(root, sAncestryRecovery.node); - - assertEquals(b, bTreePathRecovery.node); - assertEquals(b, pTreePathRecovery.node); - assertEquals(b, sTreePathRecovery.node); - assertFalse( bAncestryRecovery.requiresRecovery(), 'bAncestryRecovery.requiresRecovery()'); diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/force_layout.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/force_layout.js index 09bd60ecf09a6..0bfdc0a6f66fa 100644 --- a/chrome/test/data/extensions/api_test/automation/tests/tabs/force_layout.js +++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/force_layout.js @@ -9,10 +9,8 @@ var allTests = [ rootNode.addEventListener('focus', (evt) => { // The underlying DOM button has not changed, but its layout has. This // listener ensures we at least get a focus event on a new valid - // accessibility object. Once display none nodes are in the tree, it's - // likely that |node| == |evt.target|. - assertFalse(evt.target == node); - assertEq(undefined, node.role); + // accessibility object. + assertEq(evt.target, node); assertEq('button', evt.target.role); chrome.test.succeed(); }); diff --git a/components/pdf/renderer/pdf_accessibility_tree.cc b/components/pdf/renderer/pdf_accessibility_tree.cc index fd649a13a01c5..ca0f35cdb237d 100644 --- a/components/pdf/renderer/pdf_accessibility_tree.cc +++ b/components/pdf/renderer/pdf_accessibility_tree.cc @@ -1661,13 +1661,11 @@ PdfAccessibilityTree::GetRenderAccessibilityIfEnabled() { // we shouldn't use it. This can happen if Blink accessibility is disabled // after we started generating the accessible PDF. base::WeakPtr weak_this = GetWeakPtr(); - if (render_accessibility->GenerateAXID() <= 0) + if (!render_accessibility->HasActiveDocument()) { return nullptr; + } - // GenerateAXID() above can cause self deletion. Returning nullptr will cause - // callers to stop doing work. - if (!weak_this) - return nullptr; + DCHECK(weak_this); return render_accessibility; } diff --git a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc index bb655673ec54a..be17a623b7be0 100644 --- a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc +++ b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc @@ -2590,18 +2590,19 @@ IN_PROC_BROWSER_TEST_F(AXPlatformNodeTextRangeProviderWinBrowserTest, EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Before frame\nText in iframe\nAfter frame"); + // Traversing by word should include trailing whitespace. EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, /*count*/ 2, /*expected_text*/ L"Text ", /*expected_count*/ 2); EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word, /*count*/ -1, - /*expected_text*/ L"frame", + /*expected_text*/ L"frame\n", /*expected_count*/ -1); EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT( text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character, /*count*/ 2, - /*expected_text*/ L"frame\nT", + /*expected_text*/ L"frame\nTe", /*expected_count*/ 2); EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character, /*count*/ 7, diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc index a28c168f06f8f..f15f5ca88353a 100644 --- a/content/browser/accessibility/browser_accessibility.cc +++ b/content/browser/accessibility/browser_accessibility.cc @@ -892,6 +892,9 @@ const ui::AXUniqueId& BrowserAccessibility::GetUniqueId() const { // This is not the same as GetData().id which comes from Blink, because // those ids are only unique within the Blink process. We need one that is // unique for the browser process. + // TODO(accessibility) We should be able to get rid of this, because node IDs + // are actually unique within their renderer process, and each renderer + // process has its own OS-level window, which is all the uniqueness we need. return unique_id_; } diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h index fa165725f629a..3f51eb32114a6 100644 --- a/content/browser/accessibility/browser_accessibility.h +++ b/content/browser/accessibility/browser_accessibility.h @@ -556,6 +556,9 @@ class CONTENT_EXPORT BrowserAccessibility : public ui::AXPlatformNodeDelegate { static bool HasInvalidAttribute(const ui::TextAttributeList& attributes); // A unique ID, since node IDs are frame-local. + // TODO(accessibility) We should be able to get rid of this, because node IDs + // are actually local to the renderer process, and each renderer process has + // its own OS-level window, which is all the uniqueness we need. ui::AXUniqueId unique_id_; }; diff --git a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm index 1bc93f87475a9..da408678fe7eb 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm @@ -128,7 +128,7 @@ AccessibilityNotificationWaiter value_waiter(shell()->web_contents(), EXPECT_EQ( ui::AXTextMarkerToAXPosition(text_edit.edit_text_marker)->ToString(), - "TextPosition anchor_id=4 text_offset=1 affinity=downstream " + "TextPosition anchor_id=7 text_offset=1 affinity=downstream " "annotated_text=B<>"); } diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc index 175fc06fdfe64..caccf0a402939 100644 --- a/content/browser/accessibility/browser_accessibility_manager_android.cc +++ b/content/browser/accessibility/browser_accessibility_manager_android.cc @@ -314,6 +314,8 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent( if (android_node->IsSlider()) wcax->HandleSliderChanged(android_node->unique_id()); break; + case ui::AXEventGenerator::Event::ROLE_CHANGED: + break; case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED: case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED: wcax->HandleScrollPositionChanged(android_node->unique_id()); @@ -393,7 +395,6 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent( case ui::AXEventGenerator::Event::READONLY_CHANGED: case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED: case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED: - case ui::AXEventGenerator::Event::ROLE_CHANGED: case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED: case ui::AXEventGenerator::Event::SELECTED_CHANGED: case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED: diff --git a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc index 87d4a589cb50b..76167d48b29b6 100644 --- a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc +++ b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc @@ -276,9 +276,19 @@ void BrowserAccessibilityManagerAuraLinux::FireGeneratedEvent( FireReadonlyChangedEvent(wrapper); break; case ui::AXEventGenerator::Event::RANGE_VALUE_CHANGED: - DCHECK(wrapper->GetData().IsRangeValueSupported()); - FireEvent(wrapper, ax::mojom::Event::kValueChanged); + if (wrapper->GetData().IsRangeValueSupported()) { + FireEvent(wrapper, ax::mojom::Event::kValueChanged); + } + break; + case ui::AXEventGenerator::Event::ALERT: + case ui::AXEventGenerator::Event::ROLE_CHANGED: { + // Manually fire removal and addition of the object. + ui::AXPlatformNodeAuraLinux* platform_node = + ToBrowserAccessibilityAuraLinux(wrapper)->GetNode(); + platform_node->OnSubtreeWillBeDeleted(); + platform_node->OnSubtreeCreated(); break; + } case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED: FireEvent(wrapper, ax::mojom::Event::kSelectedChildrenChanged); break; @@ -307,7 +317,6 @@ void BrowserAccessibilityManagerAuraLinux::FireGeneratedEvent( // Currently unused events on this platform. case ui::AXEventGenerator::Event::NONE: case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED: - case ui::AXEventGenerator::Event::ALERT: case ui::AXEventGenerator::Event::ATOMIC_CHANGED: case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED: case ui::AXEventGenerator::Event::CARET_BOUNDS_CHANGED: @@ -348,7 +357,6 @@ void BrowserAccessibilityManagerAuraLinux::FireGeneratedEvent( case ui::AXEventGenerator::Event::RANGE_VALUE_STEP_CHANGED: case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED: case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED: - case ui::AXEventGenerator::Event::ROLE_CHANGED: case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED: case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED: case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED: diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc index 334d09e441ac5..82e562ee8cd2c 100644 --- a/content/browser/accessibility/browser_accessibility_manager_win.cc +++ b/content/browser/accessibility/browser_accessibility_manager_win.cc @@ -423,6 +423,7 @@ void BrowserAccessibilityManagerWin::FireGeneratedEvent( HandleAriaPropertiesChangedEvent(*wrapper); break; case ui::AXEventGenerator::Event::ROLE_CHANGED: + FireWinAccessibilityEvent(IA2_EVENT_ROLE_CHANGED, wrapper); FireUiaPropertyChangedEvent(UIA_AriaRolePropertyId, wrapper); break; case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED: diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc index 8b32ff136d1e2..10ed419b314e4 100644 --- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc @@ -984,6 +984,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, RunEventTest(FILE_PATH_LITERAL("remove-hidden-attribute-subtree.html")); } +IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, + AccessibilityEventsRoleChanged) { + RunEventTest(FILE_PATH_LITERAL("role-changed.html")); +} + IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest, AccessibilityEventsSamePageLinkNavigation) { #if BUILDFLAG(IS_WIN) diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java index d7a8459863e31..5c0a1a4149c05 100644 --- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java +++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityEventsTest.java @@ -612,7 +612,7 @@ public void test_cssDisplayDescendants() { @Test @SmallTest public void test_cssDisplay() { - performTest("css-display.html", EMPTY_EXPECTATIONS_FILE); + performTest("css-display.html", "css-display-expected-android.txt"); } @Test @@ -968,6 +968,12 @@ public void test_reportValidityInvalidField() { "report-validity-invalid-field-expected-android.txt"); } + @Test + @SmallTest + public void test_roleChanged() { + performTest("role-changed.html", "role-changed-expected-android.txt"); + } + @Test @SmallTest public void test_samePageLinkNavigation() { diff --git a/content/public/renderer/render_accessibility.h b/content/public/renderer/render_accessibility.h index bd462ecf80679..1ad982418a92b 100644 --- a/content/public/renderer/render_accessibility.h +++ b/content/public/renderer/render_accessibility.h @@ -21,6 +21,7 @@ namespace content { // This interface exposes the accessibility tree for one RenderFrame. class CONTENT_EXPORT RenderAccessibility { public: + virtual bool HasActiveDocument() const = 0; virtual int GenerateAXID() = 0; // These APIs allow a page with a single EMBED element to graft an diff --git a/content/renderer/accessibility/render_accessibility_impl.cc b/content/renderer/accessibility/render_accessibility_impl.cc index a4b77764e1c7c..91865564b40c1 100644 --- a/content/renderer/accessibility/render_accessibility_impl.cc +++ b/content/renderer/accessibility/render_accessibility_impl.cc @@ -518,8 +518,7 @@ void RenderAccessibilityImpl::HandleAXEvent(const ui::AXEvent& event) { DCHECK(!document.IsNull()); auto obj = WebAXObject::FromWebDocumentByID(document, event.id); - if (obj.IsDetached()) - return; + DCHECK(!obj.IsDetached()); #if BUILDFLAG(IS_ANDROID) // Inline text boxes are needed to support moving by character/word/line. @@ -539,11 +538,9 @@ void RenderAccessibilityImpl::HandleAXEvent(const ui::AXEvent& event) { if (IsImmediateProcessingRequiredForEvent(event)) event_schedule_mode_ = EventScheduleMode::kProcessEventsImmediately; - if (!obj.IsDetached()) { - MarkWebAXObjectDirty(obj, /* subtree= */ false, event.event_from, - event.event_from_action, event.event_intents, - event.event_type); - } + MarkWebAXObjectDirty(obj, /* subtree= */ false, event.event_from, + event.event_from_action, event.event_intents, + event.event_type); ScheduleSendPendingAccessibilityEvents(); } @@ -732,6 +729,11 @@ void RenderAccessibilityImpl::ScheduleSendPendingAccessibilityEvents( delay); } +bool RenderAccessibilityImpl::HasActiveDocument() const { + DCHECK(ax_context_); + return ax_context_->HasActiveDocument(); +} + int RenderAccessibilityImpl::GenerateAXID() { DCHECK(ax_context_); return ax_context_->GenerateAXID(); diff --git a/content/renderer/accessibility/render_accessibility_impl.h b/content/renderer/accessibility/render_accessibility_impl.h index 0bcaabd38b867..3495f078dee62 100644 --- a/content/renderer/accessibility/render_accessibility_impl.h +++ b/content/renderer/accessibility/render_accessibility_impl.h @@ -94,6 +94,7 @@ class CONTENT_EXPORT RenderAccessibilityImpl : public RenderAccessibility, ui::AXMode GetAccessibilityMode() { return accessibility_mode_; } // RenderAccessibility implementation. + bool HasActiveDocument() const override; int GenerateAXID() override; ui::AXTreeID GetTreeIDForPluginHost() const override; void SetPluginTreeSource(PluginAXTreeSource* source) override; diff --git a/content/test/data/accessibility/event/add-alert-content-expected-mac.txt b/content/test/data/accessibility/event/add-alert-content-expected-mac.txt index 6ef18ace77528..78916f51b163f 100644 --- a/content/test/data/accessibility/event/add-alert-content-expected-mac.txt +++ b/content/test/data/accessibility/event/add-alert-content-expected-mac.txt @@ -2,4 +2,4 @@ AXLiveRegionChanged on AXGroup AXSubrole=AXApplicationAlert AXDescription='Foo' === Start Continuation === AXLiveRegionChanged on AXGroup AXSubrole=AXApplicationAlert AXDescription='Bar' === Start Continuation === -AXLiveRegionChanged on AXGroup AXSubrole=AXApplicationAlert AXDescription='Baz' +AXLiveRegionChanged on AXGroup AXSubrole=AXApplicationAlert AXDescription='Baz' \ No newline at end of file diff --git a/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt b/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt new file mode 100644 index 0000000000000..7fab4c0bf7bda --- /dev/null +++ b/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt @@ -0,0 +1,2 @@ +CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_NOTIFICATION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_NOTIFICATION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt b/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt index dac2ebc49b9e6..3774bd9dffa0b 100644 --- a/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt +++ b/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt @@ -1,3 +1,3 @@ -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_LISTITEM name="Item 3" level=1 PosInSet=3 SetSize=3 +EVENT_OBJECT_HIDE on
role=ROLE_SYSTEM_LISTITEM INVISIBLE EVENT_OBJECT_REORDER on
role=ROLE_SYSTEM_LIST SetSize=2 IA2_EVENT_TEXT_REMOVED on
role=ROLE_SYSTEM_LIST SetSize=2 old_text={'' start=2 end=3} diff --git a/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt b/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt index 239d0ec42f189..de58ebbf70a96 100644 --- a/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt +++ b/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt @@ -1,3 +1,3 @@ -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_LISTITEM level=1 PosInSet=3 SetSize=3 +EVENT_OBJECT_HIDE on
  • role=ROLE_SYSTEM_GROUPING INVISIBLE EVENT_OBJECT_REORDER on
      role=ROLE_SYSTEM_LIST SetSize=2 IA2_EVENT_TEXT_REMOVED on
        role=ROLE_SYSTEM_LIST SetSize=2 old_text={'' start=2 end=3} diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt index e69de29bb2d1d..aec4f94bd7871 100644 --- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt +++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt @@ -0,0 +1 @@ +TYPE_WINDOW_CONTENT_CHANGED - [contentTypes=64] diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt index c2baee736b8fd..307519075e049 100644 --- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt +++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt @@ -1,7 +1,7 @@ CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE -CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE +STATE-CHANGE:PRESSED:TRUE role=ROLE_TOGGLE_BUTTON name='(null)' ENABLED,FOCUSABLE,PRESSED,SENSITIVE,SHOWING,VISIBLE === Start Continuation === CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE -CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE +STATE-CHANGE:PRESSED:TRUE role=ROLE_PUSH_BUTTON name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt index 68384149eddce..3d89dc07ad939 100644 --- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt +++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt @@ -1 +1,3 @@ +AXValueChanged on AXCheckBox AXSubrole=AXToggleButton AXValue=1 === Start Continuation === +AXValueChanged on AXButton diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt index ca8cdffd37149..dccb97574815c 100644 --- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt +++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt @@ -1,7 +1,7 @@ -StructureChanged/ChildAdded on role=button -StructureChanged/ChildRemoved on role=document -StructureChanged/ChildrenReordered on role=document +AriaProperties changed on role=button +AriaRole changed on role=button +ToggleToggleState changed on role=button === Start Continuation === -StructureChanged/ChildAdded on role=button -StructureChanged/ChildRemoved on role=document -StructureChanged/ChildrenReordered on role=document +AriaProperties changed on role=button +AriaRole changed on role=button +ToggleToggleState changed on role=button diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt index c1f13452443a9..12bef9687f3cb 100644 --- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt +++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt @@ -1,11 +1,5 @@ -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE -EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_GROUPING -EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE -IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_GROUPING new_text={'' start=0 end=1} -IA2_EVENT_TEXT_REMOVED on role=ROLE_SYSTEM_GROUPING old_text={'' start=0 end=1} +EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE +IA2_EVENT_ROLE_CHANGED on role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE === Start Continuation === -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE -EVENT_OBJECT_REORDER on role=ROLE_SYSTEM_GROUPING -EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE -IA2_EVENT_TEXT_INSERTED on role=ROLE_SYSTEM_GROUPING new_text={'' start=0 end=1} -IA2_EVENT_TEXT_REMOVED on role=ROLE_SYSTEM_GROUPING old_text={'' start=0 end=1} +EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE +IA2_EVENT_ROLE_CHANGED on role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE diff --git a/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt b/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt index be72da988403b..5bc568751bee7 100644 --- a/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt +++ b/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt @@ -4,5 +4,4 @@ CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_ENTRY EN === Start Continuation === CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_ENTRY EDITABLE,ENABLED,FOCUSABLE,MULTI-LINE,SENSITIVE,SHOWING,VISIBLE,SELECTABLE-TEXT === Start Continuation === -CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_ENTRY ENABLED,SENSITIVE,SHOWING,SINGLE-LINE,VISIBLE,SELECTABLE-TEXT -CHILDREN-CHANGED:REMOVE index:4 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +PARENT-CHANGED PARENT:(role=ROLE_ENTRY name='role only, plain') role=ROLE_PUSH_BUTTON name='ok' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt b/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt index 5863299cb9824..dc16c26d7c5bf 100644 --- a/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt +++ b/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt @@ -7,6 +7,4 @@ EVENT_OBJECT_VALUECHANGE on
        role=ROLE_SYSTEM_TEXT name="focusable" value=" EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE IA2_STATE_EDITABLE EVENT_OBJECT_VALUECHANGE on
        role=ROLE_SYSTEM_TEXT name="editable" value="foo" FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_MULTI_LINE,IA2_STATE_SELECTABLE_TEXT === Start Continuation === -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE -EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE EVENT_OBJECT_VALUECHANGE on role=ROLE_SYSTEM_TEXT name="role only, plain" value="foook" IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE diff --git a/content/test/data/accessibility/event/css-display-descendants-expected-win.txt b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt index 89fa29fd7ba39..ebebf4be9107d 100644 --- a/content/test/data/accessibility/event/css-display-descendants-expected-win.txt +++ b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt @@ -1,3 +1,3 @@ -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_GROUPING name="Heading" level=2 +EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_GROUPING INVISIBLE EVENT_OBJECT_REORDER on
        role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner" diff --git a/content/test/data/accessibility/event/css-display-expected-android.txt b/content/test/data/accessibility/event/css-display-expected-android.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/content/test/data/accessibility/event/css-display-expected-android.txt @@ -0,0 +1 @@ + diff --git a/content/test/data/accessibility/event/css-display-expected-win.txt b/content/test/data/accessibility/event/css-display-expected-win.txt index 86c32d1f77db1..9882d4076639d 100644 --- a/content/test/data/accessibility/event/css-display-expected-win.txt +++ b/content/test/data/accessibility/event/css-display-expected-win.txt @@ -1,4 +1,4 @@ -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_GROUPING name="Heading" level=2 +EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_GROUPING INVISIBLE EVENT_OBJECT_REORDER on
        role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING name="Banner" IA2_EVENT_TEXT_INSERTED on
        role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL new_text={'' start=1 end=2} diff --git a/content/test/data/accessibility/event/individual-nodes-become-ignored-but-included-expected-auralinux.txt b/content/test/data/accessibility/event/individual-nodes-become-ignored-but-included-expected-auralinux.txt index e5a52a008ccfe..c1f819b98f11e 100644 --- a/content/test/data/accessibility/event/individual-nodes-become-ignored-but-included-expected-auralinux.txt +++ b/content/test/data/accessibility/event/individual-nodes-become-ignored-but-included-expected-auralinux.txt @@ -2,6 +2,3 @@ CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PANEL) role=ROLE_LANDMARK ENABL CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_PANEL) role=ROLE_LANDMARK ENABLED,SENSITIVE,SHOWING,VISIBLE PARENT-CHANGED PARENT:(role=ROLE_LANDMARK name='(null)') role=ROLE_LINK name='1' ENABLED,SENSITIVE,SHOWING,VISIBLE PARENT-CHANGED PARENT:(role=ROLE_LANDMARK name='(null)') role=ROLE_STATIC name='2' ENABLED,SENSITIVE,SHOWING,VISIBLE -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt index 65f44a5bf6cda..eb507242ba46a 100644 --- a/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt +++ b/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt @@ -5,7 +5,5 @@ CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_MENU) role=ROLE_MENU_ITEM ENABLED, STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE === Start Continuation === CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_MENU) role=ROLE_MENU_ITEM ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE === Start Continuation === -CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_MENU) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='menu' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_MENU) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE \ No newline at end of file diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt index 63d587e516d48..e1b762c6a4b05 100644 --- a/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt +++ b/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt @@ -2,6 +2,4 @@ AXMenuOpened on AXMenu AXDescription='menu' === Start Continuation === AXMenuOpened on AXMenu === Start Continuation === -AXMenuClosed on AXWebArea -=== Start Continuation === -AXMenuClosed on AXWebArea +=== Start Continuation === \ No newline at end of file diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt index 32f673a0a63b8..7db8406e39a43 100644 --- a/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt +++ b/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt @@ -2,6 +2,6 @@ EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP name="menu" === Start Continuation === EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP IA2_STATE_VERTICAL SetSize=1 === Start Continuation === -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP IA2_STATE_VERTICAL SetSize=1 +EVENT_SYSTEM_MENUPOPUPEND on
        role=ROLE_SYSTEM_MENUPOPUP INVISIBLE === Start Continuation === -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2 +EVENT_SYSTEM_MENUPOPUPEND on
        role=ROLE_SYSTEM_MENUPOPUP INVISIBLE \ No newline at end of file diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt index 02f80322d1415..a3037b2eed9b7 100644 --- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt +++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt @@ -4,8 +4,8 @@ STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,V STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE === Start Continuation === STATE-CHANGE:EXPANDED:FALSE role=ROLE_MENU_ITEM name='File' ENABLED,EXPANDABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE === Start Continuation === === Start Continuation === STATE-CHANGE:EXPANDED:TRUE role=ROLE_MENU_ITEM name='File' ENABLED,EXPANDABLE,EXPANDED,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP @@ -13,6 +13,6 @@ STATE-CHANGE:EXPANDED:TRUE role=ROLE_MENU_ITEM name='New' ENABLED,EXPANDABLE,EXP STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE === Start Continuation === -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE -STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE -=== Start Continuation === +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE +STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE +=== Start Continuation === \ No newline at end of file diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt index eb736da99ba0d..c0b5cd0e963ba 100644 --- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt +++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt @@ -7,8 +7,6 @@ Name changed on role=group, name=open file and new done ExpandCollapseExpandCollapseState changed on role=menuitem, name=File MenuClosed MenuClosed -MenuClosed -MenuClosed Name changed on role=group, name=close file and new done === Start Continuation === Name changed on role=group, name=open new done @@ -19,10 +17,6 @@ MenuOpened on role=menu, name=File MenuOpened on role=menu, name=New Name changed on role=group, name=open file done === Start Continuation === -MenuClosed -MenuClosed -MenuClosed -MenuClosed Name changed on role=group, name=hide menubar done === Start Continuation === -Name changed on role=group, name=show menubar done +Name changed on role=group, name=show menubar done \ No newline at end of file diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt index d797b55d47915..06cb8ca1851db 100644 --- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt +++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt @@ -3,14 +3,11 @@ EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP name="F EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3 === Start Continuation === EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_MENUITEM name="File" COLLAPSED,HASPOPUP PosInSet=1 SetSize=2 -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3 -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3 +EVENT_SYSTEM_MENUPOPUPEND on
          role=ROLE_SYSTEM_MENUPOPUP INVISIBLE === Start Continuation === === Start Continuation === EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_MENUITEM name="File" EXPANDED,HASPOPUP PosInSet=1 SetSize=2 EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3 EVENT_SYSTEM_MENUPOPUPSTART on role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3 === Start Continuation === -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3 -EVENT_SYSTEM_MENUPOPUPEND on role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3 -=== Start Continuation === +=== Start Continuation === \ No newline at end of file diff --git a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt index f18e7d67caeea..d5ee6d2c4a752 100644 --- a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt +++ b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt @@ -9,10 +9,7 @@ STATE-CHANGE:FOCUSED:FALSE role=ROLE_MENU_ITEM name='New' ENABLED,FOCUSABLE,SENS STATE-CHANGE:FOCUSED:TRUE role=ROLE_MENU_ITEM name='Open...' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE === Start Continuation === CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PANEL) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE -CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE -PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_MENU_ITEM name='About' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE -PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_MENU_ITEM name='File' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP -STATE-CHANGE:DEFUNCT:TRUE role=ROLE_INVALID name='(null)' DEFUNCT +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PANEL) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE === Start Continuation === FOCUS-EVENT:FALSE role=ROLE_MENU_ITEM name='Open...' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE FOCUS-EVENT:TRUE role=ROLE_MENU_ITEM name='Quit' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt index 1018175de8c4b..7ed90a57a07e2 100644 --- a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt +++ b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt @@ -4,11 +4,7 @@ IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on role=ROLE_SYSTEM_MENUPOPU EVENT_OBJECT_FOCUS on role=ROLE_SYSTEM_MENUITEM name="Open..." FOCUSED,FOCUSABLE PosInSet=2 SetSize=3 IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on role=ROLE_SYSTEM_MENUPOPUP name="File" FOCUSABLE IA2_STATE_VERTICAL SetSize=3 === Start Continuation === -EVENT_OBJECT_HIDE on role=ROLE_SYSTEM_GROUPING -EVENT_OBJECT_REORDER on
          role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2 -EVENT_OBJECT_SHOW on role=ROLE_SYSTEM_GROUPING -IA2_EVENT_TEXT_INSERTED on
          role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2 new_text={'' start=0 end=1} -IA2_EVENT_TEXT_REMOVED on
          role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2 old_text={'' start=0 end=1} +IA2_EVENT_ROLE_CHANGED on role=ROLE_SYSTEM_GROUPING === Start Continuation === EVENT_OBJECT_FOCUS on role=ROLE_SYSTEM_MENUITEM name="Quit" FOCUSED,FOCUSABLE PosInSet=3 SetSize=3 -IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on role=ROLE_SYSTEM_MENUPOPUP name="File" FOCUSABLE IA2_STATE_VERTICAL SetSize=3 \ No newline at end of file +IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on role=ROLE_SYSTEM_MENUPOPUP name="File" FOCUSABLE IA2_STATE_VERTICAL SetSize=3 diff --git a/content/test/data/accessibility/event/role-changed-expected-android.txt b/content/test/data/accessibility/event/role-changed-expected-android.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/content/test/data/accessibility/event/role-changed-expected-android.txt @@ -0,0 +1 @@ + diff --git a/content/test/data/accessibility/event/role-changed-expected-auralinux.txt b/content/test/data/accessibility/event/role-changed-expected-auralinux.txt new file mode 100644 index 0000000000000..c40a938fdb04b --- /dev/null +++ b/content/test/data/accessibility/event/role-changed-expected-auralinux.txt @@ -0,0 +1,3 @@ +CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +NAME-CHANGED:Role will change role=ROLE_PUSH_BUTTON name='Role will change' ENABLED,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/event/role-changed-expected-mac.txt b/content/test/data/accessibility/event/role-changed-expected-mac.txt new file mode 100644 index 0000000000000..28576b00bba78 --- /dev/null +++ b/content/test/data/accessibility/event/role-changed-expected-mac.txt @@ -0,0 +1 @@ +AXTitleChanged on AXButton AXTitle='Role will change' diff --git a/content/test/data/accessibility/event/role-changed-expected-uia-win.txt b/content/test/data/accessibility/event/role-changed-expected-uia-win.txt new file mode 100644 index 0000000000000..594c42349ab60 --- /dev/null +++ b/content/test/data/accessibility/event/role-changed-expected-uia-win.txt @@ -0,0 +1,2 @@ +AriaRole changed on role=button, name=Role will change +Name changed on role=button, name=Role will change diff --git a/content/test/data/accessibility/event/role-changed-expected-win.txt b/content/test/data/accessibility/event/role-changed-expected-win.txt new file mode 100644 index 0000000000000..5971aee81dc8e --- /dev/null +++ b/content/test/data/accessibility/event/role-changed-expected-win.txt @@ -0,0 +1 @@ +IA2_EVENT_ROLE_CHANGED on role=ROLE_SYSTEM_PUSHBUTTON name="Role will change" diff --git a/content/test/data/accessibility/event/role-changed.html b/content/test/data/accessibility/event/role-changed.html new file mode 100644 index 0000000000000..42334014a5567 --- /dev/null +++ b/content/test/data/accessibility/event/role-changed.html @@ -0,0 +1,11 @@ + + + +
          Role will change
          + + + diff --git a/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt b/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt index 2ad88deb615a7..3657567aa57fa 100644 --- a/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt +++ b/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt @@ -1,3 +1,6 @@ -CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_COMBO_BOX name='(null)' ENABLED,EXPANDABLE,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP +PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_SECTION name='(null)' ENABLED,FOCUSABLE,SENSITIVE +TEXT-ATTRIBUTES-CHANGED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE +TEXT-ATTRIBUTES-CHANGED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE TEXT-CARET-MOVED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE -TEXT-SELECTION-CHANGED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE \ No newline at end of file +TEXT-SELECTION-CHANGED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE diff --git a/content/test/data/accessibility/mac/attributes/chrome-ax-node-id-expected.txt b/content/test/data/accessibility/mac/attributes/chrome-ax-node-id-expected.txt index ce7e03aa0cbe2..b1b1208d54a80 100644 --- a/content/test/data/accessibility/mac/attributes/chrome-ax-node-id-expected.txt +++ b/content/test/data/accessibility/mac/attributes/chrome-ax-node-id-expected.txt @@ -1,2 +1,2 @@ div.accessibilityAttributeNames.has(ChromeAXNodeId)='yes' -div.accessibilityAttributeValue(ChromeAXNodeId)='4' +div.accessibilityAttributeValue(ChromeAXNodeId)='7' diff --git a/third_party/blink/public/web/web_ax_context.h b/third_party/blink/public/web/web_ax_context.h index 01b96de526768..48a88c63eeb85 100644 --- a/third_party/blink/public/web/web_ax_context.h +++ b/third_party/blink/public/web/web_ax_context.h @@ -25,6 +25,8 @@ class BLINK_EXPORT WebAXContext { explicit WebAXContext(WebDocument document, const ui::AXMode& mode); ~WebAXContext(); + bool HasActiveDocument() const; + const ui::AXMode& GetAXMode() const; void SetAXMode(const ui::AXMode&) const; diff --git a/third_party/blink/renderer/core/accessibility/ax_object_cache.h b/third_party/blink/renderer/core/accessibility/ax_object_cache.h index 816031be40836..06fcbd9794e9e 100644 --- a/third_party/blink/renderer/core/accessibility/ax_object_cache.h +++ b/third_party/blink/renderer/core/accessibility/ax_object_cache.h @@ -96,9 +96,8 @@ class CORE_EXPORT AXObjectCache : public GarbageCollected { // Removes AXObject backed by passed-in object, if there is one. virtual void Remove(AccessibleNode*) = 0; - // Returns true if the AXObject is removed. - virtual bool Remove(LayoutObject*) = 0; - virtual void Remove(const Node*) = 0; + virtual void Remove(LayoutObject*) = 0; + virtual void Remove(Node*) = 0; virtual void Remove(Document*) = 0; virtual void Remove(AbstractInlineTextBox*) = 0; diff --git a/third_party/blink/renderer/core/accessibility/axid.h b/third_party/blink/renderer/core/accessibility/axid.h index 4b805d88be41a..e116ab0c8c132 100644 --- a/third_party/blink/renderer/core/accessibility/axid.h +++ b/third_party/blink/renderer/core/accessibility/axid.h @@ -6,7 +6,7 @@ #define THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_AXID_H_ namespace blink { -using AXID = unsigned; +using AXID = int; } #endif // THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_AXID_H_ diff --git a/third_party/blink/renderer/core/aom/computed_accessible_node.cc b/third_party/blink/renderer/core/aom/computed_accessible_node.cc index f9b67dceab0c0..42a22465a6f39 100644 --- a/third_party/blink/renderer/core/aom/computed_accessible_node.cc +++ b/third_party/blink/renderer/core/aom/computed_accessible_node.cc @@ -92,7 +92,7 @@ void ComputedAccessibleNodePromiseResolver::EnsureUpToDate() { } void ComputedAccessibleNodePromiseResolver::UpdateTreeAndResolve() { - if (!ax_context_->GetDocument()) { + if (!ax_context_->HasActiveDocument()) { resolver_->Resolve(); return; } @@ -114,7 +114,7 @@ void ComputedAccessibleNodePromiseResolver::UpdateTreeAndResolve() { ax_context_->GetDocument()->View()->UpdateAllLifecyclePhasesExceptPaint( DocumentUpdateReason::kAccessibility); AXObjectCache& cache = ax_context_->GetAXObjectCache(); - AXID ax_id = ax_id_ ? ax_id_ : cache.GetAXID(element_); + AXID ax_id = ax_id_ ? ax_id_ : cache.GetExistingAXID(element_); if (!ax_id || !cache.ObjectFromAXID(ax_id)) { resolver_->Resolve(); // No AXObject exists for this element. return; diff --git a/third_party/blink/renderer/core/dom/dom_node_ids.h b/third_party/blink/renderer/core/dom/dom_node_ids.h index dbd14182bc277..7f31040668167 100644 --- a/third_party/blink/renderer/core/dom/dom_node_ids.h +++ b/third_party/blink/renderer/core/dom/dom_node_ids.h @@ -19,8 +19,14 @@ class CORE_EXPORT DOMNodeIds { STATIC_ONLY(DOMNodeIds); public: + // Return a DOMNodeID or 0 if one hasn't been assigned. static DOMNodeId ExistingIdForNode(Node*); + + // Return the existing DOMNodeID if it has already been assigned, otherwise, + // assign a new DOMNodeID and return that. static DOMNodeId IdForNode(Node*); + + // Return a node for the DOMNodeID or null if one hasn't been assigned. static Node* NodeForId(DOMNodeId); }; diff --git a/third_party/blink/renderer/core/dom/weak_identifier_map.h b/third_party/blink/renderer/core/dom/weak_identifier_map.h index b759c8709bddc..9415d24fc8349 100644 --- a/third_party/blink/renderer/core/dom/weak_identifier_map.h +++ b/third_party/blink/renderer/core/dom/weak_identifier_map.h @@ -43,8 +43,12 @@ class WeakIdentifierMap final return result; } + // If the object is not found, returns 0 which is not a valid identifier. static IdentifierType ExistingIdentifier(T* object) { - return Instance().object_to_identifier_.at(object); + auto it_result = Instance().object_to_identifier_.find(object); + return it_result != Instance().object_to_identifier_.end() + ? it_result->value + : 0; } static T* Lookup(IdentifierType identifier) { 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 6039892fe1dda..8209a2c4d843d 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc @@ -3736,13 +3736,25 @@ bool AXNodeObject::HasValidHTMLTableStructureAndLayout() const { // In that case the children will still be added via AddNodeChildren(), // so that no content is lost. // See comments in AddTableChildren() for more information about valid tables. + auto* table = To(GetNode()); for (Element* child = ElementTraversal::FirstChild(*GetElement()); child; child = ElementTraversal::NextSibling(*child)) { - if (!IsA(child) && - !IsA(child) && - !child->HasTagName(html_names::kColgroupTag)) { - return false; + if (child->HasTagName(html_names::kColgroupTag)) { + continue; + } + if (child->HasTagName(html_names::kTbodyTag)) { + continue; } + if (child->HasTagName(html_names::kTheadTag) && child == table->tHead()) { + continue; // Only one is valid. + } + if (child->HasTagName(html_names::kTfootTag) && child == table->tFoot()) { + continue; // Only one is valid. + } + if (IsA(child) && child == table->caption()) { + continue; // Only one caption is valid. + } + return false; } return true; diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc index c99043be28ab2..213bb80db2a9f 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc +++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc @@ -44,6 +44,7 @@ #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document_lifecycle.h" +#include "third_party/blink/renderer/core/dom/dom_node_ids.h" #include "third_party/blink/renderer/core/dom/slot_assignment_engine.h" #include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" @@ -488,14 +489,15 @@ bool IsLayoutObjectRelevantForAccessibility(const LayoutObject& layout_object) { if (layout_object.IsText()) return IsLayoutTextRelevantForAccessibility(To(layout_object)); - // Menu list option and HTML area elements are indexed by DOM node, never by - // layout object. - // TODO(accessibility) Remove this special case once we prefer to always - // back/index by DOM node when present. + // An AXMenuListOption will be created, which is a subclass of AXNodeObject, + // not of AXLayoutObject. if (AXObjectCacheImpl::ShouldCreateAXMenuListOptionFor( layout_object.GetNode())) { return false; } + + // An AXImageMapLink will be created, which is a subclass of AXNodeObject, not + // of AXLayoutObject. if (IsA(layout_object.GetNode())) return false; @@ -881,12 +883,19 @@ AXObject* AXObjectCacheImpl::Get(const LayoutObject* layout_object) { DCHECK(!result->IsDetached() || has_been_disposed_) << "Detached AXNodeObject in map: " << "AXID#" << ax_id << " Node=" << node; + DCHECK(!result->IsMissingParent()) + << "Had AXObject but was missing parent: " << layout_object << " " + << result->ToString(true, true) << "\nComputed parent: " + << AXObject::ComputeNonARIAParent(*this, layout_object->GetNode()) + ->ToString(true, true); #endif + return result; } AXObject* AXObjectCacheImpl::SafeGet(const Node* node, - bool allow_display_locking_invalidation) { + bool allow_display_locking_invalidation, + bool allow_layout_object_relevance_check) { if (!node) return nullptr; @@ -901,157 +910,64 @@ AXObject* AXObjectCacheImpl::SafeGet(const Node* node, } #endif - LayoutObject* layout_object = node->GetLayoutObject(); - - AXID layout_id = 0; - if (layout_object) { - auto it = layout_object_mapping_.find(layout_object); - if (it != layout_object_mapping_.end()) - layout_id = it->value; - } - DCHECK(!HashTraits::IsDeletedValue(layout_id)); - if (layout_id) { - auto it = objects_.find(layout_id); - if (it != objects_.end()) { - if (allow_display_locking_invalidation && IsDisplayLocked(node)) { - // Change from AXLayoutObject -> AXNodeObject. - // The node is in a display locked subtree, but we've previously put it - // in the cache with its layout object. - Invalidate(layout_object->GetDocument(), layout_id); - } - return it->value; - } + AXID node_id = + static_cast(DOMNodeIds::ExistingIdForNode(const_cast(node))); + if (!node_id) { + // An ID hasn't yet been generated for this DOM node, but ::CreateAndInit() + // will ensure a DOMNodeID is generated. Therefore if an id doesn't exist + // for a DOM node, it means that it can't have an associated AXObject. return nullptr; } - auto it_node = node_object_mapping_.find(node); - AXID node_id = it_node != node_object_mapping_.end() ? it_node->value : 0; - DCHECK(!HashTraits::IsDeletedValue(node_id)); - if (node_id) { - auto it = objects_.find(node_id); - if (it != objects_.end()) { - if (allow_display_locking_invalidation && layout_object && - !IsDisplayLocked(node)) { - // Change from AXNodeObject -> AXLayoutObject. - // Has a layout object but no layout_id, meaning that when the AXObject - // was originally created only for Node*, the LayoutObject* didn't exist - // yet. This can happen if an AXNodeObject is created for a node that's - // not laid out, but later something changes and it gets a layoutObject - // (like if it's reparented). It's also possible the layout object - // changed. - Invalidate(layout_object->GetDocument(), node_id); - } - return it->value; - } - } - - return nullptr; -} - -AXObject* AXObjectCacheImpl::Get(const Node* node) { - if (!node) + auto it_result = objects_.find(node_id); + if (it_result == objects_.end()) { return nullptr; - - if (has_been_disposed_) - return SafeGet(node); - -#if DCHECK_IS_ON() - if (const Element* element = DynamicTo(node)) { - if (AccessibleNode* accessible_node = element->ExistingAccessibleNode()) { - DCHECK(!accessible_node_mapping_.Contains(accessible_node)) - << "The accessible node directly attached to an element should not " - "have its own AXObject: " - << element; - } } -#endif - LayoutObject* layout_object = node->GetLayoutObject(); + AXObject* result = it_result->value; + DCHECK(result) << "AXID#" << node_id + << " in map, but matches an AXObject of null, for " << node; - AXID layout_id = 0; - if (layout_object) { - auto it = layout_object_mapping_.find(layout_object); - if (it != layout_object_mapping_.end()) - layout_id = it->value; + // When shutting down, allow detached nodes to be in the map, and do not + // attempt invalidations. + if (has_been_disposed_) { + return result->IsDetached() ? nullptr : result; } - DCHECK(!HashTraits::IsDeletedValue(layout_id)); - auto it_node = node_object_mapping_.find(node); - AXID node_id = it_node != node_object_mapping_.end() ? it_node->value : 0; - DCHECK(!HashTraits::IsDeletedValue(node_id)); - - if (!layout_id && !node_id) - return nullptr; + DCHECK(!result->IsDetached()) << "Detached object was in map."; - // Some elements such as are indexed by DOM node, not by layout object. - if (!layout_object || - !IsLayoutObjectRelevantForAccessibility(*layout_object)) { - // Only text nodes still are able to become suddenly irrelevant. - if (layout_id && node->IsTextNode() && - !IsHiddenTextNodeRelevantForAccessibility(To(*node), - IsDisplayLocked(node))) { - // Layout object and node are now both irrelevant for accessibility. - // For example, text becomes irrelevant when it changes to whitespace, or - // if it already is whitespace and the text around it changes to makes it - // redundant whitespace. In this case, Invalidate(), which will remove - // objects that are no longer relevant. - Invalidate(node->GetDocument(), layout_id); - } else { - // Layout object is irrelevant, but node object can still be relevant. - if (!node_id) { - DCHECK(layout_id); // One of of node_id, layout_id is non-zero. - Invalidate(node->GetDocument(), layout_id); - } else { - layout_object = nullptr; - layout_id = 0; - } + // Compute whether an allowed invalidation is necessary that alter whether the + // current object should be an AXLayoutObject vs AXNodeObject. + bool is_ax_layout_object = IsA(result); + bool should_be_ax_layout_object = is_ax_layout_object; + if (node->GetLayoutObject()) { + if (allow_display_locking_invalidation) { + // Nodes that are display locked may have stale layout objects. We enforce + // that they use AXNodeObject because the layout is not up-to-date. + should_be_ax_layout_object = !IsDisplayLocked(node); + } + if (should_be_ax_layout_object && allow_layout_object_relevance_check && + !IsLayoutObjectRelevantForAccessibility(*node->GetLayoutObject())) { + should_be_ax_layout_object = false; } } - if (layout_id && IsDisplayLocked(node)) { - // Change from AXLayoutObject -> AXNodeObject. - // The node is in a display locked subtree, but we've previously put it in - // the cache with its layout object. - Invalidate(node->GetDocument(), layout_id); - } else if (layout_object && node_id && !layout_id && !IsDisplayLocked(node)) { - // Change from AXNodeObject -> AXLayoutObject. - // Has a layout object but no layout_id, meaning that when the AXObject was - // originally created only for Node*, the LayoutObject* didn't exist yet. - // This can happen if an AXNodeObject is created for a node that's not laid - // out, but later something changes and it gets a layoutObject (like if it's - // reparented). It's also possible the layout object changed. - Invalidate(layout_object->GetDocument(), node_id); - } - - if (layout_id) { - auto it = objects_.find(layout_id); - AXObject* result = it != objects_.end() ? it->value : nullptr; -#if DCHECK_IS_ON() - DCHECK(result) << "Had AXID for LayoutObject but no entry in objects_"; - DCHECK(result->IsAXLayoutObject()); - // Do not allow detached objects except when disposing entire tree. - DCHECK(!result->IsDetached() || has_been_disposed_) - << "Detached AXLayoutObject in map: " - << "AXID#" << layout_id << " LayoutObject=" << layout_object; -#endif - return result; + // Process any computed invalidation, queuing the work for later. + // TODO(crbug.com/1380449) Replace this system with notifications that + // invalidate based on Blink's knowledge of what's changing, instead of + // checking objects when we touch them. + if (is_ax_layout_object != should_be_ax_layout_object) { + Invalidate(node->GetDocument(), result->AXObjectID()); } - DCHECK(node_id); - - auto it_result = objects_.find(node_id); - AXObject* result = it_result != objects_.end() ? it_result->value : nullptr; -#if DCHECK_IS_ON() - DCHECK(result) << "Had AXID for Node but no entry in objects_"; - DCHECK(result->IsAXNodeObject()); - // Do not allow detached objects except when disposing entire tree. - DCHECK(!result->IsDetached() || has_been_disposed_) - << "Detached AXNodeObject in map: " - << "AXID#" << node_id << " Node=" << node; -#endif return result; } +AXObject* AXObjectCacheImpl::Get(const Node* node) { + return SafeGet(node, /* allow_display_locking_invalidation */ true, + /* allow_layout_object_relevance_check */ true); +} + AXObject* AXObjectCacheImpl::Get(AbstractInlineTextBox* inline_text_box) { if (!inline_text_box) return nullptr; @@ -1084,14 +1000,14 @@ void AXObjectCacheImpl::Invalidate(Document& document, AXID ax_id) { AXID AXObjectCacheImpl::GetAXID(Node* node) { AXObject* ax_object = GetOrCreate(node); if (!ax_object) - return 0; + return ui::AXNodeData::kInvalidAXID; return ax_object->AXObjectID(); } AXID AXObjectCacheImpl::GetExistingAXID(Node* node) { AXObject* ax_object = SafeGet(node); if (!ax_object) - return 0; + return ui::AXNodeData::kInvalidAXID; return ax_object->AXObjectID(); } @@ -1149,7 +1065,6 @@ AXObject* AXObjectCacheImpl::GetAXImageForMap(HTMLMapElement& map) { } AXObject* AXObjectCacheImpl::CreateFromRenderer(LayoutObject* layout_object) { - // FIXME: How could layoutObject->node() ever not be an Element? Node* node = layout_object->GetNode(); // media element @@ -1414,79 +1329,93 @@ AXObject* AXObjectCacheImpl::GetOrCreate(Node* node, if (AXObject* obj = Get(node)) return obj; - return CreateAndInit(node, parent_if_known); + return CreateAndInit(node, node->GetLayoutObject(), parent_if_known); } -AXObject* AXObjectCacheImpl::CreateAndInit( - Node* node, - AXObject* parent_if_known, - AXID use_axid, - absl::optional ax_type) { - DCHECK(node); +// Caller must provide a node, a layout object, or both (where they match). +AXObject* AXObjectCacheImpl::CreateAndInit(Node* node, + LayoutObject* layout_object, + AXObject* parent_if_known) { +#if DCHECK_IS_ON() + DCHECK(node || layout_object); + DCHECK(!node || !layout_object || layout_object->GetNode() == node); DCHECK(!parent_if_known || parent_if_known->CanHaveChildren()); + DCHECK(GetDocument().Lifecycle().GetState() >= + DocumentLifecycle::kAfterPerformLayout) + << "Unclean document at lifecycle " + << GetDocument().Lifecycle().ToString(); +#endif // DCHECK_IS_ON() - // If the node has a layout object, prefer using that as the primary key for - // the AXObject, with the exception of the HTMLAreaElement and nodes within - // a locked subtree, which are created based on its node. - LayoutObject* layout_object = node->GetLayoutObject(); - if (!ax_type) - ax_type = DetermineAXObjectType(node, layout_object, parent_if_known); - - if (*ax_type == kAXLayoutObject) { - return CreateAndInit(layout_object, parent_if_known, use_axid, - kAXLayoutObject); - } - - if (*ax_type == kPruneSubtree) + // Determine the type of accessibility object to be created. + AXObjectType ax_type = + DetermineAXObjectType(node, layout_object, parent_if_known); + if (ax_type == kPruneSubtree) { return nullptr; + } #if DCHECK_IS_ON() - DCHECK(node->isConnected()); - DCHECK(node->IsElementNode() || node->IsTextNode() || node->IsDocumentNode()); - Document* document = &node->GetDocument(); - DCHECK(document); - DCHECK(document->Lifecycle().GetState() >= - DocumentLifecycle::kAfterPerformLayout) - << "Unclean document at lifecycle " << document->Lifecycle().ToString(); - DCHECK_NE(node, document_) - << "The document's AXObject is backed by its layout object."; - - if (!IsA(node) && node->IsInUserAgentShadowRoot()) { - if (Node* owner_shadow_host = node->OwnerShadowHost()) { - DCHECK(!AXObjectCacheImpl::ShouldCreateAXMenuListFor( - owner_shadow_host->GetLayoutObject())) - << "DOM descendants of an AXMenuList should not be added to the AX " - "hierarchy, except for the AXMenuListOption children added in " - "AXMenuListPopup. An attempt was made to create an AXObject for: " - << node; - } + if (node) { + DCHECK(layout_object || ax_type != kAXLayoutObject); + DCHECK(node->isConnected()); + DCHECK(node->IsElementNode() || node->IsTextNode() || + node->IsDocumentNode()) + << "Should only attempt to create AXObjects for the following types of " + "node types: document, element and text." + << "\n* Node is: " << node; + } else { + // No node, therefore the only possibility is to create an AXLayoutObject. + DCHECK_EQ(ax_type, kAXLayoutObject); + DCHECK(!IsA(layout_object)) + << "AXObject for document is always created with a node."; } #endif - AXObject* parent = parent_if_known - ? parent_if_known - : AXObject::ComputeNonARIAParent(*this, node); - // An AXObject backed only by a DOM node must have a parent, because it's - // never the root, which will always have a layout object. - if (!parent) - return nullptr; - - DCHECK(parent->CanHaveChildren()); - - // One of the above calls could have already created the planned object via a - // recursive call to GetOrCreate(). If so, just return that object. - if (node_object_mapping_.Contains(node)) - return Get(node); + // Determine the parent. + AXObject* parent = nullptr; + if (parent_if_known) { + // Parent is known because the tree is being explored downward, and as the + // parent adds its children it passes itself in. + parent = parent_if_known; + } else if (node == &GetDocument()) { + // The root object does not have a parent. + parent = nullptr; + } else { + // Must compute the parent, which occurs when an AXObject is being created + // in the middle of the tree. + parent = AXObject::ComputeNonARIAParent(*this, node); + if (!parent) { + // An AXObject must have a parent, unless it's the root. + // This because when no parent can be computed, it means that any AXObject + // we would create would not have a path to the root. We do not create + // ophaned AXObjects, so return null. + return nullptr; + } + } - AXObject* new_obj = CreateFromNode(node); + // If there is a DOM node, use its dom_node_id, otherwise, generate an AXID. + // The dom_node_id can be used even if there is also a layout object. + AXID axid = + node ? static_cast(DOMNodeIds::IdForNode(node)) : GenerateAXID(); + DCHECK(objects_.find(axid) == objects_.end()); - // Will crash later if we have two objects for the same node. - DCHECK(!node_object_mapping_.Contains(node)) - << "Already have an AXObject for " << node; + // Create the new AXObject. + AXObject* new_obj = nullptr; + if (ax_type == kAXLayoutObject) { + // Prefer to create from renderer if there is a layout object because + // AXLayoutObjects can provide information about bounding boxes. + DCHECK(!layout_object_mapping_.Contains(layout_object)) + << "Already have an AXObject for " << layout_object; + if (!node) { + layout_object_mapping_.Set(layout_object, axid); + } + new_obj = CreateFromRenderer(layout_object); + } else { + new_obj = CreateFromNode(node); + } + DCHECK(new_obj) << "Could not create AXObject."; - const AXID ax_id = AssociateAXID(new_obj, use_axid); - DCHECK(!HashTraits::IsDeletedValue(ax_id)); - node_object_mapping_.Set(node, ax_id); + // Give the AXObject its ID and initialize. + AssociateAXID(new_obj, axid); new_obj->Init(parent); return new_obj; @@ -1504,101 +1433,8 @@ AXObject* AXObjectCacheImpl::GetOrCreate(LayoutObject* layout_object, if (AXObject* obj = Get(layout_object)) return obj; - return CreateAndInit(layout_object, parent_if_known); -} - -AXObject* AXObjectCacheImpl::CreateAndInit( - LayoutObject* layout_object, - AXObject* parent_if_known, - AXID use_axid, - absl::optional ax_type) { -#if DCHECK_IS_ON() - DCHECK(layout_object); - Document* document = &layout_object->GetDocument(); - DCHECK(document); - DCHECK(document->Lifecycle().GetState() >= - DocumentLifecycle::kAfterPerformLayout) - << "Unclean document at lifecycle " << document->Lifecycle().ToString(); - DCHECK(!parent_if_known || parent_if_known->CanHaveChildren()); -#endif // DCHECK_IS_ON() - Node* node = layout_object->GetNode(); - - if (!ax_type) - ax_type = DetermineAXObjectType(node, layout_object, parent_if_known); - if (*ax_type == kAXNodeObject) - return CreateAndInit(node, parent_if_known, use_axid, kAXNodeObject); - if (*ax_type == kPruneSubtree) - return nullptr; - -#if DCHECK_IS_ON() - if (node && !IsA(node) && - node->IsInUserAgentShadowRoot()) { - if (Node* owner_shadow_host = node->OwnerShadowHost()) { - DCHECK(!AXObjectCacheImpl::ShouldCreateAXMenuListFor( - owner_shadow_host->GetLayoutObject())) - << "DOM descendants of an AXMenuList should not be added to the AX " - "hierarchy, except for the AXMenuListOption children added in " - "AXMenuListPopup. An attempt was made to create an AXObject for: " - << node; - } - } -#endif - - AXObject* parent = parent_if_known - ? parent_if_known - : AXObject::ComputeNonARIAParent(*this, node); - if (node == document_) - DCHECK(!parent); - else if (!parent) - return nullptr; - else - DCHECK(parent->CanHaveChildren()); - - // One of the above calls could have already created the planned object via a - // recursive call to GetOrCreate(). If so, just return that object. - // Example: parent calls Init() => ComputeAccessibilityIsIgnored() => - // CanSetFocusAttribute() => CanBeActiveDescendant() => - // IsARIAControlledByTextboxWithActiveDescendant() => GetOrCreate(). - if (layout_object_mapping_.Contains(layout_object)) { - AXObject* result = Get(layout_object); - DCHECK(result) << "Missing cached AXObject for " << layout_object; - return result; - } - - if (!parent_if_known && - (layout_object->IsText() || layout_object->IsPseudoElement() || !node)) { - // If the parent is not known, it means we are creating an AXObject at an - // arbitrary place in the tree. Ensure its parent has it as a - // child. an thus is connected to the root in both directions, - // and is not an orphan. - // This is accomplished by creating an AXObject for |layout_object|, by - // first asking the parent to create its children, and then returning the - // matching AXObject for |layout_object|. - // This prevents situations where we attempt to serialize a node - // and fail, because the parent does not reach it via its children. - // It is only known to be an issue with AXObjects backed by layout, where a - // change to layout has invalidated the inclusion of something in the tree. - // For now, do this only for text and pseudo content, as it is a smaller - // change, but consider doing it for more/all nodes in the future. - DCHECK(!use_axid) - << "Cannot enforce an AXID when creating in the middle of the tree."; - parent->UpdateChildrenIfNecessary(); - return Get(layout_object); - } - - AXObject* new_obj = CreateFromRenderer(layout_object); - - DCHECK(new_obj) << "Could not create AXObject for " << layout_object; - - // Will crash later if we have two objects for the same layoutObject. - DCHECK(!layout_object_mapping_.Contains(layout_object)) - << "Already have an AXObject for " << layout_object; - - const AXID axid = AssociateAXID(new_obj, use_axid); - layout_object_mapping_.Set(layout_object, axid); - new_obj->Init(parent); - - return new_obj; + return CreateAndInit(layout_object->GetNode(), layout_object, + parent_if_known); } AXObject* AXObjectCacheImpl::GetOrCreate(AbstractInlineTextBox* inline_text_box, @@ -1642,7 +1478,7 @@ AXObject* AXObjectCacheImpl::GetOrCreate(AbstractInlineTextBox* inline_text_box, AXObject* new_obj = CreateFromInlineTextBox(inline_text_box); - const AXID axid = AssociateAXID(new_obj); + AXID axid = AssociateAXID(new_obj); inline_text_box_object_mapping_.Set(inline_text_box, axid); new_obj->Init(parent); @@ -1732,8 +1568,6 @@ void AXObjectCacheImpl::Remove(AXID ax_id) { // and it will still make sure that the object is cleaned up? if (!objects_.Take(ax_id)) return; - - DCHECK_EQ(objects_.size(), ids_in_use_.size()); } // This is safe to call even if there isn't a current mapping. @@ -1751,9 +1585,9 @@ void AXObjectCacheImpl::Remove(AccessibleNode* accessible_node) { } // This is safe to call even if there isn't a current mapping. -bool AXObjectCacheImpl::Remove(LayoutObject* layout_object) { +void AXObjectCacheImpl::Remove(LayoutObject* layout_object) { if (!layout_object) - return false; + return; if (IsA(layout_object)) { // A document is being destroyed. @@ -1767,49 +1601,42 @@ bool AXObjectCacheImpl::Remove(LayoutObject* layout_object) { invalidated_ids_popup_.clear(); } + // If a DOM node is present, it will have been used to back the AXObject, in + // which case we need to call Remove(node) instead. + if (Node* node = layout_object->GetNode()) { + // Pseudo elements are a special case. They need to be marked dirty so that + // their entire subtree is recomputed (it is disappearing or changing). + if (node->IsPseudoElement()) { + DeferTreeUpdate(&AXObjectCacheImpl::EnsureMarkDirtyWithCleanLayout, node); + } + Remove(node); + return; + } + auto iter = layout_object_mapping_.find(layout_object); if (iter == layout_object_mapping_.end()) - return false; + return; AXID ax_id = iter->value; DCHECK(ax_id); layout_object_mapping_.erase(iter); Remove(ax_id); - - return true; } // This is safe to call even if there isn't a current mapping. -void AXObjectCacheImpl::Remove(const Node* node) { - if (!node) - return; - - LayoutObject* layout_object = node->GetLayoutObject(); - - // A layout object will be used whenever it is available and relevant. It's - // the preferred backing object, rather than the DOM node. - if (Remove(node->GetLayoutObject())) { - DCHECK_EQ(node_object_mapping_.find(node), node_object_mapping_.end()) - << "AXObject cannot be backed by both a layout object and node."; - return; - } - - auto iter = node_object_mapping_.find(node); - if (iter != node_object_mapping_.end()) { - DCHECK(!layout_object || layout_object_mapping_.find(layout_object) == - layout_object_mapping_.end()) - << "AXObject cannot be backed by both a layout object and node."; - AXID ax_id = iter->value; - DCHECK(ax_id); - node_object_mapping_.erase(iter); - Remove(ax_id); +void AXObjectCacheImpl::Remove(Node* node) { + DCHECK(node); + AXID axid = static_cast(DOMNodeIds::ExistingIdForNode(node)); + if (axid) { + DCHECK_GE(axid, 1); + Remove(axid); } } void AXObjectCacheImpl::Remove(Document* document) { DCHECK(IsPopup(*document)) << "Call Dispose() to remove the main document."; - for (const Node* node = document; node; + for (Node* node = document; node; node = LayoutTreeBuilderTraversal::Next(*node, nullptr)) { Remove(node); } @@ -1833,15 +1660,29 @@ void AXObjectCacheImpl::Remove(AbstractInlineTextBox* inline_text_box) { Remove(ax_id); } +// All generated AXIDs are negative, ranging from kFirstGeneratedId to INT_MIN, +// in order to avoid conflict with the ids reused from dom_node_ids, which are +// positive, and generated IDs on the browser side, which are negative, starting +// at -1. AXID AXObjectCacheImpl::GenerateAXID() const { - static AXID last_used_id = 0; + // The first id is close to INT_MIN/2, leaving plenty of room for negative + // generated IDs both her and on the browser side, but starting at an even + // number makes it easier to read when debugging. + constexpr int kFirstGeneratedId = -1000000000; + static AXID last_used_id = kFirstGeneratedId; + + // This is very unlikely to happen, but if we find that it happens often, we + // could gracefully turn off a11y instead of crashing the renderer. + CHECK(objects_.size() < kFirstGeneratedId - INT_MIN - 1) + << "Not enough room in map for more accessibility objects."; // Generate a new ID. AXID obj_id = last_used_id; do { - ++obj_id; - } while (!obj_id || HashTraits::IsDeletedValue(obj_id) || - ids_in_use_.Contains(obj_id)); + if (--obj_id == INT_MIN) { + obj_id = kFirstGeneratedId; + } + } while (objects_.Contains(obj_id)); last_used_id = obj_id; @@ -1866,9 +1707,11 @@ AXID AXObjectCacheImpl::AssociateAXID(AXObject* obj, AXID use_axid) { // Check for already-assigned ID. DCHECK(!obj->AXObjectID()) << "Object should not already have an AXID"; - const AXID new_axid = use_axid ? use_axid : GenerateAXID(); + AXID new_axid = use_axid ? use_axid : GenerateAXID(); + + DCHECK_EQ(obj->GetNode() && !obj->IsAXInlineTextBox(), IsDOMNodeID(new_axid)) + << "AXObjects with a DOM node must use a dom_node_id for the AXID."; - ids_in_use_.insert(new_axid); obj->SetAXObjectID(new_axid); objects_.Set(new_axid, obj); @@ -1886,7 +1729,6 @@ void AXObjectCacheImpl::RemoveAXID(AXObject* object) { if (!obj_id) return; DCHECK(!HashTraits::IsDeletedValue(obj_id)); - DCHECK(ids_in_use_.Contains(obj_id)); object->SetAXObjectID(0); // Clear AXIDs from maps. Note: do not need to erase id from // changed_bounds_ids_, a set which is cleared each time @@ -1894,12 +1736,21 @@ void AXObjectCacheImpl::RemoveAXID(AXObject* object) { // invalidated_ids_main_ or invalidated_ids_popup_, which are cleared each // time ProcessInvalidatedObjects() finishes, and having extra ids in those // sets is not harmful. - ids_in_use_.erase(obj_id); - autofill_state_map_.erase(obj_id); - fixed_or_sticky_node_ids_.erase(obj_id); cached_bounding_boxes_.erase(obj_id); - // Clear id from relation cache. - relation_cache_->RemoveAXID(obj_id); + + if (IsDOMNodeID(obj_id)) { + // Optimization: these maps only contain ids for AXObjects with a DOM node. + autofill_state_map_.erase(obj_id); + fixed_or_sticky_node_ids_.erase(obj_id); + // Only objects with a DOM node can be in the relation cache. + relation_cache_->RemoveAXID(obj_id); + // Allow the new AXObject for the same node to be serialized correctly. + nodes_with_pending_children_changed_.erase(obj_id); + } else { + // Non-DOM ids should never find their way into these maps. + DCHECK(!autofill_state_map_.Contains(obj_id)); + DCHECK(!fixed_or_sticky_node_ids_.Contains(obj_id)); + } } AXObject* AXObjectCacheImpl::NearestExistingAncestor(Node* node) { @@ -2100,9 +1951,11 @@ void AXObjectCacheImpl::TextChanged(Node* node) { return; // A text changed event is redundant with children changed on the same node. - if (nodes_with_pending_children_changed_.find(node) != - nodes_with_pending_children_changed_.end()) { - return; + if (AXID node_id = static_cast(DOMNodeIds::ExistingIdForNode(node))) { + if (nodes_with_pending_children_changed_.find(node_id) != + nodes_with_pending_children_changed_.end()) { + return; + } } DeferTreeUpdate(&AXObjectCacheImpl::TextChangedWithCleanLayout, node); @@ -2117,10 +1970,13 @@ void AXObjectCacheImpl::TextChanged(const LayoutObject* layout_object) { // when it has a block sibling. Node* node = GetClosestNodeForLayoutObject(layout_object); if (node) { - // A text changed event is redundant with children changed on the same node. - if (nodes_with_pending_children_changed_.find(node) != - nodes_with_pending_children_changed_.end()) { - return; + if (AXID node_id = static_cast(DOMNodeIds::ExistingIdForNode(node))) { + // A text changed event is redundant with children changed on the same + // node. + if (nodes_with_pending_children_changed_.find(node_id) != + nodes_with_pending_children_changed_.end()) { + return; + } } DeferTreeUpdate(&AXObjectCacheImpl::TextChangedWithCleanLayout, node); @@ -2208,7 +2064,7 @@ void AXObjectCacheImpl::UpdateCacheAfterNodeIsAttached(Node* node) { if (document) { // A popup is being shown. DCHECK(*document != GetDocument()); - DCHECK(!popup_document_); + DCHECK(!popup_document_) << "Last popup was not cleared."; popup_document_ = document; DCHECK(IsPopup(*document)); // Fire children changed on the focused element that owns this popup. @@ -2241,13 +2097,12 @@ void AXObjectCacheImpl::UpdateCacheAfterNodeIsAttachedWithCleanLayout( << "Unclean document at lifecycle " << document->Lifecycle().ToString(); #endif // DCHECK_IS_ON() - // Process any relation attributes that can affect ax objects already created. - // Force computation of aria-owns, so that original parents that already // computed their children get the aria-owned children removed. if (AXObject::HasARIAOwns(element)) HandleAttributeChangedWithCleanLayout(html_names::kAriaOwnsAttr, element); + // Process any relation attributes that can affect ax objects already created. MaybeNewRelationTarget(*node, Get(node)); // Even if the node or parent are ignored, an ancestor may need to include @@ -2255,7 +2110,7 @@ void AXObjectCacheImpl::UpdateCacheAfterNodeIsAttachedWithCleanLayout( // must be called. It handles ignored logic, ensuring that the first ancestor // that should have this as a child will be updated. ChildrenChangedWithCleanLayout( - Get(LayoutTreeBuilderTraversal::Parent(*node))); + GetOrCreate(LayoutTreeBuilderTraversal::Parent(*node))); // If an image map area is added, we need to update children on the image. if (IsA(node)) @@ -2362,7 +2217,8 @@ AXObject* AXObjectCacheImpl::InvalidateChildren(AXObject* obj) { return nullptr; // Don't enqueue a deferred event on the same node more than once. if (ancestor->GetNode() && - !nodes_with_pending_children_changed_.insert(ancestor->GetNode()) + !nodes_with_pending_children_changed_ + .insert(DOMNodeIds::ExistingIdForNode(ancestor->GetNode())) .is_new_entry) { return nullptr; } @@ -2435,23 +2291,6 @@ void AXObjectCacheImpl::ChildrenChanged(AccessibleNode* accessible_node) { } void AXObjectCacheImpl::ChildrenChangedWithCleanLayout(Node* node) { - if (!node) - return; - - LayoutObject* layout_object = node->GetLayoutObject(); - AXID layout_id = 0; - if (layout_object) { - auto it = layout_object_mapping_.find(layout_object); - if (it != layout_object_mapping_.end()) - layout_id = it->value; - } - DCHECK(!HashTraits::IsDeletedValue(layout_id)); - - auto it = node_object_mapping_.find(node); - AXID node_id = it != node_object_mapping_.end() ? it->value : 0; - DCHECK(!HashTraits::IsDeletedValue(node_id)); - DCHECK(!node->GetDocument().NeedsLayoutTreeUpdateForNode(*node)); - ChildrenChangedWithCleanLayout(node, Get(node)); } @@ -2677,17 +2516,7 @@ void AXObjectCacheImpl::ProcessInvalidatedObjects(Document& document) { } AXID retained_axid = current->AXObjectID(); - // Remove from relevant maps, but not from relation cache, as the relations - // between AXIDs will still be the same. - node_object_mapping_.erase(node); - if (is_ax_layout_object) { - layout_object_mapping_.erase(current->GetLayoutObject()); - } else { - DCHECK(will_be_ax_layout_object); - DCHECK(node->GetLayoutObject()); - DCHECK(!layout_object_mapping_.Contains(node->GetLayoutObject())) - << node << " " << node->GetLayoutObject(); - } + DCHECK_EQ(retained_axid, DOMNodeIds::ExistingIdForNode(node)); ChildrenChangedOnAncestorOf(current); current->Detach(); @@ -2700,8 +2529,9 @@ void AXObjectCacheImpl::ProcessInvalidatedObjects(Document& document) { // TODO(accessibility) That may be the only example of this, in which case // it could be handled in RoleChangedWithCleanLayout(), and the cached // parent could be used. - AXObject* new_object = CreateAndInit( - node, AXObject::ComputeNonARIAParent(*this, node), retained_axid); + AXObject* new_object = + CreateAndInit(node, node->GetLayoutObject(), + AXObject::ComputeNonARIAParent(*this, node)); if (new_object) { // Any owned objects need to reset their parent_ to point to the // new object. @@ -2709,6 +2539,7 @@ void AXObjectCacheImpl::ProcessInvalidatedObjects(Document& document) { AXRelationCache::IsValidOwner(new_object)) { relation_cache_->UpdateAriaOwnsWithCleanLayout(new_object, true); } + DCHECK_EQ(new_object->AXObjectID(), retained_axid); } else { // Failed to create, so remove object completely. RemoveAXID(current); @@ -3263,8 +3094,13 @@ void AXObjectCacheImpl::HandleRoleChangeWithCleanLayout(Node* node) { // because some roles allow aria-owns and others don't. // In addition, any owned objects need to reset their parent_ to point // to the new object. - if (AXObject* new_object = GetOrCreate(node)) + if (AXObject* new_object = GetOrCreate(node)) { relation_cache_->UpdateAriaOwnsWithCleanLayout(new_object, true); + // Need to mark dirty because the dom_node_id-based ID remains the same, + // and therefore the serializer may not automatically serialize this node + // from the children changed on the parent. + MarkAXObjectDirtyWithCleanLayout(new_object); + } } } @@ -3984,9 +3820,11 @@ bool AXObjectCacheImpl::SerializeEntireTree(bool exclude_offscreen, tree_source->Freeze(); if (!tree_source->GetRoot() || tree_source->GetRoot()->IsDetached()) { - tree_source->Thaw(); // TODO(chrishtr): not clear why this can happen. - NOTREACHED(); + DCHECK(tree_source->GetRoot()); + DCHECK(!tree_source->GetRoot()->IsDetached()) + << tree_source->GetRoot()->ToString(true); + tree_source->Thaw(); return false; } @@ -4098,15 +3936,15 @@ void AXObjectCacheImpl::SerializeDirtyObjectsAndEvents( update.has_tree_data = true; if (!SerializeChanges(*obj, &update)) { - VLOG(1) << "Failed to serialize one accessibility event."; + VLOG(1) << "Failed to serialize a dirty object: " << obj->ToString(true); continue; } DCHECK_GT(update.nodes.size(), 0U); - for (auto& node : update.nodes) { - DCHECK(node.id); - already_serialized_ids.insert(node.id); + for (auto& node_data : update.nodes) { + DCHECK(node_data.id); + already_serialized_ids.insert(node_data.id); } DCHECK(already_serialized_ids.Contains(obj->AXObjectID())) @@ -4262,10 +4100,14 @@ void AXObjectCacheImpl::HandleTextMarkerDataAddedWithCleanLayout(Node* node) { !marker_controller.MarkersFor(*text_node, spelling_and_grammar_markers) .empty(); if (has_spelling_or_grammar_markers) { - if (nodes_with_spelling_or_grammar_markers_.insert(node).is_new_entry) + if (nodes_with_spelling_or_grammar_markers_ + .insert(DOMNodeIds::IdForNode(node)) + .is_new_entry) { ChildrenChangedWithCleanLayout(node); + } } else { - const auto& iter = nodes_with_spelling_or_grammar_markers_.find(node); + const auto& iter = nodes_with_spelling_or_grammar_markers_.find( + DOMNodeIds::IdForNode(node)); if (iter != nodes_with_spelling_or_grammar_markers_.end()) { nodes_with_spelling_or_grammar_markers_.erase(iter); ChildrenChangedWithCleanLayout(node); @@ -4560,7 +4402,6 @@ void AXObjectCacheImpl::Trace(Visitor* visitor) const { visitor->Trace(last_selected_from_active_descendant_); visitor->Trace(accessible_node_mapping_); visitor->Trace(layout_object_mapping_); - visitor->Trace(node_object_mapping_); visitor->Trace(active_aria_modal_dialog_); visitor->Trace(objects_); @@ -4570,8 +4411,6 @@ void AXObjectCacheImpl::Trace(Visitor* visitor) const { visitor->Trace(permission_observer_receiver_); visitor->Trace(tree_update_callback_queue_main_); visitor->Trace(tree_update_callback_queue_popup_); - visitor->Trace(nodes_with_pending_children_changed_); - visitor->Trace(nodes_with_spelling_or_grammar_markers_); visitor->Trace(ax_tree_source_); visitor->Trace(dirty_objects_); visitor->Trace(aria_notifications_); diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h index bd87a2ad8389c..e56895e9fca2c 100644 --- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h +++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h @@ -151,9 +151,8 @@ class MODULES_EXPORT AXObjectCacheImpl void ImageLoaded(const LayoutObject*) override; void Remove(AccessibleNode*) override; - // Returns false if no associated AXObject exists in the cache. - bool Remove(LayoutObject*) override; - void Remove(const Node*) override; + void Remove(LayoutObject*) override; + void Remove(Node*) override; void Remove(Document*) override; void Remove(AbstractInlineTextBox*) override; void Remove(AXObject*); // Calls more specific Remove methods as necessary. @@ -284,7 +283,8 @@ class MODULES_EXPORT AXObjectCacheImpl // simply writing a DCHECK, where a pure get is optimal so as to avoid // changing behavior. AXObject* SafeGet(const Node* node, - bool allow_display_locking_invalidation = false); + bool allow_display_locking_invalidation = false, + bool allow_layout_object_relevance_check = false); // Return true if the object is still part of the tree, meaning that ancestors // exist or can be repaired all the way to the root. @@ -519,32 +519,15 @@ class MODULES_EXPORT AXObjectCacheImpl return active_event_intents_; } - // Create an AXObject, and do not check if a previous one exists. - // Also, initialize the object and add it to maps for later retrieval. - AXObject* CreateAndInit( - Node*, - AXObject* parent_if_known, - AXID use_axid = 0, - absl::optional ax_object_type = absl::nullopt); - AXObject* CreateAndInit( - LayoutObject*, - AXObject* parent_if_known, - AXID use_axid = 0, - absl::optional ax_object_type = absl::nullopt); - // Mark object as invalid and needing to be refreshed when layout is clean. // Will result in a new object with the same AXID, and will also call // ChildrenChanged() on the parent of invalidated objects. Automatically // de-dupes extra object refreshes and ChildrenChanged() calls. void Invalidate(Document&, AXID); - AXObject* CreateFromRenderer(LayoutObject*); - AXObject* CreateFromNode(Node*); - AXObject* CreateFromInlineTextBox(AbstractInlineTextBox*); void Remove(AXID); private: - struct AXDirtyObject : public GarbageCollected { AXDirtyObject(AXObject* obj_arg, ax::mojom::blink::EventFrom event_from_arg, @@ -571,6 +554,15 @@ class MODULES_EXPORT AXObjectCacheImpl std::vector event_intents; }; + // Create an AXObject, and do not check if a previous one exists. + // Also, initialize the object and add it to maps for later retrieval. + AXObject* CreateAndInit(Node*, LayoutObject*, AXObject* parent_if_known); + // Helpers for CreateAndInitIfRelevant() methods.. + AXObject* CreateFromRenderer(LayoutObject*); + AXObject* CreateFromNode(Node*); + + AXObject* CreateFromInlineTextBox(AbstractInlineTextBox*); + mojo::Remote& GetOrCreateRemoteRenderAccessibilityHost(); WebLocalFrameClient* GetWebLocalFrameClient() const; @@ -580,6 +572,10 @@ class MODULES_EXPORT AXObjectCacheImpl bool IsMainDocumentDirty() const; bool IsPopupDocumentDirty() const; + // Returns true if the AXID is for a DOM node. + // All other AXIDs are generated. + bool IsDOMNodeID(AXID axid) { return axid > 0; } + HeapHashSet> agents_; struct AXEventParams final : public GarbageCollected { @@ -658,17 +654,16 @@ class MODULES_EXPORT AXObjectCacheImpl Member popup_document_; ui::AXMode ax_mode_; + // AXIDs for AXNodeObjects reuse the int ids in dom_node_id, all other AXIDs + // are negative in order to avoid a conflict. HeapHashMap> objects_; // LayoutObject and AbstractInlineTextBox are not on the Oilpan heap so we // do not use HeapHashMap for those mappings. HeapHashMap, AXID> accessible_node_mapping_; HeapHashMap, AXID> layout_object_mapping_; - HeapHashMap, AXID> node_object_mapping_; HashMap inline_text_box_object_mapping_; int modification_count_; - HashSet ids_in_use_; - // Used for a mock AXObject representing the message displayed in the // validation message bubble. // There can be only one of these per document with invalid form controls, @@ -771,6 +766,7 @@ class MODULES_EXPORT AXObjectCacheImpl void TextChangedWithCleanLayout(Node* node); void ChildrenChangedWithCleanLayout(Node* node); + // If the presence of document markers changed for the given text node, then // call children changed. void HandleTextMarkerDataAddedWithCleanLayout(Node*); @@ -849,11 +845,11 @@ class MODULES_EXPORT AXObjectCacheImpl TreeUpdateCallbackQueue tree_update_callback_queue_popup_; // Help de-dupe processing of repetitive events. - HeapHashSet> nodes_with_pending_children_changed_; + HashSet nodes_with_pending_children_changed_; HashSet nodes_with_pending_location_changed_; // Nodes with document markers that have received accessibility updates. - HeapHashSet> nodes_with_spelling_or_grammar_markers_; + HashSet nodes_with_spelling_or_grammar_markers_; // True when layout has changed, and changed locations must be serialized. bool need_to_send_location_changes_ = false; diff --git a/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc b/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc index 0229224de7f54..d9e393494cbd5 100644 --- a/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc +++ b/third_party/blink/renderer/modules/accessibility/inspector_accessibility_agent.cc @@ -919,7 +919,7 @@ protocol::Response InspectorAccessibilityAgent::getChildAXNodes( auto& cache = AttachToAXObjectCache(document); - AXID ax_id = in_id.ToUInt(); + AXID ax_id = in_id.ToInt(); AXObject* ax_object = cache.ObjectFromAXID(ax_id); if (!ax_object || ax_object->IsDetached()) diff --git a/third_party/blink/renderer/modules/exported/web_ax_context.cc b/third_party/blink/renderer/modules/exported/web_ax_context.cc index 140e825a6333a..2f71befa69ca7 100644 --- a/third_party/blink/renderer/modules/exported/web_ax_context.cc +++ b/third_party/blink/renderer/modules/exported/web_ax_context.cc @@ -18,7 +18,12 @@ WebAXContext::WebAXContext(WebDocument root_document, const ui::AXMode& mode) WebAXContext::~WebAXContext() {} +bool WebAXContext::HasActiveDocument() const { + return private_->HasActiveDocument(); +} + const ui::AXMode& WebAXContext::GetAXMode() const { + DCHECK(!private_->GetAXMode().is_mode_off()); return private_->GetAXMode(); } @@ -27,38 +32,42 @@ void WebAXContext::SetAXMode(const ui::AXMode& mode) const { } void WebAXContext::ResetSerializer() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().ResetSerializer(); } int WebAXContext::GenerateAXID() const { - if (!private_->HasActiveDocument()) - return -1; + DCHECK(HasActiveDocument()); return private_->GetAXObjectCache().GenerateAXID(); } void WebAXContext::SerializeLocationChanges() const { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().SerializeLocationChanges(); } WebAXObject WebAXContext::GetPluginRoot() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return WebAXObject(); + } return WebAXObject(private_->GetAXObjectCache().GetPluginRoot()); } void WebAXContext::Freeze() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().Freeze(); } void WebAXContext::Thaw() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().Thaw(); } @@ -66,8 +75,9 @@ bool WebAXContext::SerializeEntireTree(bool exclude_offscreen, size_t max_node_count, base::TimeDelta timeout, ui::AXTreeUpdate* response) { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return false; + } if (!private_->GetDocument()->ExistingAXObjectCache()) { // TODO(chrishtr): not clear why this can happen. NOTREACHED(); @@ -79,8 +89,9 @@ bool WebAXContext::SerializeEntireTree(bool exclude_offscreen, } void WebAXContext::MarkAllImageAXObjectsDirty() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().MarkAllImageAXObjectsDirty(); } @@ -91,42 +102,48 @@ void WebAXContext::SerializeDirtyObjectsAndEvents( bool& had_end_of_test_event, bool& had_load_complete_messages, bool& need_to_send_location_changes) { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().SerializeDirtyObjectsAndEvents( has_plugin_tree_source, updates, events, had_end_of_test_event, had_load_complete_messages, need_to_send_location_changes); } void WebAXContext::ClearDirtyObjectsAndPendingEvents() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } private_->GetAXObjectCache().ClearDirtyObjectsAndPendingEvents(); } bool WebAXContext::HasDirtyObjects() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return true; + } return private_->GetAXObjectCache().HasDirtyObjects(); } bool WebAXContext::AddPendingEvent(const ui::AXEvent& event, bool insert_at_beginning) { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return true; + } return private_->GetAXObjectCache().AddPendingEvent(event, insert_at_beginning); } void WebAXContext::UpdateAXForAllDocuments() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } return private_->GetAXObjectCache().UpdateAXForAllDocuments(); } void WebAXContext::ScheduleAXUpdate() { - if (!private_->HasActiveDocument()) + if (!HasActiveDocument()) { return; + } const auto& cache = private_->GetAXObjectCache(); diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng index 143b188c4b97d..625ad899b0525 100644 --- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng +++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng @@ -13,7 +13,6 @@ # Tests that fail in legacy but pass in NG # ====== New tests from wpt-importer added here ====== -crbug.com/626703 virtual/force-renderer-accessibility/external/wpt/accessibility/crashtests/bdo-table-cell.html [ Crash ] crbug.com/626703 external/wpt/css/selectors/invalidation/media-pseudo-classes-in-has.html [ Timeout ] crbug.com/626703 external/wpt/fullscreen/api/document-exit-fullscreen-nested-in-iframe.html [ Timeout ] crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/replaced-element-039.html [ Failure ] @@ -118,6 +117,10 @@ crbug.com/1159730 accessibility/inline-text-box-next-on-line.html [ Failure ] crbug.com/591099 accessibility/listitem-presentation-inherited.html [ Failure ] crbug.com/591099 accessibility/presentation-owned-elements.html [ Failure ] crbug.com/591099 accessibility/role-attribute.html [ Failure ] +# Broken by CL:4027071: Stable ids for AXObjects with DOM nodes. +# However, these edges case only crash in the old layout code, so leaving as expectations: +accessibility/details-summary-crash.html [ Crash ] +virtual/force-renderer-accessibility/external/wpt/accessibility/crashtests/bdo-table-cell.html [ Crash ] ### editing/pasteboard/ crbug.com/591099 editing/pasteboard/mathml-sanitizer-bypass.html [ Failure ] diff --git a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html index e7f937ef9588d..dfa6c57dd327e 100644 --- a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html +++ b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html @@ -51,13 +51,12 @@ assert_equals(button2CAXNode.name, "axButton"); assert_equals(button2CAXNode.role, "button"); - // As button1 has no node in the accessibility tree anymore, assert that the - // its previously retrieved computed accessible node has had its attributes - // nullified. + // As button1 still has a node in the accessibility tree, but its layout has + // been removed,and therefore the name is now null. assert_equals(button1CAXNode.name, null); - assert_equals(button1CAXNode.role, null); + assert_equals(button1CAXNode.role, "button"); -}, "Deleting nodes from the accessibility tree will not cause a crash, and properties on any references to a deleted computed accessible node have been nullified."); +}, "Deleting layout from the accessibility tree will not cause a crash."); diff --git a/third_party/blink/web_tests/accessibility/canvas-select-row.html b/third_party/blink/web_tests/accessibility/canvas-select-row.html index 5b470e0a831f0..ad541159134aa 100644 --- a/third_party/blink/web_tests/accessibility/canvas-select-row.html +++ b/third_party/blink/web_tests/accessibility/canvas-select-row.html @@ -22,6 +22,7 @@ cell.role = "gridcell"; row.accessibleNode.appendChild(cell); var ax = axElementById("row0"); + assert_equals(ax.role, "AXRole: AXRow"); var axChild = ax.childAtIndex(0); assert_equals(axChild.role, "AXRole: AXCell"); }, "An ARIA cell without a layout object does not crash"); diff --git a/third_party/blink/web_tests/accessibility/notification-listeners.html b/third_party/blink/web_tests/accessibility/notification-listeners.html index e1801bfbe95df..533b96d8db572 100644 --- a/third_party/blink/web_tests/accessibility/notification-listeners.html +++ b/third_party/blink/web_tests/accessibility/notification-listeners.html @@ -7,6 +7,12 @@