From 408b51a92e5ccbaf4d6da2562651893186834e4c Mon Sep 17 00:00:00 2001
From: Aaron Leventhal <aleventhal@google.com>
Date: Mon, 5 Feb 2024 18:37:11 +0000
Subject: [PATCH] [A11y] Invalidate AXIDs when those objects are removed

After CL:4185636, an AXObject may have been detached and recreated
with the same DOM node, and therefore has the same id (the dom node id).
This breaks the accessibility object caching mechanism used for web
tests, which assumes that if it sees the same ID, it can assume the
object is still the same one. The fix is to notify the caching
mechanism of detached AXObjects so that it can invalidate the
corresponding entries.

Bug: none
Change-Id: I152538644f9dc24d4e7fbfe68057deb4ab4d34aa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5262560
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Auto-Submit: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1256301}
---
 .../web_test/renderer/accessibility_controller.cc    |  6 ++++++
 content/web_test/renderer/accessibility_controller.h |  2 ++
 content/web_test/renderer/web_ax_object_proxy.h      |  1 +
 content/web_test/renderer/web_frame_test_proxy.cc    |  4 ++++
 content/web_test/renderer/web_frame_test_proxy.h     |  1 +
 .../blink/public/web/web_local_frame_client.h        |  5 +++++
 .../modules/accessibility/ax_object_cache_impl.cc    | 12 ++++++++++--
 7 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/content/web_test/renderer/accessibility_controller.cc b/content/web_test/renderer/accessibility_controller.cc
index d7b011dd07fd9..e73fc949caf47 100644
--- a/content/web_test/renderer/accessibility_controller.cc
+++ b/content/web_test/renderer/accessibility_controller.cc
@@ -343,4 +343,10 @@ blink::WebAXObject AccessibilityController::GetAccessibilityObjectForMainFrame()
       web_view()->MainFrame()->ToWebLocalFrame()->GetDocument());
 }
 
+void AccessibilityController::Remove(unsigned axid) {
+  if (IsInstalled()) {
+    elements_->Remove(axid);
+  }
+}
+
 }  // namespace content
diff --git a/content/web_test/renderer/accessibility_controller.h b/content/web_test/renderer/accessibility_controller.h
index 8433ea7dbcc57..b5ce3ddb3668b 100644
--- a/content/web_test/renderer/accessibility_controller.h
+++ b/content/web_test/renderer/accessibility_controller.h
@@ -51,6 +51,8 @@ class AccessibilityController {
                         const std::string& notification_name,
                         const std::vector<ui::AXEventIntent>& event_intents);
 
+  void Remove(unsigned axid);
+
  private:
   friend class AccessibilityControllerBindings;
 
diff --git a/content/web_test/renderer/web_ax_object_proxy.h b/content/web_test/renderer/web_ax_object_proxy.h
index 0c9b1ed3c6270..97f5889a717fa 100644
--- a/content/web_test/renderer/web_ax_object_proxy.h
+++ b/content/web_test/renderer/web_ax_object_proxy.h
@@ -267,6 +267,7 @@ class WebAXObjectProxyList : public WebAXObjectProxy::Factory {
   ~WebAXObjectProxyList() override;
 
   void Clear();
+  void Remove(unsigned axid) { ax_objects_.erase(axid); }
   v8::Local<v8::Object> GetOrCreate(const blink::WebAXObject&) override;
   blink::WebAXContext* GetAXContext() override;
 
diff --git a/content/web_test/renderer/web_frame_test_proxy.cc b/content/web_test/renderer/web_frame_test_proxy.cc
index 95f25fe629a4a..ac7dd5356af30 100644
--- a/content/web_test/renderer/web_frame_test_proxy.cc
+++ b/content/web_test/renderer/web_frame_test_proxy.cc
@@ -561,6 +561,10 @@ void WebFrameTestProxy::PostAccessibilityEvent(const ui::AXEvent& event) {
   RenderFrameImpl::PostAccessibilityEvent(event);
 }
 
+void WebFrameTestProxy::HandleAXObjectDetachedForTest(unsigned axid) {
+  accessibility_controller_.Remove(axid);
+}
+
 void WebFrameTestProxy::HandleWebAccessibilityEventForTest(
     const blink::WebAXObject& object,
     const char* event_name,
diff --git a/content/web_test/renderer/web_frame_test_proxy.h b/content/web_test/renderer/web_frame_test_proxy.h
index fda17ec1f51d0..91506d3091f71 100644
--- a/content/web_test/renderer/web_frame_test_proxy.h
+++ b/content/web_test/renderer/web_frame_test_proxy.h
@@ -89,6 +89,7 @@ class WebFrameTestProxy : public RenderFrameImpl,
       bool should_reset_browser_interface_broker,
       const blink::ParsedPermissionsPolicy& permissions_policy_header,
       const blink::DocumentPolicyFeatureState& document_policy_header) override;
+  void HandleAXObjectDetachedForTest(unsigned axid) override;
   void HandleWebAccessibilityEventForTest(
       const blink::WebAXObject& object,
       const char* event_name,
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index 763db453aed44..91e7590e8c04c 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -818,6 +818,11 @@ class BLINK_EXPORT WebLocalFrameClient {
   // a window.print() call.
   virtual void ScriptedPrint() {}
 
+  // This method is ONLY for web tests and is not supposed to be overridden in
+  // classes other than web_frame_test_proxy. It's called from accessibility and
+  // is used as a way to notify that an accessibility object has been destroyed.
+  virtual void HandleAXObjectDetachedForTest(unsigned axid) {}
+
   // This method is ONLY for web tests and is not supposed to be overridden in
   // classes other than web_frame_test_proxy. It's called from accessibility and
   // is used as a way to tunnel events to the accessibility_controller in web
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 09f936a123c36..fdcecfec8cd05 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
@@ -1775,8 +1775,16 @@ void AXObjectCacheImpl::Remove(AXID ax_id, bool notify_parent) {
   }
 #endif
 
-  if (notify_parent && !has_been_disposed_) {
-    ChildrenChangedOnAncestorOf(obj);
+  if (!has_been_disposed_) {
+    if (notify_parent) {
+      ChildrenChangedOnAncestorOf(obj);
+    }
+    // TODO(aleventhal) This is for web tests only, in order to record MarkDirty
+    // events. Is there a way to avoid these calls for normal browsing?
+    // Maybe we should use dependency injection from AccessibilityController.
+    if (auto* client = GetWebLocalFrameClient()) {
+      client->HandleAXObjectDetachedForTest(ax_id);
+    }
   }
 
   obj->Detach();