Commit d1401c9c authored by Mark Schillaci's avatar Mark Schillaci Committed by Chromium LUCI CQ

Android a11y WINDOW_CONTENT_CHANGED event suppression tests

This CL is an follow-up to: crrev.com/c/2552897

In this CL we add on to the event suppression logic for events
of TYPE_WINDOW_CONTENT_CHANGED type on Android.

With this CL we move the logic into web_contents_accessibility_android,
and we add unit tests. We include a VisibleForTesting method in
WebContentsAccessibilityImpl that allows us to change the
suppression threshold as needed.

AX-Relnotes: N/A
Change-Id: Iba5f332ddc4d0a9889f188d7f2a9583bec144ae0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575422Reviewed-by: default avatarMark Schillaci <mschillaci@google.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Mark Schillaci <mschillaci@google.com>
Cr-Commit-Position: refs/heads/master@{#838942}
parent 846a2db5
......@@ -15,12 +15,6 @@
namespace content {
namespace {
// The maximum number of TYPE_WINDOW_CONTENT_CHANGED events to fire in one
// atomic update before we give up and fire it on the root node instead.
constexpr int kMaxContentChangedEventsToFire = 5;
} // namespace
// static
BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
const ui::AXTreeUpdate& initial_tree,
......@@ -201,27 +195,9 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent(
// Always send AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED to notify
// the Android system that the accessibility hierarchy rooted at this
// node has changed. However, if there are a large number of changes
// it's too expensive to fire all of them, so we just fire one
// on the root instead.
if (event_type != ui::AXEventGenerator::Event::SUBTREE_CREATED) {
content_changed_events_++;
if (content_changed_events_ < kMaxContentChangedEventsToFire) {
// If it's less than the max event count, fire the event on the specific
// node that changed.
wcax->HandleContentChanged(android_node->unique_id());
} else if (content_changed_events_ == kMaxContentChangedEventsToFire) {
// If it's equal to the max event count, fire the event on the
// root instead.
BrowserAccessibilityManager* root_manager = GetRootManager();
if (root_manager) {
auto* root_node =
static_cast<BrowserAccessibilityAndroid*>(root_manager->GetRoot());
if (root_node)
wcax->HandleContentChanged(root_node->unique_id());
}
}
}
// node has changed.
if (event_type != ui::AXEventGenerator::Event::SUBTREE_CREATED)
wcax->HandleContentChanged(android_node->unique_id());
switch (event_type) {
case ui::AXEventGenerator::Event::ALERT: {
......@@ -529,16 +505,17 @@ void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
ui::AXTree* tree,
bool root_changed,
const std::vector<ui::AXTreeObserver::Change>& changes) {
// Reset this every time we get an atomic update.
content_changed_events_ = 0;
BrowserAccessibilityManager::OnAtomicUpdateFinished(tree, root_changed,
changes);
WebContentsAccessibilityAndroid* wcax = GetWebContentsAXFromRootManager();
if (!wcax)
return;
// Reset content changed events counter every time we finish an atomic update.
wcax->ResetContentChangedEventsCounter();
if (root_changed) {
WebContentsAccessibilityAndroid* wcax = GetWebContentsAXFromRootManager();
if (!wcax)
return;
wcax->HandleNavigate();
}
}
......
......@@ -134,10 +134,6 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
// See docs for set_prune_tree_for_screen_reader, above.
bool prune_tree_for_screen_reader_;
// A count of the number of TYPE_WINDOW_CONTENT_CHANGED events we've
// fired during a single atomic update.
int content_changed_events_ = 0;
DISALLOW_COPY_AND_ASSIGN(BrowserAccessibilityManagerAndroid);
};
......
......@@ -446,7 +446,27 @@ void WebContentsAccessibilityAndroid::HandleContentChanged(int32_t unique_id) {
ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
if (obj.is_null())
return;
Java_WebContentsAccessibilityImpl_handleContentChanged(env, obj, unique_id);
// If there are a large number of changes it's too expensive to fire all of
// them, so we just fire one on the root instead.
content_changed_events_++;
if (content_changed_events_ < max_content_changed_events_to_fire_) {
// If it's less than the max event count, fire the event on the specific
// node that changed.
Java_WebContentsAccessibilityImpl_handleContentChanged(env, obj, unique_id);
} else if (content_changed_events_ == max_content_changed_events_to_fire_) {
// If it's equal to the max event count, fire the event on the
// root instead.
auto* root_manager = GetRootBrowserAccessibilityManager();
if (root_manager) {
auto* root_node =
static_cast<BrowserAccessibilityAndroid*>(root_manager->GetRoot());
if (root_node) {
Java_WebContentsAccessibilityImpl_handleContentChanged(
env, obj, root_node->unique_id());
}
}
}
}
void WebContentsAccessibilityAndroid::HandleFocusChanged(int32_t unique_id) {
......
......@@ -17,6 +17,12 @@ class MotionEventAndroid;
namespace content {
namespace {
// The maximum number of TYPE_WINDOW_CONTENT_CHANGED events to fire in one
// atomic update before we give up and fire it on the root node instead.
constexpr int kMaxContentChangedEventsToFire = 5;
} // namespace
class BrowserAccessibilityAndroid;
class BrowserAccessibilityManagerAndroid;
class WebContents;
......@@ -248,6 +254,27 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
void UpdateFrameInfo(float page_scale);
// Set a new max for TYPE_WINDOW_CONTENT_CHANGED events to fire.
void SetMaxContentChangedEventsToFireForTesting(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jint maxEvents) {
// Consider a new |maxEvents| value of -1 to mean to reset to the default.
if (maxEvents == -1) {
max_content_changed_events_to_fire_ = kMaxContentChangedEventsToFire;
} else {
max_content_changed_events_to_fire_ = maxEvents;
}
}
// Get the current max for TYPE_WINDOW_CONTENT_CHANGED events to fire.
jint GetMaxContentChangedEventsToFireForTesting(JNIEnv* env) {
return max_content_changed_events_to_fire_;
}
// Reset count of content changed events fired this atomic update.
void ResetContentChangedEventsCounter() { content_changed_events_ = 0; }
// --------------------------------------------------------------------------
// Methods called from the BrowserAccessibilityManager
// --------------------------------------------------------------------------
......@@ -297,6 +324,13 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
bool use_zoom_for_dsf_enabled_;
// Current max number of events to fire, mockable for unit tests
int max_content_changed_events_to_fire_ = kMaxContentChangedEventsToFire;
// A count of the number of TYPE_WINDOW_CONTENT_CHANGED events we've
// fired during a single atomic update.
int content_changed_events_ = 0;
// Manages the connection between web contents and the RenderFrameHost that
// receives accessibility events.
// Owns itself, and destroyed upon WebContentsObserver::WebContentsDestroyed.
......
......@@ -338,6 +338,18 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
WebContentsAccessibilityImpl.this, virtualViewId, startOffset, endOffset);
}
@VisibleForTesting
public void setMaxContentChangedEventsToFireForTesting(int maxEvents) {
WebContentsAccessibilityImplJni.get().setMaxContentChangedEventsToFireForTesting(
mNativeObj, WebContentsAccessibilityImpl.this, maxEvents);
}
@VisibleForTesting
public int getMaxContentChangedEventsToFireForTesting() {
return WebContentsAccessibilityImplJni.get().getMaxContentChangedEventsToFireForTesting(
mNativeObj);
}
// WindowEventObserver
@Override
......@@ -2010,5 +2022,8 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
WebContentsAccessibilityImpl caller, int id);
void addSpellingErrorForTesting(long nativeWebContentsAccessibilityAndroid,
WebContentsAccessibilityImpl caller, int id, int startOffset, int endOffset);
void setMaxContentChangedEventsToFireForTesting(long nativeWebContentsAccessibilityAndroid,
WebContentsAccessibilityImpl caller, int maxEvents);
int getMaxContentChangedEventsToFireForTesting(long nativeWebContentsAccessibilityAndroid);
}
}
<!-- Simple file to generate many TYPE_WINDOW_CONTENT_CHANGED events. -->
<html>
<head>
<script>
const SCRIPT_COUNT = 250;
var elementArray = [];
function expandComboboxes() {
elementArray.forEach(node => {
node.setAttribute("aria-expanded", true);
node.getElementsByTagName('div')[0].removeAttribute('hidden');
});
}
function createComboboxes() {
var body = document.getElementsByTagName('body')[0];
for (i = 0; i < SCRIPT_COUNT; i++) {
var containingDiv = document.createElement('div');
var combobox = document.createElement('div');
combobox.setAttribute("role", "combobox");
combobox.setAttribute("aria-expanded", "false");
var input = document.createElement('input');
input.setAttribute("type", "text");
var childDiv = document.createElement('div');
childDiv.setAttribute("hidden", "");
var paragraph = document.createElement('p');
paragraph.innerHTML = "Example Text";
childDiv.appendChild(paragraph);
combobox.appendChild(input);
combobox.appendChild(childDiv);
containingDiv.appendChild(combobox);
body.appendChild(containingDiv);
elementArray[i] = combobox;
}
}
</script>
</head>
<body onload="createComboboxes()">
<button onclick="expandComboboxes()">Expand All</button>
</body>
</html>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment