1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/accessibility/web_contents_accessibility_android.h"
6 
7 #include <memory>
8 #include <string>
9 #include <unordered_map>
10 #include <vector>
11 
12 #include "base/android/jni_android.h"
13 #include "base/android/jni_array.h"
14 #include "base/android/jni_string.h"
15 #include "base/debug/crash_logging.h"
16 #include "base/feature_list.h"
17 #include "base/macros.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/numerics/ranges.h"
20 #include "content/browser/accessibility/browser_accessibility_android.h"
21 #include "content/browser/accessibility/browser_accessibility_manager_android.h"
22 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
23 #include "content/browser/accessibility/one_shot_accessibility_tree_search.h"
24 #include "content/browser/android/render_widget_host_connector.h"
25 #include "content/browser/renderer_host/render_widget_host_view_android.h"
26 #include "content/browser/web_contents/web_contents_impl.h"
27 #include "content/public/android/content_jni_headers/WebContentsAccessibilityImpl_jni.h"
28 #include "content/public/common/content_features.h"
29 #include "content/public/common/use_zoom_for_dsf_policy.h"
30 #include "ui/events/android/motion_event_android.h"
31 
32 using base::android::AttachCurrentThread;
33 using base::android::JavaParamRef;
34 using base::android::ScopedJavaLocalRef;
35 
36 namespace content {
37 
38 namespace {
39 
40 // IMPORTANT!
41 // These values are written to logs.  Do not renumber or delete
42 // existing items; add new entries to the end of the list.
43 //
44 // Note: The string names for these enums must correspond with the names of
45 // constants from AccessibilityEvent and AccessibilityServiceInfo, defined
46 // below. For example, UMA_EVENT_ANNOUNCEMENT corresponds to
47 // ACCESSIBILITYEVENT_TYPE_ANNOUNCEMENT via the macro
48 // EVENT_TYPE_HISTOGRAM(event_type_mask, ANNOUNCEMENT).
49 enum {
50   UMA_CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0,
51   UMA_CAPABILITY_CAN_PERFORM_GESTURES = 1,
52   UMA_CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 2,
53   UMA_CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 3,
54   UMA_CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 4,
55   UMA_CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 5,
56   UMA_EVENT_ANNOUNCEMENT = 6,
57   UMA_EVENT_ASSIST_READING_CONTEXT = 7,
58   UMA_EVENT_GESTURE_DETECTION_END = 8,
59   UMA_EVENT_GESTURE_DETECTION_START = 9,
60   UMA_EVENT_NOTIFICATION_STATE_CHANGED = 10,
61   UMA_EVENT_TOUCH_EXPLORATION_GESTURE_END = 11,
62   UMA_EVENT_TOUCH_EXPLORATION_GESTURE_START = 12,
63   UMA_EVENT_TOUCH_INTERACTION_END = 13,
64   UMA_EVENT_TOUCH_INTERACTION_START = 14,
65   UMA_EVENT_VIEW_ACCESSIBILITY_FOCUSED = 15,
66   UMA_EVENT_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 16,
67   UMA_EVENT_VIEW_CLICKED = 17,
68   UMA_EVENT_VIEW_CONTEXT_CLICKED = 18,
69   UMA_EVENT_VIEW_FOCUSED = 19,
70   UMA_EVENT_VIEW_HOVER_ENTER = 20,
71   UMA_EVENT_VIEW_HOVER_EXIT = 21,
72   UMA_EVENT_VIEW_LONG_CLICKED = 22,
73   UMA_EVENT_VIEW_SCROLLED = 23,
74   UMA_EVENT_VIEW_SELECTED = 24,
75   UMA_EVENT_VIEW_TEXT_CHANGED = 25,
76   UMA_EVENT_VIEW_TEXT_SELECTION_CHANGED = 26,
77   UMA_EVENT_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 27,
78   UMA_EVENT_WINDOWS_CHANGED = 28,
79   UMA_EVENT_WINDOW_CONTENT_CHANGED = 29,
80   UMA_EVENT_WINDOW_STATE_CHANGED = 30,
81   UMA_FEEDBACK_AUDIBLE = 31,
82   UMA_FEEDBACK_BRAILLE = 32,
83   UMA_FEEDBACK_GENERIC = 33,
84   UMA_FEEDBACK_HAPTIC = 34,
85   UMA_FEEDBACK_SPOKEN = 35,
86   UMA_FEEDBACK_VISUAL = 36,
87   UMA_FLAG_FORCE_DIRECT_BOOT_AWARE = 37,
88   UMA_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 38,
89   UMA_FLAG_REPORT_VIEW_IDS = 39,
90   UMA_FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 40,
91   UMA_FLAG_REQUEST_FILTER_KEY_EVENTS = 41,
92   UMA_FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 42,
93   UMA_FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 43,
94 
95   // This must always be the last enum. It's okay for its value to
96   // increase, but none of the other enum values may change.
97   UMA_ACCESSIBILITYSERVICEINFO_MAX
98 };
99 
100 // These are constants from
101 // android.view.accessibility.AccessibilityEvent in Java.
102 //
103 // If you add a new constant, add a new UMA enum above and add a line
104 // to CollectStats(), below.
105 enum {
106   ACCESSIBILITYEVENT_TYPE_VIEW_CLICKED = 0x00000001,
107   ACCESSIBILITYEVENT_TYPE_VIEW_LONG_CLICKED = 0x00000002,
108   ACCESSIBILITYEVENT_TYPE_VIEW_SELECTED = 0x00000004,
109   ACCESSIBILITYEVENT_TYPE_VIEW_FOCUSED = 0x00000008,
110   ACCESSIBILITYEVENT_TYPE_VIEW_TEXT_CHANGED = 0x00000010,
111   ACCESSIBILITYEVENT_TYPE_WINDOW_STATE_CHANGED = 0x00000020,
112   ACCESSIBILITYEVENT_TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040,
113   ACCESSIBILITYEVENT_TYPE_VIEW_HOVER_ENTER = 0x00000080,
114   ACCESSIBILITYEVENT_TYPE_VIEW_HOVER_EXIT = 0x00000100,
115   ACCESSIBILITYEVENT_TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200,
116   ACCESSIBILITYEVENT_TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400,
117   ACCESSIBILITYEVENT_TYPE_WINDOW_CONTENT_CHANGED = 0x00000800,
118   ACCESSIBILITYEVENT_TYPE_VIEW_SCROLLED = 0x00001000,
119   ACCESSIBILITYEVENT_TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000,
120   ACCESSIBILITYEVENT_TYPE_ANNOUNCEMENT = 0x00004000,
121   ACCESSIBILITYEVENT_TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000,
122   ACCESSIBILITYEVENT_TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000,
123   ACCESSIBILITYEVENT_TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY =
124       0x00020000,
125   ACCESSIBILITYEVENT_TYPE_GESTURE_DETECTION_START = 0x00040000,
126   ACCESSIBILITYEVENT_TYPE_GESTURE_DETECTION_END = 0x00080000,
127   ACCESSIBILITYEVENT_TYPE_TOUCH_INTERACTION_START = 0x00100000,
128   ACCESSIBILITYEVENT_TYPE_TOUCH_INTERACTION_END = 0x00200000,
129   ACCESSIBILITYEVENT_TYPE_WINDOWS_CHANGED = 0x00400000,
130   ACCESSIBILITYEVENT_TYPE_VIEW_CONTEXT_CLICKED = 0x00800000,
131   ACCESSIBILITYEVENT_TYPE_ASSIST_READING_CONTEXT = 0x01000000,
132 };
133 
134 // These are constants from
135 // android.accessibilityservice.AccessibilityServiceInfo in Java:
136 //
137 // If you add a new constant, add a new UMA enum above and add a line
138 // to CollectStats(), below.
139 enum {
140   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001,
141   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION =
142       0x00000002,
143   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY =
144       0x00000004,
145   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS =
146       0x00000008,
147   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010,
148   ACCESSIBILITYSERVICEINFO_CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020,
149   ACCESSIBILITYSERVICEINFO_FEEDBACK_SPOKEN = 0x0000001,
150   ACCESSIBILITYSERVICEINFO_FEEDBACK_HAPTIC = 0x0000002,
151   ACCESSIBILITYSERVICEINFO_FEEDBACK_AUDIBLE = 0x0000004,
152   ACCESSIBILITYSERVICEINFO_FEEDBACK_VISUAL = 0x0000008,
153   ACCESSIBILITYSERVICEINFO_FEEDBACK_GENERIC = 0x0000010,
154   ACCESSIBILITYSERVICEINFO_FEEDBACK_BRAILLE = 0x0000020,
155   ACCESSIBILITYSERVICEINFO_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002,
156   ACCESSIBILITYSERVICEINFO_FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004,
157   ACCESSIBILITYSERVICEINFO_FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008,
158   ACCESSIBILITYSERVICEINFO_FLAG_REPORT_VIEW_IDS = 0x00000010,
159   ACCESSIBILITYSERVICEINFO_FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020,
160   ACCESSIBILITYSERVICEINFO_FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040,
161   ACCESSIBILITYSERVICEINFO_FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000,
162 };
163 
164 // These macros simplify recording a histogram based on information we get
165 // from an AccessibilityService. There are four bitmasks of information
166 // we get from each AccessibilityService, and for each possible bit mask
167 // corresponding to one flag, we want to check if that flag is set, and if
168 // so, record the corresponding histogram.
169 //
170 // Doing this with macros reduces the chance for human error by
171 // recording the wrong histogram for the wrong flag.
172 //
173 // These macros are used by CollectStats(), below.
174 #define EVENT_TYPE_HISTOGRAM(event_type_mask, event_type)       \
175   if (event_type_mask & ACCESSIBILITYEVENT_TYPE_##event_type)   \
176   UMA_HISTOGRAM_ENUMERATION("Accessibility.AndroidServiceInfo", \
177                             UMA_EVENT_##event_type,             \
178                             UMA_ACCESSIBILITYSERVICEINFO_MAX)
179 #define FLAGS_HISTOGRAM(flags_mask, flag)                       \
180   if (flags_mask & ACCESSIBILITYSERVICEINFO_FLAG_##flag)        \
181   UMA_HISTOGRAM_ENUMERATION("Accessibility.AndroidServiceInfo", \
182                             UMA_FLAG_##flag, UMA_ACCESSIBILITYSERVICEINFO_MAX)
183 #define FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, feedback_type)            \
184   if (feedback_type_mask & ACCESSIBILITYSERVICEINFO_FEEDBACK_##feedback_type) \
185   UMA_HISTOGRAM_ENUMERATION("Accessibility.AndroidServiceInfo",               \
186                             UMA_FEEDBACK_##feedback_type,                     \
187                             UMA_ACCESSIBILITYSERVICEINFO_MAX)
188 #define CAPABILITY_TYPE_HISTOGRAM(capability_type_mask, capability_type) \
189   if (capability_type_mask &                                             \
190       ACCESSIBILITYSERVICEINFO_CAPABILITY_##capability_type)             \
191   UMA_HISTOGRAM_ENUMERATION("Accessibility.AndroidServiceInfo",          \
192                             UMA_CAPABILITY_##capability_type,            \
193                             UMA_ACCESSIBILITYSERVICEINFO_MAX)
194 
195 using SearchKeyToPredicateMap =
196     std::unordered_map<base::string16, AccessibilityMatchPredicate>;
197 base::LazyInstance<SearchKeyToPredicateMap>::Leaky
198     g_search_key_to_predicate_map = LAZY_INSTANCE_INITIALIZER;
199 base::LazyInstance<base::string16>::Leaky g_all_search_keys =
200     LAZY_INSTANCE_INITIALIZER;
201 
SectionPredicate(BrowserAccessibility * start,BrowserAccessibility * node)202 bool SectionPredicate(BrowserAccessibility* start, BrowserAccessibility* node) {
203   switch (node->GetRole()) {
204     case ax::mojom::Role::kApplication:
205     case ax::mojom::Role::kArticle:
206     case ax::mojom::Role::kBanner:
207     case ax::mojom::Role::kComplementary:
208     case ax::mojom::Role::kContentInfo:
209     case ax::mojom::Role::kHeading:
210     case ax::mojom::Role::kMain:
211     case ax::mojom::Role::kNavigation:
212     case ax::mojom::Role::kRegion:
213     case ax::mojom::Role::kSearch:
214     case ax::mojom::Role::kSection:
215       return true;
216     default:
217       return false;
218   }
219 }
220 
AllInterestingNodesPredicate(BrowserAccessibility * start,BrowserAccessibility * node)221 bool AllInterestingNodesPredicate(BrowserAccessibility* start,
222                                   BrowserAccessibility* node) {
223   BrowserAccessibilityAndroid* android_node =
224       static_cast<BrowserAccessibilityAndroid*>(node);
225   return android_node->IsInterestingOnAndroid();
226 }
227 
AddToPredicateMap(const char * search_key_ascii,AccessibilityMatchPredicate predicate)228 void AddToPredicateMap(const char* search_key_ascii,
229                        AccessibilityMatchPredicate predicate) {
230   base::string16 search_key_utf16 = base::ASCIIToUTF16(search_key_ascii);
231   g_search_key_to_predicate_map.Get()[search_key_utf16] = predicate;
232   if (!g_all_search_keys.Get().empty())
233     g_all_search_keys.Get() += base::ASCIIToUTF16(",");
234   g_all_search_keys.Get() += search_key_utf16;
235 }
236 
237 // These are special unofficial strings sent from TalkBack/BrailleBack
238 // to jump to certain categories of web elements.
InitSearchKeyToPredicateMapIfNeeded()239 void InitSearchKeyToPredicateMapIfNeeded() {
240   if (!g_search_key_to_predicate_map.Get().empty())
241     return;
242 
243   AddToPredicateMap("ARTICLE", AccessibilityArticlePredicate);
244   AddToPredicateMap("BUTTON", AccessibilityButtonPredicate);
245   AddToPredicateMap("CHECKBOX", AccessibilityCheckboxPredicate);
246   AddToPredicateMap("COMBOBOX", AccessibilityComboboxPredicate);
247   AddToPredicateMap("CONTROL", AccessibilityControlPredicate);
248   AddToPredicateMap("FOCUSABLE", AccessibilityFocusablePredicate);
249   AddToPredicateMap("FRAME", AccessibilityFramePredicate);
250   AddToPredicateMap("GRAPHIC", AccessibilityGraphicPredicate);
251   AddToPredicateMap("H1", AccessibilityH1Predicate);
252   AddToPredicateMap("H2", AccessibilityH2Predicate);
253   AddToPredicateMap("H3", AccessibilityH3Predicate);
254   AddToPredicateMap("H4", AccessibilityH4Predicate);
255   AddToPredicateMap("H5", AccessibilityH5Predicate);
256   AddToPredicateMap("H6", AccessibilityH6Predicate);
257   AddToPredicateMap("HEADING", AccessibilityHeadingPredicate);
258   AddToPredicateMap("LANDMARK", AccessibilityLandmarkPredicate);
259   AddToPredicateMap("LINK", AccessibilityLinkPredicate);
260   AddToPredicateMap("LIST", AccessibilityListPredicate);
261   AddToPredicateMap("LIST_ITEM", AccessibilityListItemPredicate);
262   AddToPredicateMap("MAIN", AccessibilityMainPredicate);
263   AddToPredicateMap("MEDIA", AccessibilityMediaPredicate);
264   AddToPredicateMap("RADIO", AccessibilityRadioButtonPredicate);
265   AddToPredicateMap("SECTION", SectionPredicate);
266   AddToPredicateMap("TABLE", AccessibilityTablePredicate);
267   AddToPredicateMap("TEXT_FIELD", AccessibilityTextfieldPredicate);
268   AddToPredicateMap("UNVISITED_LINK", AccessibilityUnvisitedLinkPredicate);
269   AddToPredicateMap("VISITED_LINK", AccessibilityVisitedLinkPredicate);
270 }
271 
PredicateForSearchKey(const base::string16 & element_type)272 AccessibilityMatchPredicate PredicateForSearchKey(
273     const base::string16& element_type) {
274   InitSearchKeyToPredicateMapIfNeeded();
275   const auto& iter = g_search_key_to_predicate_map.Get().find(element_type);
276   if (iter != g_search_key_to_predicate_map.Get().end())
277     return iter->second;
278 
279   // If we don't recognize the selector, return any element that a
280   // screen reader should navigate to.
281   return AllInterestingNodesPredicate;
282 }
283 
284 // The element in the document for which we may be displaying an autofill popup.
285 int32_t g_element_hosting_autofill_popup_unique_id = -1;
286 
287 // The element in the document that is the next element after
288 // |g_element_hosting_autofill_popup_unique_id|.
289 int32_t g_element_after_element_hosting_autofill_popup_unique_id = -1;
290 
291 // Autofill popup will not be part of the |AXTree| that is sent by renderer.
292 // Hence, we need a proxy |AXNode| to represent the autofill popup.
293 BrowserAccessibility* g_autofill_popup_proxy_node = nullptr;
294 ui::AXNode* g_autofill_popup_proxy_node_ax_node = nullptr;
295 
DeleteAutofillPopupProxy()296 void DeleteAutofillPopupProxy() {
297   if (g_autofill_popup_proxy_node) {
298     delete g_autofill_popup_proxy_node;
299     delete g_autofill_popup_proxy_node_ax_node;
300     g_autofill_popup_proxy_node = nullptr;
301   }
302 }
303 
304 // The most common use of the EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
305 // API is to retrieve character bounds for one word at a time. There
306 // should never need to be a reason to return more than this many
307 // character bounding boxes at once. Set the limit much higher than needed
308 // but small enough to prevent wasting memory and cpu if abused.
309 const int kMaxCharacterBoundingBoxLen = 1024;
310 
311 }  // anonymous namespace
312 
313 class WebContentsAccessibilityAndroid::Connector
314     : public RenderWidgetHostConnector {
315  public:
316   Connector(WebContents* web_contents,
317             WebContentsAccessibilityAndroid* accessibility);
318   ~Connector() override = default;
319 
320   void DeleteEarly();
321 
322   // RenderWidgetHostConnector:
323   void UpdateRenderProcessConnection(
324       RenderWidgetHostViewAndroid* old_rwhva,
325       RenderWidgetHostViewAndroid* new_rhwva) override;
326 
327  private:
328   std::unique_ptr<WebContentsAccessibilityAndroid> accessibility_;
329 };
330 
Connector(WebContents * web_contents,WebContentsAccessibilityAndroid * accessibility)331 WebContentsAccessibilityAndroid::Connector::Connector(
332     WebContents* web_contents,
333     WebContentsAccessibilityAndroid* accessibility)
334     : RenderWidgetHostConnector(web_contents), accessibility_(accessibility) {
335   Initialize();
336 }
337 
DeleteEarly()338 void WebContentsAccessibilityAndroid::Connector::DeleteEarly() {
339   RenderWidgetHostConnector::DestroyEarly();
340 }
341 
UpdateRenderProcessConnection(RenderWidgetHostViewAndroid * old_rwhva,RenderWidgetHostViewAndroid * new_rwhva)342 void WebContentsAccessibilityAndroid::Connector::UpdateRenderProcessConnection(
343     RenderWidgetHostViewAndroid* old_rwhva,
344     RenderWidgetHostViewAndroid* new_rwhva) {
345   if (old_rwhva)
346     old_rwhva->SetWebContentsAccessibility(nullptr);
347   if (new_rwhva)
348     new_rwhva->SetWebContentsAccessibility(accessibility_.get());
349   accessibility_->UpdateBrowserAccessibilityManager();
350 }
351 
WebContentsAccessibilityAndroid(JNIEnv * env,const JavaParamRef<jobject> & obj,WebContents * web_contents)352 WebContentsAccessibilityAndroid::WebContentsAccessibilityAndroid(
353     JNIEnv* env,
354     const JavaParamRef<jobject>& obj,
355     WebContents* web_contents)
356     : java_ref_(env, obj),
357       web_contents_(static_cast<WebContentsImpl*>(web_contents)),
358       frame_info_initialized_(false),
359       use_zoom_for_dsf_enabled_(IsUseZoomForDSFEnabled()) {
360   // We must initialize this after weak_ptr_factory_ because it can result in
361   // calling UpdateBrowserAccessibilityManager() which accesses
362   // weak_ptr_factory_.
363   connector_ = new Connector(web_contents, this);
364 
365   CollectStats();
366 }
367 
~WebContentsAccessibilityAndroid()368 WebContentsAccessibilityAndroid::~WebContentsAccessibilityAndroid() {
369   JNIEnv* env = AttachCurrentThread();
370   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
371   if (obj.is_null())
372     return;
373 
374   // Clean up autofill popup proxy node in case the popup was not dismissed.
375   DeleteAutofillPopupProxy();
376 
377   Java_WebContentsAccessibilityImpl_onNativeObjectDestroyed(env, obj);
378 }
379 
UpdateBrowserAccessibilityManager()380 void WebContentsAccessibilityAndroid::UpdateBrowserAccessibilityManager() {
381   auto* manager = GetRootBrowserAccessibilityManager();
382   if (manager)
383     manager->set_web_contents_accessibility(GetWeakPtr());
384 }
385 
DeleteEarly(JNIEnv * env)386 void WebContentsAccessibilityAndroid::DeleteEarly(JNIEnv* env) {
387   connector_->DeleteEarly();
388 }
389 
IsEnabled(JNIEnv * env,const JavaParamRef<jobject> & obj)390 jboolean WebContentsAccessibilityAndroid::IsEnabled(
391     JNIEnv* env,
392     const JavaParamRef<jobject>& obj) {
393   return GetRootBrowserAccessibilityManager() != nullptr;
394 }
395 
Enable(JNIEnv * env,const JavaParamRef<jobject> & obj)396 void WebContentsAccessibilityAndroid::Enable(JNIEnv* env,
397                                              const JavaParamRef<jobject>& obj) {
398   BrowserAccessibilityStateImpl* accessibility_state =
399       BrowserAccessibilityStateImpl::GetInstance();
400   auto* manager = GetRootBrowserAccessibilityManager();
401 
402   // First check if we already have a BrowserAccessibilityManager that
403   // that needs to be connected to this instance. This can happen if
404   // BAM creation precedes render view updates for the associated
405   // web contents.
406   if (manager) {
407     manager->set_web_contents_accessibility(GetWeakPtr());
408     return;
409   }
410 
411   // Otherwise, enable accessibility globally unless it was
412   // explicitly disallowed by a command-line flag, then enable it for
413   // this WebContents if that succeeded.
414   accessibility_state->OnScreenReaderDetected();
415   if (accessibility_state->IsAccessibleBrowser())
416     web_contents_->AddAccessibilityMode(ui::kAXModeComplete);
417 }
418 
ShouldRespectDisplayedPasswordText()419 bool WebContentsAccessibilityAndroid::ShouldRespectDisplayedPasswordText() {
420   JNIEnv* env = AttachCurrentThread();
421   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
422   if (obj.is_null())
423     return false;
424   return Java_WebContentsAccessibilityImpl_shouldRespectDisplayedPasswordText(
425       env, obj);
426 }
427 
ShouldExposePasswordText()428 bool WebContentsAccessibilityAndroid::ShouldExposePasswordText() {
429   JNIEnv* env = AttachCurrentThread();
430   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
431   if (obj.is_null())
432     return false;
433   return Java_WebContentsAccessibilityImpl_shouldExposePasswordText(env, obj);
434 }
435 
HandlePageLoaded(int32_t unique_id)436 void WebContentsAccessibilityAndroid::HandlePageLoaded(int32_t unique_id) {
437   JNIEnv* env = AttachCurrentThread();
438   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
439   if (obj.is_null())
440     return;
441   Java_WebContentsAccessibilityImpl_handlePageLoaded(env, obj, unique_id);
442 }
443 
HandleContentChanged(int32_t unique_id)444 void WebContentsAccessibilityAndroid::HandleContentChanged(int32_t unique_id) {
445   JNIEnv* env = AttachCurrentThread();
446   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
447   if (obj.is_null())
448     return;
449   Java_WebContentsAccessibilityImpl_handleContentChanged(env, obj, unique_id);
450 }
451 
HandleFocusChanged(int32_t unique_id)452 void WebContentsAccessibilityAndroid::HandleFocusChanged(int32_t unique_id) {
453   JNIEnv* env = AttachCurrentThread();
454   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
455   if (obj.is_null())
456     return;
457   Java_WebContentsAccessibilityImpl_handleFocusChanged(env, obj, unique_id);
458 }
459 
HandleCheckStateChanged(int32_t unique_id)460 void WebContentsAccessibilityAndroid::HandleCheckStateChanged(
461     int32_t unique_id) {
462   JNIEnv* env = AttachCurrentThread();
463   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
464   if (obj.is_null())
465     return;
466   Java_WebContentsAccessibilityImpl_handleCheckStateChanged(env, obj,
467                                                             unique_id);
468 }
469 
HandleClicked(int32_t unique_id)470 void WebContentsAccessibilityAndroid::HandleClicked(int32_t unique_id) {
471   JNIEnv* env = AttachCurrentThread();
472   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
473   if (obj.is_null())
474     return;
475   Java_WebContentsAccessibilityImpl_handleClicked(env, obj, unique_id);
476 }
477 
HandleScrollPositionChanged(int32_t unique_id)478 void WebContentsAccessibilityAndroid::HandleScrollPositionChanged(
479     int32_t unique_id) {
480   JNIEnv* env = AttachCurrentThread();
481   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
482   if (obj.is_null())
483     return;
484   Java_WebContentsAccessibilityImpl_handleScrollPositionChanged(env, obj,
485                                                                 unique_id);
486 }
487 
HandleScrolledToAnchor(int32_t unique_id)488 void WebContentsAccessibilityAndroid::HandleScrolledToAnchor(
489     int32_t unique_id) {
490   JNIEnv* env = AttachCurrentThread();
491   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
492   if (obj.is_null())
493     return;
494   Java_WebContentsAccessibilityImpl_handleScrolledToAnchor(env, obj, unique_id);
495 }
496 
AnnounceLiveRegionText(const base::string16 & text)497 void WebContentsAccessibilityAndroid::AnnounceLiveRegionText(
498     const base::string16& text) {
499   JNIEnv* env = AttachCurrentThread();
500   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
501   if (obj.is_null())
502     return;
503   Java_WebContentsAccessibilityImpl_announceLiveRegionText(
504       env, obj, base::android::ConvertUTF16ToJavaString(env, text));
505 }
506 
HandleTextSelectionChanged(int32_t unique_id)507 void WebContentsAccessibilityAndroid::HandleTextSelectionChanged(
508     int32_t unique_id) {
509   JNIEnv* env = AttachCurrentThread();
510   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
511   if (obj.is_null())
512     return;
513   Java_WebContentsAccessibilityImpl_handleTextSelectionChanged(env, obj,
514                                                                unique_id);
515 }
516 
HandleEditableTextChanged(int32_t unique_id)517 void WebContentsAccessibilityAndroid::HandleEditableTextChanged(
518     int32_t unique_id) {
519   JNIEnv* env = AttachCurrentThread();
520   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
521   if (obj.is_null())
522     return;
523   Java_WebContentsAccessibilityImpl_handleEditableTextChanged(env, obj,
524                                                               unique_id);
525 }
526 
HandleSliderChanged(int32_t unique_id)527 void WebContentsAccessibilityAndroid::HandleSliderChanged(int32_t unique_id) {
528   JNIEnv* env = AttachCurrentThread();
529   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
530   if (obj.is_null())
531     return;
532   Java_WebContentsAccessibilityImpl_handleSliderChanged(env, obj, unique_id);
533 }
534 
SendDelayedWindowContentChangedEvent()535 void WebContentsAccessibilityAndroid::SendDelayedWindowContentChangedEvent() {
536   JNIEnv* env = AttachCurrentThread();
537   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
538   if (obj.is_null())
539     return;
540   Java_WebContentsAccessibilityImpl_sendDelayedWindowContentChangedEvent(env,
541                                                                          obj);
542 }
543 
HandleHover(int32_t unique_id)544 void WebContentsAccessibilityAndroid::HandleHover(int32_t unique_id) {
545   JNIEnv* env = AttachCurrentThread();
546   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
547   if (obj.is_null())
548     return;
549   Java_WebContentsAccessibilityImpl_handleHover(env, obj, unique_id);
550 }
551 
OnHoverEvent(const ui::MotionEventAndroid & event)552 bool WebContentsAccessibilityAndroid::OnHoverEvent(
553     const ui::MotionEventAndroid& event) {
554   JNIEnv* env = AttachCurrentThread();
555   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
556   if (obj.is_null())
557     return false;
558 
559   if (!Java_WebContentsAccessibilityImpl_onHoverEvent(
560           env, obj,
561           ui::MotionEventAndroid::GetAndroidAction(event.GetAction())))
562     return false;
563 
564   // |HitTest| sends an IPC to the render process to do the hit testing.
565   // The response is handled by HandleHover when it returns.
566   // Hover event was consumed by accessibility by now. Return true to
567   // stop the event from proceeding.
568   if (event.GetAction() != ui::MotionEvent::Action::HOVER_EXIT &&
569       GetRootBrowserAccessibilityManager()) {
570     gfx::PointF point = event.GetPointPix();
571     point.Scale(1 / page_scale_);
572     GetRootBrowserAccessibilityManager()->HitTest(gfx::ToFlooredPoint(point));
573   }
574   return true;
575 }
576 
HandleNavigate()577 void WebContentsAccessibilityAndroid::HandleNavigate() {
578   JNIEnv* env = AttachCurrentThread();
579   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
580   if (obj.is_null())
581     return;
582   Java_WebContentsAccessibilityImpl_handleNavigate(env, obj);
583 }
584 
ClearNodeInfoCacheForGivenId(int32_t unique_id)585 void WebContentsAccessibilityAndroid::ClearNodeInfoCacheForGivenId(
586     int32_t unique_id) {
587   JNIEnv* env = AttachCurrentThread();
588   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
589   if (obj.is_null())
590     return;
591 
592   Java_WebContentsAccessibilityImpl_clearNodeInfoCacheForGivenId(env, obj,
593                                                                  unique_id);
594 }
595 
596 base::android::ScopedJavaLocalRef<jstring>
GetSupportedHtmlElementTypes(JNIEnv * env,const JavaParamRef<jobject> & obj)597 WebContentsAccessibilityAndroid::GetSupportedHtmlElementTypes(
598     JNIEnv* env,
599     const JavaParamRef<jobject>& obj) {
600   InitSearchKeyToPredicateMapIfNeeded();
601   return base::android::ConvertUTF16ToJavaString(env, g_all_search_keys.Get());
602 }
603 
GetRootId(JNIEnv * env,const JavaParamRef<jobject> & obj)604 jint WebContentsAccessibilityAndroid::GetRootId(
605     JNIEnv* env,
606     const JavaParamRef<jobject>& obj) {
607   if (auto* root_manager = GetRootBrowserAccessibilityManager()) {
608     auto* root =
609         static_cast<BrowserAccessibilityAndroid*>(root_manager->GetRoot());
610     if (root)
611       return static_cast<jint>(root->unique_id());
612   }
613   return -1;
614 }
615 
IsNodeValid(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)616 jboolean WebContentsAccessibilityAndroid::IsNodeValid(
617     JNIEnv* env,
618     const JavaParamRef<jobject>& obj,
619     jint unique_id) {
620   return GetAXFromUniqueID(unique_id) != NULL;
621 }
622 
HitTest(JNIEnv * env,const JavaParamRef<jobject> & obj,jint x,jint y)623 void WebContentsAccessibilityAndroid::HitTest(JNIEnv* env,
624                                               const JavaParamRef<jobject>& obj,
625                                               jint x,
626                                               jint y) {
627   if (auto* root_manager = GetRootBrowserAccessibilityManager())
628     root_manager->HitTest(gfx::Point(x, y));
629 }
630 
IsEditableText(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)631 jboolean WebContentsAccessibilityAndroid::IsEditableText(
632     JNIEnv* env,
633     const JavaParamRef<jobject>& obj,
634     jint unique_id) {
635   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
636   if (!node)
637     return false;
638 
639   return node->IsTextField();
640 }
641 
IsFocused(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)642 jboolean WebContentsAccessibilityAndroid::IsFocused(
643     JNIEnv* env,
644     const JavaParamRef<jobject>& obj,
645     jint unique_id) {
646   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
647   if (!node)
648     return false;
649 
650   return node->IsFocused();
651 }
652 
GetEditableTextSelectionStart(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)653 jint WebContentsAccessibilityAndroid::GetEditableTextSelectionStart(
654     JNIEnv* env,
655     const JavaParamRef<jobject>& obj,
656     jint unique_id) {
657   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
658   if (!node)
659     return false;
660 
661   return node->GetSelectionStart();
662 }
663 
GetEditableTextSelectionEnd(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)664 jint WebContentsAccessibilityAndroid::GetEditableTextSelectionEnd(
665     JNIEnv* env,
666     const JavaParamRef<jobject>& obj,
667     jint unique_id) {
668   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
669   if (!node)
670     return false;
671 
672   return node->GetSelectionEnd();
673 }
674 
ActualUnignoredChildCount(const ui::AXNode * node)675 static size_t ActualUnignoredChildCount(const ui::AXNode* node) {
676   size_t count = 0;
677   for (const ui::AXNode* child : node->children()) {
678     if (child->IsIgnored()) {
679       count += ActualUnignoredChildCount(child);
680     } else {
681       ++count;
682     }
683   }
684   return count;
685 }
686 
UpdateAccessibilityNodeInfoBoundsRect(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & info,jint unique_id,BrowserAccessibilityAndroid * node)687 void WebContentsAccessibilityAndroid::UpdateAccessibilityNodeInfoBoundsRect(
688     JNIEnv* env,
689     const JavaParamRef<jobject>& obj,
690     const JavaParamRef<jobject>& info,
691     jint unique_id,
692     BrowserAccessibilityAndroid* node) {
693   auto* root_manager = GetRootBrowserAccessibilityManager();
694   if (!root_manager)
695     return;
696 
697   float dip_scale =
698       use_zoom_for_dsf_enabled_ ? 1 / root_manager->device_scale_factor() : 1.0;
699   gfx::Rect absolute_rect = gfx::ScaleToEnclosingRect(
700       node->GetUnclippedRootFrameBoundsRect(), dip_scale, dip_scale);
701   gfx::Rect parent_relative_rect = absolute_rect;
702   bool is_root = node->PlatformGetParent() == nullptr;
703   if (!is_root) {
704     gfx::Rect parent_rect = gfx::ScaleToEnclosingRect(
705         node->PlatformGetParent()->GetUnclippedRootFrameBoundsRect(), dip_scale,
706         dip_scale);
707     parent_relative_rect.Offset(-parent_rect.OffsetFromOrigin());
708   }
709   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoLocation(
710       env, obj, info, unique_id, absolute_rect.x(), absolute_rect.y(),
711       parent_relative_rect.x(), parent_relative_rect.y(), absolute_rect.width(),
712       absolute_rect.height(), is_root);
713 }
714 
UpdateCachedAccessibilityNodeInfo(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & info,jint unique_id)715 jboolean WebContentsAccessibilityAndroid::UpdateCachedAccessibilityNodeInfo(
716     JNIEnv* env,
717     const JavaParamRef<jobject>& obj,
718     const JavaParamRef<jobject>& info,
719     jint unique_id) {
720   auto* root_manager = GetRootBrowserAccessibilityManager();
721   if (!root_manager)
722     return false;
723 
724   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
725   if (!node)
726     return false;
727 
728   // Update cached nodes by providing new enclosing Rects
729   UpdateAccessibilityNodeInfoBoundsRect(env, obj, info, unique_id, node);
730 
731   return true;
732 }
733 
PopulateAccessibilityNodeInfo(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & info,jint unique_id)734 jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityNodeInfo(
735     JNIEnv* env,
736     const JavaParamRef<jobject>& obj,
737     const JavaParamRef<jobject>& info,
738     jint unique_id) {
739   if (!GetRootBrowserAccessibilityManager())
740     return false;
741 
742   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
743   if (!node)
744     return false;
745 
746   bool is_root = node->PlatformGetParent() == nullptr;
747   if (!is_root) {
748     auto* android_node =
749         static_cast<BrowserAccessibilityAndroid*>(node->PlatformGetParent());
750     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoParent(
751         env, obj, info, android_node->unique_id());
752   }
753   for (BrowserAccessibility::PlatformChildIterator it =
754            node->PlatformChildrenBegin();
755        it != node->PlatformChildrenEnd(); ++it) {
756     auto* android_node = static_cast<BrowserAccessibilityAndroid*>(it.get());
757     Java_WebContentsAccessibilityImpl_addAccessibilityNodeInfoChild(
758         env, obj, info, android_node->unique_id());
759   }
760   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoBooleanAttributes(
761       env, obj, info, unique_id, node->IsReportingCheckable(),
762       node->IsChecked(), node->IsClickable(), node->IsContentInvalid(),
763       node->IsEnabled(), node->IsFocusable(), node->IsFocused(),
764       node->HasImage(), node->IsPasswordField(), node->IsScrollable(),
765       node->IsSelected(), node->IsVisibleToUser());
766   Java_WebContentsAccessibilityImpl_addAccessibilityNodeInfoActions(
767       env, obj, info, unique_id, node->CanScrollForward(),
768       node->CanScrollBackward(), node->CanScrollUp(), node->CanScrollDown(),
769       node->CanScrollLeft(), node->CanScrollRight(), node->IsClickable(),
770       node->IsTextField(), node->IsEnabled(), node->IsFocusable(),
771       node->IsFocused(), node->IsCollapsed(), node->IsExpanded(),
772       node->HasNonEmptyValue(), !node->GetInnerText().empty(),
773       node->IsSeekControl(), node->IsFormDescendant());
774 
775   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoBaseAttributes(
776       env, obj, info, is_root,
777       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()),
778       base::android::ConvertUTF8ToJavaString(env, node->GetRoleString()),
779       base::android::ConvertUTF16ToJavaString(env, node->GetRoleDescription()),
780       base::android::ConvertUTF16ToJavaString(env, node->GetHint()),
781       base::android::ConvertUTF16ToJavaString(env, node->GetTargetUrl()));
782 
783   ScopedJavaLocalRef<jintArray> suggestion_starts_java;
784   ScopedJavaLocalRef<jintArray> suggestion_ends_java;
785   ScopedJavaLocalRef<jobjectArray> suggestion_text_java;
786   std::vector<int> suggestion_starts;
787   std::vector<int> suggestion_ends;
788   node->GetSuggestions(&suggestion_starts, &suggestion_ends);
789   if (suggestion_starts.size() && suggestion_ends.size()) {
790     suggestion_starts_java = base::android::ToJavaIntArray(
791         env, suggestion_starts.data(), suggestion_starts.size());
792     suggestion_ends_java = base::android::ToJavaIntArray(
793         env, suggestion_ends.data(), suggestion_ends.size());
794 
795     // Currently we don't retrieve the text of each suggestion, so
796     // store a blank string for now.
797     std::vector<std::string> suggestion_text(suggestion_starts.size());
798     suggestion_text_java =
799         base::android::ToJavaArrayOfStrings(env, suggestion_text);
800   }
801 
802   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoText(
803       env, obj, info,
804       base::android::ConvertUTF16ToJavaString(env, node->GetInnerText()),
805       node->IsLink(), node->IsTextField(),
806       base::android::ConvertUTF16ToJavaString(
807           env, node->GetInheritedString16Attribute(
808                    ax::mojom::StringAttribute::kLanguage)),
809       suggestion_starts_java, suggestion_ends_java, suggestion_text_java,
810       base::android::ConvertUTF16ToJavaString(env,
811                                               node->GetStateDescription()));
812 
813   base::string16 element_id;
814   if (node->GetHtmlAttribute("id", &element_id)) {
815     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoViewIdResourceName(
816         env, obj, info,
817         base::android::ConvertUTF16ToJavaString(env, element_id));
818   }
819 
820   UpdateAccessibilityNodeInfoBoundsRect(env, obj, info, unique_id, node);
821 
822   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoAttributes(
823       env, obj, info, node->CanOpenPopup(), node->IsDismissable(),
824       node->IsMultiLine(), node->AndroidInputType(),
825       node->AndroidLiveRegionType(),
826       base::android::ConvertUTF16ToJavaString(
827           env, node->GetContentInvalidErrorMessage()));
828 
829   Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoOAttributes(
830       env, obj, info, node->HasCharacterLocations(),
831       base::android::ConvertUTF16ToJavaString(env, node->GetHint()));
832 
833   if (node->IsCollection()) {
834     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoCollectionInfo(
835         env, obj, info, node->RowCount(), node->ColumnCount(),
836         node->IsHierarchical());
837   }
838   if (node->IsCollectionItem() || node->IsHeading()) {
839     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoCollectionItemInfo(
840         env, obj, info, node->RowIndex(), node->RowSpan(), node->ColumnIndex(),
841         node->ColumnSpan(), node->IsHeading());
842   }
843   if (node->GetData().IsRangeValueSupported()) {
844     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoRangeInfo(
845         env, obj, info, node->AndroidRangeType(), node->RangeMin(),
846         node->RangeMax(), node->RangeCurrentValue());
847   }
848 
849   if (ui::IsDialog(node->GetRole())) {
850     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoPaneTitle(
851         env, obj, info,
852         base::android::ConvertUTF16ToJavaString(env, node->GetInnerText()));
853   }
854 
855   if (node->IsTextField()) {
856     Java_WebContentsAccessibilityImpl_setAccessibilityNodeInfoSelectionAttrs(
857         env, obj, info, node->GetSelectionStart(), node->GetSelectionEnd());
858   }
859 
860   return true;
861 }
862 
PopulateAccessibilityEvent(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & event,jint unique_id,jint event_type)863 jboolean WebContentsAccessibilityAndroid::PopulateAccessibilityEvent(
864     JNIEnv* env,
865     const JavaParamRef<jobject>& obj,
866     const JavaParamRef<jobject>& event,
867     jint unique_id,
868     jint event_type) {
869   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
870   if (!node)
871     return false;
872 
873   Java_WebContentsAccessibilityImpl_setAccessibilityEventBooleanAttributes(
874       env, obj, event, node->IsChecked(), node->IsEnabled(),
875       node->IsPasswordField(), node->IsScrollable());
876   Java_WebContentsAccessibilityImpl_setAccessibilityEventClassName(
877       env, obj, event,
878       base::android::ConvertUTF8ToJavaString(env, node->GetClassName()));
879   Java_WebContentsAccessibilityImpl_setAccessibilityEventListAttributes(
880       env, obj, event, node->GetItemIndex(), node->GetItemCount());
881   Java_WebContentsAccessibilityImpl_setAccessibilityEventScrollAttributes(
882       env, obj, event, node->GetScrollX(), node->GetScrollY(),
883       node->GetMaxScrollX(), node->GetMaxScrollY());
884 
885   switch (event_type) {
886     case ANDROID_ACCESSIBILITY_EVENT_TEXT_CHANGED: {
887       base::string16 before_text = node->GetTextChangeBeforeText();
888       base::string16 text = node->GetInnerText();
889       Java_WebContentsAccessibilityImpl_setAccessibilityEventTextChangedAttrs(
890           env, obj, event, node->GetTextChangeFromIndex(),
891           node->GetTextChangeAddedCount(), node->GetTextChangeRemovedCount(),
892           base::android::ConvertUTF16ToJavaString(env, before_text),
893           base::android::ConvertUTF16ToJavaString(env, text));
894       break;
895     }
896     case ANDROID_ACCESSIBILITY_EVENT_TEXT_SELECTION_CHANGED: {
897       base::string16 text = node->GetInnerText();
898       Java_WebContentsAccessibilityImpl_setAccessibilityEventSelectionAttrs(
899           env, obj, event, node->GetSelectionStart(), node->GetSelectionEnd(),
900           node->GetEditableTextLength(),
901           base::android::ConvertUTF16ToJavaString(env, text));
902       break;
903     }
904     default:
905       break;
906   }
907 
908   return true;
909 }
910 
Click(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)911 void WebContentsAccessibilityAndroid::Click(JNIEnv* env,
912                                             const JavaParamRef<jobject>& obj,
913                                             jint unique_id) {
914   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
915 
916   // If it's a heading consisting of only a link, click the link.
917   if (node->IsHeadingLink()) {
918     node = static_cast<BrowserAccessibilityAndroid*>(
919         node->InternalChildrenBegin().get());
920   }
921 
922   if (node)
923     node->manager()->DoDefaultAction(*node);
924 }
925 
Focus(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)926 void WebContentsAccessibilityAndroid::Focus(JNIEnv* env,
927                                             const JavaParamRef<jobject>& obj,
928                                             jint unique_id) {
929   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
930   if (node)
931     node->manager()->SetFocus(*node);
932 }
933 
Blur(JNIEnv * env,const JavaParamRef<jobject> & obj)934 void WebContentsAccessibilityAndroid::Blur(JNIEnv* env,
935                                            const JavaParamRef<jobject>& obj) {
936   if (auto* root_manager = GetRootBrowserAccessibilityManager())
937     root_manager->SetFocus(*root_manager->GetRoot());
938 }
939 
ScrollToMakeNodeVisible(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)940 void WebContentsAccessibilityAndroid::ScrollToMakeNodeVisible(
941     JNIEnv* env,
942     const JavaParamRef<jobject>& obj,
943     jint unique_id) {
944   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
945   if (node) {
946     node->manager()->ScrollToMakeVisible(
947         *node, gfx::Rect(node->GetClippedFrameBoundsRect().size()));
948   }
949 }
950 
SetTextFieldValue(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,const JavaParamRef<jstring> & value)951 void WebContentsAccessibilityAndroid::SetTextFieldValue(
952     JNIEnv* env,
953     const JavaParamRef<jobject>& obj,
954     jint unique_id,
955     const JavaParamRef<jstring>& value) {
956   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
957   if (node) {
958     node->manager()->SetValue(
959         *node, base::android::ConvertJavaStringToUTF8(env, value));
960   }
961 }
962 
SetSelection(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,jint start,jint end)963 void WebContentsAccessibilityAndroid::SetSelection(
964     JNIEnv* env,
965     const JavaParamRef<jobject>& obj,
966     jint unique_id,
967     jint start,
968     jint end) {
969   using AXPlatformPositionInstance =
970       BrowserAccessibilityPosition::AXPositionInstance;
971   using AXPlatformRange = ui::AXRange<AXPlatformPositionInstance::element_type>;
972 
973   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
974   if (node) {
975     node->manager()->SetSelection(
976         AXPlatformRange(node->CreatePositionForSelectionAt(start),
977                         node->CreatePositionForSelectionAt(end)));
978   }
979 }
980 
AdjustSlider(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,jboolean increment)981 jboolean WebContentsAccessibilityAndroid::AdjustSlider(
982     JNIEnv* env,
983     const JavaParamRef<jobject>& obj,
984     jint unique_id,
985     jboolean increment) {
986   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
987   if (!node)
988     return false;
989 
990   BrowserAccessibilityAndroid* android_node =
991       static_cast<BrowserAccessibilityAndroid*>(node);
992 
993   if (!android_node->IsSlider() || !android_node->IsEnabled())
994     return false;
995 
996   float value =
997       node->GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange);
998   float min =
999       node->GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange);
1000   float max =
1001       node->GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange);
1002   if (max <= min)
1003     return false;
1004 
1005   // To behave similarly to an Android SeekBar, move by an increment of
1006   // approximately 5%.
1007   float original_value = value;
1008   float delta = (max - min) / 20.0f;
1009   // Slider does not move if the delta value is less than 1.
1010   delta = ((delta < 1) ? 1 : delta);
1011   value += (increment ? delta : -delta);
1012   value = base::ClampToRange(value, min, max);
1013   if (value != original_value) {
1014     node->manager()->SetValue(*node, base::NumberToString(value));
1015     return true;
1016   }
1017   return false;
1018 }
1019 
ShowContextMenu(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1020 void WebContentsAccessibilityAndroid::ShowContextMenu(
1021     JNIEnv* env,
1022     const JavaParamRef<jobject>& obj,
1023     jint unique_id) {
1024   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1025   if (node)
1026     node->manager()->ShowContextMenu(*node);
1027 }
1028 
FindElementType(JNIEnv * env,const JavaParamRef<jobject> & obj,jint start_id,const JavaParamRef<jstring> & element_type_str,jboolean forwards)1029 jint WebContentsAccessibilityAndroid::FindElementType(
1030     JNIEnv* env,
1031     const JavaParamRef<jobject>& obj,
1032     jint start_id,
1033     const JavaParamRef<jstring>& element_type_str,
1034     jboolean forwards) {
1035   BrowserAccessibilityAndroid* start_node = GetAXFromUniqueID(start_id);
1036   if (!start_node)
1037     return 0;
1038 
1039   auto* root_manager = GetRootBrowserAccessibilityManager();
1040   if (!root_manager)
1041     return 0;
1042 
1043   BrowserAccessibility* root = root_manager->GetRoot();
1044   if (!root)
1045     return 0;
1046 
1047   AccessibilityMatchPredicate predicate = PredicateForSearchKey(
1048       base::android::ConvertJavaStringToUTF16(env, element_type_str));
1049 
1050   OneShotAccessibilityTreeSearch tree_search(root);
1051   tree_search.SetStartNode(start_node);
1052   tree_search.SetDirection(forwards
1053                                ? OneShotAccessibilityTreeSearch::FORWARDS
1054                                : OneShotAccessibilityTreeSearch::BACKWARDS);
1055   tree_search.SetResultLimit(1);
1056   tree_search.SetImmediateDescendantsOnly(false);
1057   // SetCanWrapToLastElement needs to be set as true after talkback pushes its
1058   // corresponding change for b/29103330.
1059   tree_search.SetCanWrapToLastElement(false);
1060   tree_search.SetOnscreenOnly(false);
1061   tree_search.AddPredicate(predicate);
1062 
1063   if (tree_search.CountMatches() == 0)
1064     return 0;
1065 
1066   auto* android_node =
1067       static_cast<BrowserAccessibilityAndroid*>(tree_search.GetMatchAtIndex(0));
1068   int32_t element_id = android_node->unique_id();
1069 
1070   // Navigate forwards to the autofill popup's proxy node if focus is currently
1071   // on the element hosting the autofill popup. Once within the popup, a back
1072   // press will navigate back to the element hosting the popup. If user swipes
1073   // past last suggestion in the popup, or swipes left from the first suggestion
1074   // in the popup, we will navigate to the element that is the next element in
1075   // the document after the element hosting the popup.
1076   if (forwards && start_id == g_element_hosting_autofill_popup_unique_id &&
1077       g_autofill_popup_proxy_node) {
1078     g_element_after_element_hosting_autofill_popup_unique_id = element_id;
1079     auto* proxy_android_node =
1080         static_cast<BrowserAccessibilityAndroid*>(g_autofill_popup_proxy_node);
1081     return proxy_android_node->unique_id();
1082   }
1083 
1084   return element_id;
1085 }
1086 
NextAtGranularity(JNIEnv * env,const JavaParamRef<jobject> & obj,jint granularity,jboolean extend_selection,jint unique_id,jint cursor_index)1087 jboolean WebContentsAccessibilityAndroid::NextAtGranularity(
1088     JNIEnv* env,
1089     const JavaParamRef<jobject>& obj,
1090     jint granularity,
1091     jboolean extend_selection,
1092     jint unique_id,
1093     jint cursor_index) {
1094   auto* root_manager = GetRootBrowserAccessibilityManager();
1095   if (!root_manager)
1096     return false;
1097 
1098   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1099   if (!node)
1100     return false;
1101 
1102   jint start_index = -1;
1103   int end_index = -1;
1104   if (root_manager->NextAtGranularity(granularity, cursor_index, node,
1105                                       &start_index, &end_index)) {
1106     base::string16 text = node->GetInnerText();
1107     Java_WebContentsAccessibilityImpl_finishGranularityMoveNext(
1108         env, obj, base::android::ConvertUTF16ToJavaString(env, text),
1109         extend_selection, start_index, end_index);
1110     return true;
1111   }
1112   return false;
1113 }
1114 
GetTextLength(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1115 jint WebContentsAccessibilityAndroid::GetTextLength(
1116     JNIEnv* env,
1117     const JavaParamRef<jobject>& obj,
1118     jint unique_id) {
1119   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1120   if (!node)
1121     return -1;
1122   base::string16 text = node->GetInnerText();
1123   return text.size();
1124 }
1125 
AddSpellingErrorForTesting(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,jint start_offset,jint end_offset)1126 void WebContentsAccessibilityAndroid::AddSpellingErrorForTesting(
1127     JNIEnv* env,
1128     const JavaParamRef<jobject>& obj,
1129     jint unique_id,
1130     jint start_offset,
1131     jint end_offset) {
1132   BrowserAccessibility* node = GetAXFromUniqueID(unique_id);
1133   CHECK(node);
1134 
1135   while (node->GetRole() != ax::mojom::Role::kStaticText &&
1136          node->InternalChildCount() > 0) {
1137     node = node->InternalChildrenBegin().get();
1138   }
1139 
1140   CHECK(node->GetRole() == ax::mojom::Role::kStaticText);
1141   base::string16 text = node->GetInnerText();
1142   CHECK_LT(start_offset, static_cast<int>(text.size()));
1143   CHECK_LE(end_offset, static_cast<int>(text.size()));
1144 
1145   ui::AXNodeData data = node->GetData();
1146   data.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
1147                            {start_offset});
1148   data.AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
1149                            {end_offset});
1150   data.AddIntListAttribute(
1151       ax::mojom::IntListAttribute::kMarkerTypes,
1152       {static_cast<int>(ax::mojom::MarkerType::kSuggestion)});
1153   node->node()->SetData(data);
1154 }
1155 
PreviousAtGranularity(JNIEnv * env,const JavaParamRef<jobject> & obj,jint granularity,jboolean extend_selection,jint unique_id,jint cursor_index)1156 jboolean WebContentsAccessibilityAndroid::PreviousAtGranularity(
1157     JNIEnv* env,
1158     const JavaParamRef<jobject>& obj,
1159     jint granularity,
1160     jboolean extend_selection,
1161     jint unique_id,
1162     jint cursor_index) {
1163   auto* root_manager = GetRootBrowserAccessibilityManager();
1164   if (!root_manager)
1165     return false;
1166 
1167   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1168   if (!node)
1169     return false;
1170 
1171   jint start_index = -1;
1172   int end_index = -1;
1173   if (root_manager->PreviousAtGranularity(granularity, cursor_index, node,
1174                                           &start_index, &end_index)) {
1175     Java_WebContentsAccessibilityImpl_finishGranularityMovePrevious(
1176         env, obj,
1177         base::android::ConvertUTF16ToJavaString(env, node->GetInnerText()),
1178         extend_selection, start_index, end_index);
1179     return true;
1180   }
1181   return false;
1182 }
1183 
MoveAccessibilityFocus(JNIEnv * env,const JavaParamRef<jobject> & obj,jint old_unique_id,jint new_unique_id)1184 void WebContentsAccessibilityAndroid::MoveAccessibilityFocus(
1185     JNIEnv* env,
1186     const JavaParamRef<jobject>& obj,
1187     jint old_unique_id,
1188     jint new_unique_id) {
1189   BrowserAccessibilityAndroid* old_node = GetAXFromUniqueID(old_unique_id);
1190   if (old_node)
1191     old_node->manager()->ClearAccessibilityFocus(*old_node);
1192 
1193   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(new_unique_id);
1194   if (!node)
1195     return;
1196   node->manager()->SetAccessibilityFocus(*node);
1197 
1198   // When Android sets accessibility focus to a node, we load inline text
1199   // boxes for that node so that subsequent requests for character bounding
1200   // boxes will succeed. However, don't do that for the root of the tree,
1201   // as that will result in loading inline text boxes for the whole tree.
1202   if (node != node->manager()->GetRoot())
1203     node->manager()->LoadInlineTextBoxes(*node);
1204 }
1205 
IsSlider(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1206 bool WebContentsAccessibilityAndroid::IsSlider(JNIEnv* env,
1207                                                const JavaParamRef<jobject>& obj,
1208                                                jint unique_id) {
1209   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1210   if (!node)
1211     return false;
1212 
1213   return node->GetRole() == ax::mojom::Role::kSlider;
1214 }
1215 
OnAutofillPopupDisplayed(JNIEnv * env,const JavaParamRef<jobject> & obj)1216 void WebContentsAccessibilityAndroid::OnAutofillPopupDisplayed(
1217     JNIEnv* env,
1218     const JavaParamRef<jobject>& obj) {
1219   auto* root_manager = GetRootBrowserAccessibilityManager();
1220   if (!root_manager ||
1221       !base::FeatureList::IsEnabled(features::kAndroidAutofillAccessibility))
1222     return;
1223 
1224   BrowserAccessibility* current_focus = root_manager->GetFocus();
1225   if (current_focus == nullptr) {
1226     return;
1227   }
1228 
1229   DeleteAutofillPopupProxy();
1230 
1231   g_autofill_popup_proxy_node = BrowserAccessibility::Create();
1232   g_autofill_popup_proxy_node_ax_node = new ui::AXNode(nullptr, nullptr, -1, 0);
1233   ui::AXNodeData ax_node_data;
1234   ax_node_data.role = ax::mojom::Role::kMenu;
1235   ax_node_data.SetName("Autofill");
1236   ax_node_data.SetRestriction(ax::mojom::Restriction::kReadOnly);
1237   ax_node_data.AddState(ax::mojom::State::kFocusable);
1238   ax_node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
1239   g_autofill_popup_proxy_node_ax_node->SetData(ax_node_data);
1240   g_autofill_popup_proxy_node->Init(root_manager,
1241                                     g_autofill_popup_proxy_node_ax_node);
1242 
1243   auto* android_node = static_cast<BrowserAccessibilityAndroid*>(current_focus);
1244 
1245   g_element_hosting_autofill_popup_unique_id = android_node->unique_id();
1246 }
1247 
OnAutofillPopupDismissed(JNIEnv * env,const JavaParamRef<jobject> & obj)1248 void WebContentsAccessibilityAndroid::OnAutofillPopupDismissed(
1249     JNIEnv* env,
1250     const JavaParamRef<jobject>& obj) {
1251   g_element_hosting_autofill_popup_unique_id = -1;
1252   g_element_after_element_hosting_autofill_popup_unique_id = -1;
1253   DeleteAutofillPopupProxy();
1254 }
1255 
1256 jint WebContentsAccessibilityAndroid::
GetIdForElementAfterElementHostingAutofillPopup(JNIEnv * env,const JavaParamRef<jobject> & obj)1257     GetIdForElementAfterElementHostingAutofillPopup(
1258         JNIEnv* env,
1259         const JavaParamRef<jobject>& obj) {
1260   if (!base::FeatureList::IsEnabled(features::kAndroidAutofillAccessibility) ||
1261       g_element_after_element_hosting_autofill_popup_unique_id == -1 ||
1262       GetAXFromUniqueID(
1263           g_element_after_element_hosting_autofill_popup_unique_id) == nullptr)
1264     return 0;
1265 
1266   return g_element_after_element_hosting_autofill_popup_unique_id;
1267 }
1268 
IsAutofillPopupNode(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1269 jboolean WebContentsAccessibilityAndroid::IsAutofillPopupNode(
1270     JNIEnv* env,
1271     const JavaParamRef<jobject>& obj,
1272     jint unique_id) {
1273   auto* android_node =
1274       static_cast<BrowserAccessibilityAndroid*>(g_autofill_popup_proxy_node);
1275 
1276   return g_autofill_popup_proxy_node && android_node->unique_id() == unique_id;
1277 }
1278 
Scroll(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,int direction,bool is_page_scroll)1279 bool WebContentsAccessibilityAndroid::Scroll(JNIEnv* env,
1280                                              const JavaParamRef<jobject>& obj,
1281                                              jint unique_id,
1282                                              int direction,
1283                                              bool is_page_scroll) {
1284   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1285   if (!node)
1286     return false;
1287 
1288   return node->Scroll(direction, is_page_scroll);
1289 }
1290 
SetRangeValue(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id,float value)1291 bool WebContentsAccessibilityAndroid::SetRangeValue(
1292     JNIEnv* env,
1293     const JavaParamRef<jobject>& obj,
1294     jint unique_id,
1295     float value) {
1296   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1297   if (!node)
1298     return false;
1299 
1300   BrowserAccessibilityAndroid* android_node =
1301       static_cast<BrowserAccessibilityAndroid*>(node);
1302 
1303   if (!android_node->GetData().IsRangeValueSupported())
1304     return false;
1305 
1306   float min =
1307       node->GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange);
1308   float max =
1309       node->GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange);
1310   if (max <= min)
1311     return false;
1312 
1313   value = base::ClampToRange(value, min, max);
1314   node->manager()->SetValue(*node, base::NumberToString(value));
1315   return true;
1316 }
1317 
AreInlineTextBoxesLoaded(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1318 jboolean WebContentsAccessibilityAndroid::AreInlineTextBoxesLoaded(
1319     JNIEnv* env,
1320     const JavaParamRef<jobject>& obj,
1321     jint unique_id) {
1322   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1323   if (!node)
1324     return false;
1325 
1326   return node->AreInlineTextBoxesLoaded();
1327 }
1328 
LoadInlineTextBoxes(JNIEnv * env,const JavaParamRef<jobject> & obj,jint unique_id)1329 void WebContentsAccessibilityAndroid::LoadInlineTextBoxes(
1330     JNIEnv* env,
1331     const JavaParamRef<jobject>& obj,
1332     jint unique_id) {
1333   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1334   if (node)
1335     node->manager()->LoadInlineTextBoxes(*node);
1336 }
1337 
1338 base::android::ScopedJavaLocalRef<jintArray>
GetCharacterBoundingBoxes(JNIEnv * env,const base::android::JavaParamRef<jobject> & obj,jint unique_id,jint start,jint len)1339 WebContentsAccessibilityAndroid::GetCharacterBoundingBoxes(
1340     JNIEnv* env,
1341     const base::android::JavaParamRef<jobject>& obj,
1342     jint unique_id,
1343     jint start,
1344     jint len) {
1345   auto* root_manager = GetRootBrowserAccessibilityManager();
1346   if (!root_manager)
1347     return nullptr;
1348 
1349   BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id);
1350   if (!node)
1351     return nullptr;
1352 
1353   if (len <= 0 || len > kMaxCharacterBoundingBoxLen) {
1354     LOG(ERROR) << "Trying to request EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY "
1355                << "with a length of " << len << ". Valid values are between 1 "
1356                << "and " << kMaxCharacterBoundingBoxLen;
1357     return nullptr;
1358   }
1359 
1360   float dip_scale =
1361       use_zoom_for_dsf_enabled_ ? 1 / root_manager->device_scale_factor() : 1.0;
1362 
1363   gfx::Rect object_bounds = node->GetUnclippedRootFrameBoundsRect();
1364   int coords[4 * len];
1365   for (int i = 0; i < len; i++) {
1366     gfx::Rect char_bounds = node->GetUnclippedRootFrameInnerTextRangeBoundsRect(
1367         start + i, start + i + 1);
1368     if (char_bounds.IsEmpty())
1369       char_bounds = object_bounds;
1370 
1371     char_bounds = gfx::ScaleToEnclosingRect(char_bounds, dip_scale, dip_scale);
1372 
1373     coords[4 * i + 0] = char_bounds.x();
1374     coords[4 * i + 1] = char_bounds.y();
1375     coords[4 * i + 2] = char_bounds.right();
1376     coords[4 * i + 3] = char_bounds.bottom();
1377   }
1378   return base::android::ToJavaIntArray(env, coords,
1379                                        static_cast<size_t>(4 * len));
1380 }
1381 
1382 BrowserAccessibilityManagerAndroid*
GetRootBrowserAccessibilityManager()1383 WebContentsAccessibilityAndroid::GetRootBrowserAccessibilityManager() {
1384   return static_cast<BrowserAccessibilityManagerAndroid*>(
1385       web_contents_->GetRootBrowserAccessibilityManager());
1386 }
1387 
GetAXFromUniqueID(int32_t unique_id)1388 BrowserAccessibilityAndroid* WebContentsAccessibilityAndroid::GetAXFromUniqueID(
1389     int32_t unique_id) {
1390   return BrowserAccessibilityAndroid::GetFromUniqueId(unique_id);
1391 }
1392 
UpdateFrameInfo(float page_scale)1393 void WebContentsAccessibilityAndroid::UpdateFrameInfo(float page_scale) {
1394   page_scale_ = page_scale;
1395   if (frame_info_initialized_)
1396     return;
1397 
1398   JNIEnv* env = AttachCurrentThread();
1399   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
1400   if (obj.is_null())
1401     return;
1402 
1403   Java_WebContentsAccessibilityImpl_notifyFrameInfoInitialized(env, obj);
1404   frame_info_initialized_ = true;
1405 }
1406 
CollectStats()1407 void WebContentsAccessibilityAndroid::CollectStats() {
1408   JNIEnv* env = AttachCurrentThread();
1409   ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
1410   if (obj.is_null())
1411     return;
1412 
1413   int event_type_mask =
1414       Java_WebContentsAccessibilityImpl_getAccessibilityServiceEventTypeMask(
1415           env, obj);
1416   EVENT_TYPE_HISTOGRAM(event_type_mask, ANNOUNCEMENT);
1417   EVENT_TYPE_HISTOGRAM(event_type_mask, ASSIST_READING_CONTEXT);
1418   EVENT_TYPE_HISTOGRAM(event_type_mask, GESTURE_DETECTION_END);
1419   EVENT_TYPE_HISTOGRAM(event_type_mask, GESTURE_DETECTION_START);
1420   EVENT_TYPE_HISTOGRAM(event_type_mask, NOTIFICATION_STATE_CHANGED);
1421   EVENT_TYPE_HISTOGRAM(event_type_mask, TOUCH_EXPLORATION_GESTURE_END);
1422   EVENT_TYPE_HISTOGRAM(event_type_mask, TOUCH_EXPLORATION_GESTURE_START);
1423   EVENT_TYPE_HISTOGRAM(event_type_mask, TOUCH_INTERACTION_END);
1424   EVENT_TYPE_HISTOGRAM(event_type_mask, TOUCH_INTERACTION_START);
1425   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_ACCESSIBILITY_FOCUSED);
1426   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_ACCESSIBILITY_FOCUS_CLEARED);
1427   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_CLICKED);
1428   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_CONTEXT_CLICKED);
1429   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_FOCUSED);
1430   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_HOVER_ENTER);
1431   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_HOVER_EXIT);
1432   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_LONG_CLICKED);
1433   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_SCROLLED);
1434   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_SELECTED);
1435   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_TEXT_CHANGED);
1436   EVENT_TYPE_HISTOGRAM(event_type_mask, VIEW_TEXT_SELECTION_CHANGED);
1437   EVENT_TYPE_HISTOGRAM(event_type_mask,
1438                        VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
1439   EVENT_TYPE_HISTOGRAM(event_type_mask, WINDOWS_CHANGED);
1440   EVENT_TYPE_HISTOGRAM(event_type_mask, WINDOW_CONTENT_CHANGED);
1441   EVENT_TYPE_HISTOGRAM(event_type_mask, WINDOW_STATE_CHANGED);
1442 
1443   int feedback_type_mask =
1444       Java_WebContentsAccessibilityImpl_getAccessibilityServiceFeedbackTypeMask(
1445           env, obj);
1446   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, SPOKEN);
1447   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, HAPTIC);
1448   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, AUDIBLE);
1449   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, VISUAL);
1450   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, GENERIC);
1451   FEEDBACK_TYPE_HISTOGRAM(feedback_type_mask, BRAILLE);
1452 
1453   int flags_mask =
1454       Java_WebContentsAccessibilityImpl_getAccessibilityServiceFlagsMask(env,
1455                                                                          obj);
1456   FLAGS_HISTOGRAM(flags_mask, INCLUDE_NOT_IMPORTANT_VIEWS);
1457   FLAGS_HISTOGRAM(flags_mask, REQUEST_TOUCH_EXPLORATION_MODE);
1458   FLAGS_HISTOGRAM(flags_mask, REQUEST_ENHANCED_WEB_ACCESSIBILITY);
1459   FLAGS_HISTOGRAM(flags_mask, REPORT_VIEW_IDS);
1460   FLAGS_HISTOGRAM(flags_mask, REQUEST_FILTER_KEY_EVENTS);
1461   FLAGS_HISTOGRAM(flags_mask, RETRIEVE_INTERACTIVE_WINDOWS);
1462   FLAGS_HISTOGRAM(flags_mask, FORCE_DIRECT_BOOT_AWARE);
1463 
1464   int capabilities_mask =
1465       Java_WebContentsAccessibilityImpl_getAccessibilityServiceCapabilitiesMask(
1466           env, obj);
1467   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask, CAN_RETRIEVE_WINDOW_CONTENT);
1468   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask, CAN_REQUEST_TOUCH_EXPLORATION);
1469   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask,
1470                             CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY);
1471   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask, CAN_REQUEST_FILTER_KEY_EVENTS);
1472   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask, CAN_CONTROL_MAGNIFICATION);
1473   CAPABILITY_TYPE_HISTOGRAM(capabilities_mask, CAN_PERFORM_GESTURES);
1474 }
1475 
1476 base::WeakPtr<WebContentsAccessibilityAndroid>
GetWeakPtr()1477 WebContentsAccessibilityAndroid::GetWeakPtr() {
1478   return weak_ptr_factory_.GetWeakPtr();
1479 }
1480 
JNI_WebContentsAccessibilityImpl_Init(JNIEnv * env,const JavaParamRef<jobject> & obj,const JavaParamRef<jobject> & jweb_contents)1481 jlong JNI_WebContentsAccessibilityImpl_Init(
1482     JNIEnv* env,
1483     const JavaParamRef<jobject>& obj,
1484     const JavaParamRef<jobject>& jweb_contents) {
1485   WebContents* web_contents = WebContents::FromJavaWebContents(jweb_contents);
1486   DCHECK(web_contents);
1487 
1488   return reinterpret_cast<intptr_t>(
1489       new WebContentsAccessibilityAndroid(env, obj, web_contents));
1490 }
1491 
1492 }  // namespace content
1493