Commit 2863d36c authored by Mark Schillaci's avatar Mark Schillaci Committed by Commit Bot

AccessibilityNodeInfo caching for Android performance improvement

This CL expands on our work of improving the performance of
accessibility features on Android.

Here we create a SparseArray<AccessibilityNodeInfo> to store
a mapping of view ids to their corresponding
AccessibilityNodeInfo objects. When the system asks us to create
a new object for that view, we leverage the object stored
in our array (cache) rather than constructing one from scratch.

There are helper methods so the native code can drive clearing
the cache at appropriate times. We are clearing the cache on a
given node during OnDataChanged or during OnNodeWillBeDeleted.

Change-Id: Iaf817886b8d7038d268d4b459d135c46f60cbfff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2080857
Commit-Queue: Mark Schillaci <mschillaci@google.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747861}
parent 2a877987
...@@ -1846,6 +1846,10 @@ void BrowserAccessibilityAndroid::OnDataChanged() { ...@@ -1846,6 +1846,10 @@ void BrowserAccessibilityAndroid::OnDataChanged() {
new_value_ = value; new_value_ = value;
} }
} }
auto* manager =
static_cast<BrowserAccessibilityManagerAndroid*>(this->manager());
manager->ClearNodeInfoCacheForGivenId(unique_id());
} }
int BrowserAccessibilityAndroid::CountChildrenWithRole( int BrowserAccessibilityAndroid::CountChildrenWithRole(
......
...@@ -365,6 +365,15 @@ bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity( ...@@ -365,6 +365,15 @@ bool BrowserAccessibilityManagerAndroid::PreviousAtGranularity(
return true; return true;
} }
void BrowserAccessibilityManagerAndroid::ClearNodeInfoCacheForGivenId(
int32_t unique_id) {
WebContentsAccessibilityAndroid* wcax = GetWebContentsAXFromRootManager();
if (!wcax)
return;
wcax->ClearNodeInfoCacheForGivenId(unique_id);
}
bool BrowserAccessibilityManagerAndroid::OnHoverEvent( bool BrowserAccessibilityManagerAndroid::OnHoverEvent(
const ui::MotionEventAndroid& event) { const ui::MotionEventAndroid& event) {
WebContentsAccessibilityAndroid* wcax = GetWebContentsAXFromRootManager(); WebContentsAccessibilityAndroid* wcax = GetWebContentsAXFromRootManager();
...@@ -414,6 +423,17 @@ gfx::Rect BrowserAccessibilityManagerAndroid::GetViewBounds() { ...@@ -414,6 +423,17 @@ gfx::Rect BrowserAccessibilityManagerAndroid::GetViewBounds() {
return gfx::Rect(); return gfx::Rect();
} }
void BrowserAccessibilityManagerAndroid::OnNodeWillBeDeleted(ui::AXTree* tree,
ui::AXNode* node) {
BrowserAccessibility* wrapper = GetFromAXNode(node);
BrowserAccessibilityAndroid* android_node =
static_cast<BrowserAccessibilityAndroid*>(wrapper);
ClearNodeInfoCacheForGivenId(android_node->unique_id());
BrowserAccessibilityManager::OnNodeWillBeDeleted(tree, node);
}
void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished( void BrowserAccessibilityManagerAndroid::OnAtomicUpdateFinished(
ui::AXTree* tree, ui::AXTree* tree,
bool root_changed, bool root_changed,
......
...@@ -103,6 +103,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid ...@@ -103,6 +103,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
int32_t* start_index, int32_t* start_index,
int32_t* end_index); int32_t* end_index);
// Helper method to clear AccessibilityNodeInfo cache on given node
void ClearNodeInfoCacheForGivenId(int32_t unique_id);
private: private:
// AXTreeObserver overrides. // AXTreeObserver overrides.
void OnAtomicUpdateFinished( void OnAtomicUpdateFinished(
...@@ -110,6 +113,8 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid ...@@ -110,6 +113,8 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
bool root_changed, bool root_changed,
const std::vector<ui::AXTreeObserver::Change>& changes) override; const std::vector<ui::AXTreeObserver::Change>& changes) override;
void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override;
bool UseRootScrollOffsetsWhenComputingBounds() override; bool UseRootScrollOffsetsWhenComputingBounds() override;
WebContentsAccessibilityAndroid* GetWebContentsAXFromRootManager(); WebContentsAccessibilityAndroid* GetWebContentsAXFromRootManager();
......
...@@ -579,6 +579,17 @@ void WebContentsAccessibilityAndroid::HandleNavigate() { ...@@ -579,6 +579,17 @@ void WebContentsAccessibilityAndroid::HandleNavigate() {
Java_WebContentsAccessibilityImpl_handleNavigate(env, obj); Java_WebContentsAccessibilityImpl_handleNavigate(env, obj);
} }
void WebContentsAccessibilityAndroid::ClearNodeInfoCacheForGivenId(
int32_t unique_id) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
if (obj.is_null())
return;
Java_WebContentsAccessibilityImpl_clearNodeInfoCacheForGivenId(env, obj,
unique_id);
}
base::android::ScopedJavaLocalRef<jstring> base::android::ScopedJavaLocalRef<jstring>
WebContentsAccessibilityAndroid::GetSupportedHtmlElementTypes( WebContentsAccessibilityAndroid::GetSupportedHtmlElementTypes(
JNIEnv* env, JNIEnv* env,
...@@ -670,6 +681,46 @@ static size_t ActualUnignoredChildCount(const ui::AXNode* node) { ...@@ -670,6 +681,46 @@ static size_t ActualUnignoredChildCount(const ui::AXNode* node) {
return count; return count;
} }
void WebContentsAccessibilityAndroid::UpdateAccessibilityNodeInfoBoundsRect(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& info,
jint unique_id,
BrowserAccessibilityAndroid* node) {
float dip_scale = use_zoom_for_dsf_enabled_
? 1 / root_manager_->device_scale_factor()
: 1.0;
gfx::Rect absolute_rect = gfx::ScaleToEnclosingRect(
node->GetUnclippedRootFrameBoundsRect(), dip_scale, dip_scale);
gfx::Rect parent_relative_rect = absolute_rect;
if (node->PlatformGetParent()) {
gfx::Rect parent_rect = gfx::ScaleToEnclosingRect(
node->PlatformGetParent()->GetUnclippedRootFrameBoundsRect(), dip_scale,
dip_scale);
parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
}
bool is_root = node->PlatformGetParent() == NULL;
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoLocation(
env, obj, info, unique_id, absolute_rect.x(), absolute_rect.y(),
parent_relative_rect.x(), parent_relative_rect.y(), absolute_rect.width(),
absolute_rect.height(), is_root);
}
jboolean WebContentsAccessibilityAndroid::UpdateCachedAccessibilityNodeInfo(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jobject>& info,
jint unique_id) {
BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
if (!node)
return false;
// Update cached nodes by providing new enclosing Rects
UpdateAccessibilityNodeInfoBoundsRect(env, obj, info, unique_id, node);
return true;
}
jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo( jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& obj,
...@@ -744,23 +795,9 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo( ...@@ -744,23 +795,9 @@ jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
base::android::ConvertUTF16ToJavaString(env, element_id)); base::android::ConvertUTF16ToJavaString(env, element_id));
} }
float dip_scale = use_zoom_for_dsf_enabled_ UpdateAccessibilityNodeInfoBoundsRect(env, obj, info, unique_id, node);
? 1 / root_manager_->device_scale_factor()
: 1.0;
gfx::Rect absolute_rect = gfx::ScaleToEnclosingRect(
node->GetUnclippedRootFrameBoundsRect(), dip_scale, dip_scale);
gfx::Rect parent_relative_rect = absolute_rect;
if (node->PlatformGetParent()) {
gfx::Rect parent_rect = gfx::ScaleToEnclosingRect(
node->PlatformGetParent()->GetUnclippedRootFrameBoundsRect(), dip_scale,
dip_scale);
parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
}
bool is_root = node->PlatformGetParent() == NULL; bool is_root = node->PlatformGetParent() == NULL;
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoLocation(
env, obj, info, unique_id, absolute_rect.x(), absolute_rect.y(),
parent_relative_rect.x(), parent_relative_rect.y(), absolute_rect.width(),
absolute_rect.height(), is_root);
Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoKitKatAttributes( Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoKitKatAttributes(
env, obj, info, is_root, node->IsEditableText(), env, obj, info, is_root, node->IsEditableText(),
......
...@@ -82,6 +82,11 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid ...@@ -82,6 +82,11 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
jint id); jint id);
// Populate Java accessibility data structures with info about a node. // Populate Java accessibility data structures with info about a node.
jboolean UpdateCachedAccessibilityNodeInfo(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jobject>& info,
jint id);
jboolean PopulateAccessibilityNodeInfo( jboolean PopulateAccessibilityNodeInfo(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& obj,
...@@ -264,6 +269,7 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid ...@@ -264,6 +269,7 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
bool OnHoverEvent(const ui::MotionEventAndroid& event); bool OnHoverEvent(const ui::MotionEventAndroid& event);
void HandleHover(int32_t unique_id); void HandleHover(int32_t unique_id);
void HandleNavigate(); void HandleNavigate();
void ClearNodeInfoCacheForGivenId(int32_t unique_id);
base::WeakPtr<WebContentsAccessibilityAndroid> GetWeakPtr(); base::WeakPtr<WebContentsAccessibilityAndroid> GetWeakPtr();
...@@ -271,6 +277,12 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid ...@@ -271,6 +277,12 @@ class CONTENT_EXPORT WebContentsAccessibilityAndroid
BrowserAccessibilityAndroid* GetAXFromUniqueID(int32_t unique_id); BrowserAccessibilityAndroid* GetAXFromUniqueID(int32_t unique_id);
void CollectStats(); void CollectStats();
void UpdateAccessibilityNodeInfoBoundsRect(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jobject>& info,
jint id,
BrowserAccessibilityAndroid* node);
// A weak reference to the Java WebContentsAccessibilityAndroid object. // A weak reference to the Java WebContentsAccessibilityAndroid object.
JavaObjectWeakGlobalRef java_ref_; JavaObjectWeakGlobalRef java_ref_;
......
...@@ -178,6 +178,10 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -178,6 +178,10 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
// to a single Runnable that will send an event after some delay. // to a single Runnable that will send an event after some delay.
private SparseArray<Runnable> mPendingEvents = new SparseArray<>(); private SparseArray<Runnable> mPendingEvents = new SparseArray<>();
// This array maps a given virtualViewId to an |AccessibilityNodeInfo| for that view. We use
// this to update a node quickly rather than building from one scratch each time.
private SparseArray<AccessibilityNodeInfo> mNodeInfoCache = new SparseArray<>();
/** /**
* Create a WebContentsAccessibilityImpl object. * Create a WebContentsAccessibilityImpl object.
*/ */
...@@ -313,6 +317,15 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -313,6 +317,15 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
return this; return this;
} }
@CalledByNative
public void clearNodeInfoCacheForGivenId(int virtualViewId) {
// Recycle and remove the element in our cache for this |virtualViewId|.
if (mNodeInfoCache.get(virtualViewId) != null) {
mNodeInfoCache.get(virtualViewId).recycle();
mNodeInfoCache.remove(virtualViewId);
}
}
@Override @Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (!isAccessibilityEnabled()) { if (!isAccessibilityEnabled()) {
...@@ -329,20 +342,43 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -329,20 +342,43 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
return null; return null;
} }
final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mView); // We need to create an |AccessibilityNodeInfo| object for this |virtualViewId|. If we have
info.setPackageName(mContext.getPackageName()); // one in our cache, then communicate this so web_contents_accessibility_android.cc
info.setSource(mView, virtualViewId); // will update a fraction of the object and for the rest leverage what is already there.
if (mNodeInfoCache.get(virtualViewId) != null) {
AccessibilityNodeInfo cachedNode =
AccessibilityNodeInfo.obtain(mNodeInfoCache.get(virtualViewId));
if (virtualViewId == rootId) { if (WebContentsAccessibilityImplJni.get().updateCachedAccessibilityNodeInfo(
info.setParent(mView); mNativeObj, WebContentsAccessibilityImpl.this, cachedNode, virtualViewId)) {
} // After successfully re-populating this cached node, return result.
return cachedNode;
} else {
// If the node is no longer valid, wipe it from the cache and return null
mNodeInfoCache.get(virtualViewId).recycle();
mNodeInfoCache.remove(virtualViewId);
return null;
}
if (WebContentsAccessibilityImplJni.get().populateAccessibilityNodeInfo(
mNativeObj, WebContentsAccessibilityImpl.this, info, virtualViewId)) {
return info;
} else { } else {
info.recycle(); // If we have no copy of this node in our cache, build a new one from scratch.
return null; final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(mView);
info.setPackageName(mContext.getPackageName());
info.setSource(mView, virtualViewId);
if (virtualViewId == rootId) {
info.setParent(mView);
}
if (WebContentsAccessibilityImplJni.get().populateAccessibilityNodeInfo(
mNativeObj, WebContentsAccessibilityImpl.this, info, virtualViewId)) {
// After successfully populating this node, add it to our cache then return.
mNodeInfoCache.put(virtualViewId, AccessibilityNodeInfo.obtain(info));
return info;
} else {
info.recycle();
return null;
}
} }
} }
...@@ -1765,6 +1801,8 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider ...@@ -1765,6 +1801,8 @@ public class WebContentsAccessibilityImpl extends AccessibilityNodeProvider
WebContentsAccessibilityImpl caller, int id); WebContentsAccessibilityImpl caller, int id);
int getEditableTextSelectionEnd(long nativeWebContentsAccessibilityAndroid, int getEditableTextSelectionEnd(long nativeWebContentsAccessibilityAndroid,
WebContentsAccessibilityImpl caller, int id); WebContentsAccessibilityImpl caller, int id);
boolean updateCachedAccessibilityNodeInfo(long nativeWebContentsAccessibilityAndroid,
WebContentsAccessibilityImpl caller, AccessibilityNodeInfo info, int id);
boolean populateAccessibilityNodeInfo(long nativeWebContentsAccessibilityAndroid, boolean populateAccessibilityNodeInfo(long nativeWebContentsAccessibilityAndroid,
WebContentsAccessibilityImpl caller, AccessibilityNodeInfo info, int id); WebContentsAccessibilityImpl caller, AccessibilityNodeInfo info, int id);
boolean populateAccessibilityEvent(long nativeWebContentsAccessibilityAndroid, boolean populateAccessibilityEvent(long nativeWebContentsAccessibilityAndroid,
......
...@@ -761,6 +761,9 @@ public class WebContentsAccessibilityTest { ...@@ -761,6 +761,9 @@ public class WebContentsAccessibilityTest {
final WebContentsAccessibilityImpl wcax = mActivityTestRule.getWebContentsAccessibility(); final WebContentsAccessibilityImpl wcax = mActivityTestRule.getWebContentsAccessibility();
wcax.addSpellingErrorForTesting(textNodeVirtualViewId, 4, 9); wcax.addSpellingErrorForTesting(textNodeVirtualViewId, 4, 9);
// Clear our cache for this node.
wcax.clearNodeInfoCacheForGivenId(textNodeVirtualViewId);
// Now get that AccessibilityNodeInfo and retrieve its text. // Now get that AccessibilityNodeInfo and retrieve its text.
AccessibilityNodeInfo textNode = AccessibilityNodeInfo textNode =
provider.createAccessibilityNodeInfo(textNodeVirtualViewId); provider.createAccessibilityNodeInfo(textNodeVirtualViewId);
......
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