1 // Copyright (c) 2012 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/browser_accessibility_manager_win.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
10 #include <set>
11 #include <vector>
12 
13 #include "base/command_line.h"
14 #include "base/stl_util.h"
15 #include "base/win/scoped_variant.h"
16 #include "base/win/windows_version.h"
17 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
18 #include "content/browser/accessibility/browser_accessibility_win.h"
19 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
20 #include "content/public/common/content_switches.h"
21 #include "content/public/common/use_zoom_for_dsf_policy.h"
22 #include "ui/accessibility/accessibility_switches.h"
23 #include "ui/accessibility/ax_role_properties.h"
24 #include "ui/accessibility/platform/ax_fragment_root_win.h"
25 #include "ui/accessibility/platform/ax_platform_node_delegate_utils_win.h"
26 #include "ui/accessibility/platform/uia_registrar_win.h"
27 #include "ui/base/win/atl_module.h"
28 
29 namespace content {
30 
31 namespace {
32 
33 #if DCHECK_IS_ON()
34 #define DCHECK_IN_ON_ACCESSIBILITY_EVENTS()                                \
35   DCHECK(in_on_accessibility_events_)                                      \
36       << "This method should only be called during OnAccessibilityEvents " \
37          "because memoized information is cleared afterwards in "          \
38          "FinalizeAccessibilityEvents"
39 #else
40 #define DCHECK_IN_ON_ACCESSIBILITY_EVENTS()
41 #endif  // DCHECK_IS_ON()
42 
GetUiaTextPatternProvider(BrowserAccessibility & node)43 BrowserAccessibility* GetUiaTextPatternProvider(BrowserAccessibility& node) {
44   for (BrowserAccessibility* current_node = &node; current_node;
45        current_node = current_node->PlatformGetParent()) {
46     if (ToBrowserAccessibilityWin(current_node)
47             ->GetCOM()
48             ->IsPatternProviderSupported(UIA_TextPatternId)) {
49       return current_node;
50     }
51   }
52 
53   return nullptr;
54 }
55 
56 }  // namespace
57 
58 // static
Create(const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate)59 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
60     const ui::AXTreeUpdate& initial_tree,
61     BrowserAccessibilityDelegate* delegate) {
62   return new BrowserAccessibilityManagerWin(initial_tree, delegate);
63 }
64 
65 BrowserAccessibilityManagerWin*
ToBrowserAccessibilityManagerWin()66 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
67   return static_cast<BrowserAccessibilityManagerWin*>(this);
68 }
69 
BrowserAccessibilityManagerWin(const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate)70 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
71     const ui::AXTreeUpdate& initial_tree,
72     BrowserAccessibilityDelegate* delegate)
73     : BrowserAccessibilityManager(delegate), load_complete_pending_(false) {
74   ui::win::CreateATLModuleIfNeeded();
75   Initialize(initial_tree);
76 }
77 
78 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() = default;
79 
80 // static
GetEmptyDocument()81 ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
82   ui::AXNodeData empty_document;
83   empty_document.id = 1;
84   empty_document.role = ax::mojom::Role::kRootWebArea;
85   empty_document.AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, true);
86   ui::AXTreeUpdate update;
87   update.root_id = empty_document.id;
88   update.nodes.push_back(empty_document);
89   return update;
90 }
91 
GetParentHWND()92 HWND BrowserAccessibilityManagerWin::GetParentHWND() {
93   BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
94   if (!delegate)
95     return NULL;
96   return delegate->AccessibilityGetAcceleratedWidget();
97 }
98 
UserIsReloading()99 void BrowserAccessibilityManagerWin::UserIsReloading() {
100   if (GetRoot())
101     FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot());
102 }
103 
GetFocus() const104 BrowserAccessibility* BrowserAccessibilityManagerWin::GetFocus() const {
105   BrowserAccessibility* focus = BrowserAccessibilityManager::GetFocus();
106   return GetActiveDescendant(focus);
107 }
108 
FireFocusEvent(BrowserAccessibility * node)109 void BrowserAccessibilityManagerWin::FireFocusEvent(
110     BrowserAccessibility* node) {
111   BrowserAccessibilityManager::FireFocusEvent(node);
112   DCHECK(node);
113   FireWinAccessibilityEvent(EVENT_OBJECT_FOCUS, node);
114   FireUiaAccessibilityEvent(UIA_AutomationFocusChangedEventId, node);
115 }
116 
FireBlinkEvent(ax::mojom::Event event_type,BrowserAccessibility * node)117 void BrowserAccessibilityManagerWin::FireBlinkEvent(
118     ax::mojom::Event event_type,
119     BrowserAccessibility* node) {
120   BrowserAccessibilityManager::FireBlinkEvent(event_type, node);
121   switch (event_type) {
122     case ax::mojom::Event::kClicked:
123       if (node->GetData().IsInvocable())
124         FireUiaAccessibilityEvent(UIA_Invoke_InvokedEventId, node);
125       break;
126     case ax::mojom::Event::kEndOfTest:
127       // Event tests use kEndOfTest as a sentinel to mark the end of the test.
128       FireUiaAccessibilityEvent(
129           ui::UiaRegistrarWin::GetInstance().GetUiaTestCompleteEventId(), node);
130       break;
131     case ax::mojom::Event::kLocationChanged:
132       FireWinAccessibilityEvent(IA2_EVENT_VISIBLE_DATA_CHANGED, node);
133       break;
134     case ax::mojom::Event::kScrolledToAnchor:
135       FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGSTART, node);
136       break;
137     case ax::mojom::Event::kTextChanged:
138       // TODO(crbug.com/1049261) Remove when Views are exposed in the AXTree
139       // which will fire generated text-changed events.
140       if (!node->IsWebContent())
141         HandleTextChangedEvent(*node);
142       break;
143     case ax::mojom::Event::kTextSelectionChanged:
144       HandleTextSelectionChangedEvent(*node);
145       break;
146     default:
147       break;
148   }
149 }
150 
FireGeneratedEvent(ui::AXEventGenerator::Event event_type,BrowserAccessibility * node)151 void BrowserAccessibilityManagerWin::FireGeneratedEvent(
152     ui::AXEventGenerator::Event event_type,
153     BrowserAccessibility* node) {
154   BrowserAccessibilityManager::FireGeneratedEvent(event_type, node);
155   bool can_fire_events = CanFireEvents();
156 
157   if (event_type == ui::AXEventGenerator::Event::LOAD_COMPLETE &&
158       can_fire_events)
159     load_complete_pending_ = false;
160 
161   if (load_complete_pending_ && can_fire_events && GetRoot()) {
162     load_complete_pending_ = false;
163     FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, GetRoot());
164   }
165 
166   if (!can_fire_events && !load_complete_pending_ &&
167       event_type == ui::AXEventGenerator::Event::LOAD_COMPLETE && GetRoot() &&
168       !GetRoot()->IsOffscreen() && GetRoot()->PlatformChildCount() > 0) {
169     load_complete_pending_ = true;
170   }
171 
172   switch (event_type) {
173     case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
174       FireUiaPropertyChangedEvent(UIA_AccessKeyPropertyId, node);
175       break;
176     case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
177       FireWinAccessibilityEvent(IA2_EVENT_ACTIVE_DESCENDANT_CHANGED, node);
178       break;
179     case ui::AXEventGenerator::Event::ALERT:
180       FireWinAccessibilityEvent(EVENT_SYSTEM_ALERT, node);
181       FireUiaAccessibilityEvent(UIA_SystemAlertEventId, node);
182       break;
183     case ui::AXEventGenerator::Event::ATOMIC_CHANGED:
184       HandleAriaPropertiesChangedEvent(*node);
185       break;
186     case ui::AXEventGenerator::Event::BUSY_CHANGED:
187       HandleAriaPropertiesChangedEvent(*node);
188       break;
189     case ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED:
190       // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
191       // SelectionItem.IsSelected is set according to the True or False value of
192       // aria-checked for 'radio' and 'menuitemradio' roles.
193       if (ui::IsRadio(node->GetRole())) {
194         HandleSelectedStateChanged(uia_selection_events_, node,
195                                    IsUIANodeSelected(node));
196       }
197       FireUiaPropertyChangedEvent(UIA_ToggleToggleStatePropertyId, node);
198       HandleAriaPropertiesChangedEvent(*node);
199       break;
200     case ui::AXEventGenerator::Event::CHILDREN_CHANGED: {
201       // If this node is ignored, fire the event on the platform parent since
202       // ignored nodes cannot raise events.
203       BrowserAccessibility* target_node =
204           node->IsIgnored() ? node->PlatformGetParent() : node;
205       if (target_node) {
206         FireWinAccessibilityEvent(EVENT_OBJECT_REORDER, target_node);
207         FireUiaStructureChangedEvent(StructureChangeType_ChildrenReordered,
208                                      target_node);
209       }
210       break;
211     }
212     case ui::AXEventGenerator::Event::CLASS_NAME_CHANGED:
213       FireUiaPropertyChangedEvent(UIA_ClassNamePropertyId, node);
214       break;
215     case ui::AXEventGenerator::Event::COLLAPSED:
216     case ui::AXEventGenerator::Event::EXPANDED:
217       FireUiaPropertyChangedEvent(
218           UIA_ExpandCollapseExpandCollapseStatePropertyId, node);
219       HandleAriaPropertiesChangedEvent(*node);
220       break;
221     case ui::AXEventGenerator::Event::CONTROLS_CHANGED:
222       FireUiaPropertyChangedEvent(UIA_ControllerForPropertyId, node);
223       break;
224     case ui::AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
225       FireUiaPropertyChangedEvent(UIA_DescribedByPropertyId, node);
226       break;
227     case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED:
228       FireUiaPropertyChangedEvent(UIA_FullDescriptionPropertyId, node);
229       break;
230     case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED: {
231       // Fire the event on the object where the focus of the selection is.
232       ui::AXNode::AXID focus_id =
233           ax_tree()->GetUnignoredSelection().focus_object_id;
234       BrowserAccessibility* focus_object = GetFromID(focus_id);
235       if (focus_object) {
236         HandleTextSelectionChangedEvent(*focus_object);
237         if (BrowserAccessibility* text_field =
238                 focus_object->GetTextFieldAncestor()) {
239           HandleTextSelectionChangedEvent(*text_field);
240 
241           // Plain text fields (including input and textarea elements) have
242           // descendant objects that are part of their internal implementation
243           // in Blink, which are not exposed to platform APIs in the
244           // accessibility tree. Firing an event on such descendants will not
245           // reach the assistive software.
246           if (text_field->IsPlainTextField()) {
247             FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, text_field);
248           } else {
249             FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object);
250           }
251         } else {
252           // Fire the event on the root object, which in the absence of a text
253           // field ancestor is the closest UIA text provider (other than the
254           // focused object) in which the selection has changed.
255           HandleTextSelectionChangedEvent(*node);
256 
257           // "IA2_EVENT_TEXT_CARET_MOVED" should only be fired when a visible
258           // caret or a selection is present. In the case of a text field above,
259           // this is implicitly true.
260           if (node->HasVisibleCaretOrSelection())
261             FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object);
262         }
263       }
264       break;
265     }
266     // aria-dropeffect is deprecated in WAI-ARIA 1.1.
267     case ui::AXEventGenerator::Event::DROPEFFECT_CHANGED:
268       HandleAriaPropertiesChangedEvent(*node);
269       break;
270     case ui::AXEventGenerator::Event::EDITABLE_TEXT_CHANGED:
271       HandleTextChangedEvent(*node);
272       break;
273     case ui::AXEventGenerator::Event::ENABLED_CHANGED:
274       FireUiaPropertyChangedEvent(UIA_IsEnabledPropertyId, node);
275       HandleAriaPropertiesChangedEvent(*node);
276       break;
277     case ui::AXEventGenerator::Event::FLOW_FROM_CHANGED:
278       FireUiaPropertyChangedEvent(UIA_FlowsFromPropertyId, node);
279       break;
280     case ui::AXEventGenerator::Event::FLOW_TO_CHANGED:
281       FireUiaPropertyChangedEvent(UIA_FlowsToPropertyId, node);
282       break;
283     // aria-grabbed is deprecated in WAI-ARIA 1.1.
284     case ui::AXEventGenerator::Event::GRABBED_CHANGED:
285       HandleAriaPropertiesChangedEvent(*node);
286       break;
287     case ui::AXEventGenerator::Event::HASPOPUP_CHANGED:
288       HandleAriaPropertiesChangedEvent(*node);
289       break;
290     case ui::AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED:
291       FireUiaPropertyChangedEvent(UIA_LevelPropertyId, node);
292       HandleAriaPropertiesChangedEvent(*node);
293       break;
294     case ui::AXEventGenerator::Event::IGNORED_CHANGED:
295       if (node->IsIgnored()) {
296         FireWinAccessibilityEvent(EVENT_OBJECT_HIDE, node);
297         FireUiaStructureChangedEvent(StructureChangeType_ChildRemoved, node);
298         if (node->GetRole() == ax::mojom::Role::kMenu) {
299           FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPEND, node);
300           FireUiaAccessibilityEvent(UIA_MenuClosedEventId, node);
301         }
302       }
303       HandleAriaPropertiesChangedEvent(*node);
304       break;
305     case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
306       FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, node);
307       break;
308     case ui::AXEventGenerator::Event::INVALID_STATUS_CHANGED:
309       FireUiaPropertyChangedEvent(UIA_IsDataValidForFormPropertyId, node);
310       HandleAriaPropertiesChangedEvent(*node);
311       break;
312     case ui::AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED:
313       FireUiaPropertyChangedEvent(UIA_AcceleratorKeyPropertyId, node);
314       break;
315     case ui::AXEventGenerator::Event::LABELED_BY_CHANGED:
316       FireUiaPropertyChangedEvent(UIA_LabeledByPropertyId, node);
317       break;
318     case ui::AXEventGenerator::Event::LANGUAGE_CHANGED:
319       FireUiaPropertyChangedEvent(UIA_CulturePropertyId, node);
320       break;
321     case ui::AXEventGenerator::Event::LIVE_REGION_CREATED:
322       FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, node);
323       break;
324     case ui::AXEventGenerator::Event::LIVE_REGION_CHANGED:
325       // This event is redundant with the IA2_EVENT_TEXT_INSERTED events;
326       // however, JAWS 2018 and earlier do not process the text inserted
327       // events when "virtual cursor mode" is turned off (Insert+Z).
328       // Fortunately, firing the redudant event does not cause duplicate
329       // verbalizations in either screen reader.
330       // Future versions of JAWS may process the text inserted event when
331       // in focus mode, and so at some point the live region
332       // changed events may truly become redundant with the text inserted
333       // events. Note: Firefox does not fire this event, but JAWS processes
334       // Firefox live region events differently (utilizes MSAA's
335       // EVENT_OBJECT_SHOW).
336       FireWinAccessibilityEvent(EVENT_OBJECT_LIVEREGIONCHANGED, node);
337       FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, node);
338       break;
339     case ui::AXEventGenerator::Event::LIVE_RELEVANT_CHANGED:
340       HandleAriaPropertiesChangedEvent(*node);
341       break;
342     case ui::AXEventGenerator::Event::LIVE_STATUS_CHANGED:
343       FireUiaPropertyChangedEvent(UIA_LiveSettingPropertyId, node);
344       HandleAriaPropertiesChangedEvent(*node);
345       break;
346     case ui::AXEventGenerator::Event::LOAD_COMPLETE:
347       FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, node);
348       break;
349     case ui::AXEventGenerator::Event::LAYOUT_INVALIDATED:
350       FireUiaAccessibilityEvent(UIA_LayoutInvalidatedEventId, node);
351       break;
352     case ui::AXEventGenerator::Event::MULTILINE_STATE_CHANGED:
353       HandleAriaPropertiesChangedEvent(*node);
354       break;
355     case ui::AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED:
356       FireUiaPropertyChangedEvent(UIA_SelectionCanSelectMultiplePropertyId,
357                                   node);
358       HandleAriaPropertiesChangedEvent(*node);
359       break;
360     case ui::AXEventGenerator::Event::NAME_CHANGED:
361       if (ui::IsText(node->GetRole())) {
362         HandleTextChangedEvent(*node);
363       } else {
364         FireUiaPropertyChangedEvent(UIA_NamePropertyId, node);
365       }
366       // Only fire name changes when the name comes from an attribute, otherwise
367       // name changes are redundant with text removed/inserted events.
368       if (node->GetData().GetNameFrom() != ax::mojom::NameFrom::kContents)
369         FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, node);
370       break;
371     case ui::AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED:
372       FireWinAccessibilityEvent(IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, node);
373       // TODO(crbug.com/1108871): Fire UIA event.
374       break;
375     case ui::AXEventGenerator::Event::PLACEHOLDER_CHANGED:
376       FireUiaPropertyChangedEvent(UIA_HelpTextPropertyId, node);
377       break;
378     case ui::AXEventGenerator::Event::POSITION_IN_SET_CHANGED:
379       FireUiaPropertyChangedEvent(UIA_PositionInSetPropertyId, node);
380       HandleAriaPropertiesChangedEvent(*node);
381       break;
382     case ui::AXEventGenerator::Event::RANGE_VALUE_CHANGED:
383       DCHECK(node->GetData().IsRangeValueSupported());
384       FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, node);
385       FireUiaPropertyChangedEvent(UIA_RangeValueValuePropertyId, node);
386       HandleAriaPropertiesChangedEvent(*node);
387       break;
388     case ui::AXEventGenerator::Event::RANGE_VALUE_MAX_CHANGED:
389       DCHECK(node->GetData().IsRangeValueSupported());
390       FireUiaPropertyChangedEvent(UIA_RangeValueMaximumPropertyId, node);
391       HandleAriaPropertiesChangedEvent(*node);
392       break;
393     case ui::AXEventGenerator::Event::RANGE_VALUE_MIN_CHANGED:
394       DCHECK(node->GetData().IsRangeValueSupported());
395       FireUiaPropertyChangedEvent(UIA_RangeValueMinimumPropertyId, node);
396       HandleAriaPropertiesChangedEvent(*node);
397       break;
398     case ui::AXEventGenerator::Event::RANGE_VALUE_STEP_CHANGED:
399       DCHECK(node->GetData().IsRangeValueSupported());
400       FireUiaPropertyChangedEvent(UIA_RangeValueSmallChangePropertyId, node);
401       FireUiaPropertyChangedEvent(UIA_RangeValueLargeChangePropertyId, node);
402       HandleAriaPropertiesChangedEvent(*node);
403       break;
404     case ui::AXEventGenerator::Event::READONLY_CHANGED:
405       if (node->GetData().IsRangeValueSupported())
406         FireUiaPropertyChangedEvent(UIA_RangeValueIsReadOnlyPropertyId, node);
407       else if (ui::IsValuePatternSupported(node))
408         FireUiaPropertyChangedEvent(UIA_ValueIsReadOnlyPropertyId, node);
409       HandleAriaPropertiesChangedEvent(*node);
410       break;
411     case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
412       FireUiaPropertyChangedEvent(UIA_IsRequiredForFormPropertyId, node);
413       HandleAriaPropertiesChangedEvent(*node);
414       break;
415     case ui::AXEventGenerator::Event::ROLE_CHANGED:
416       FireUiaPropertyChangedEvent(UIA_AriaRolePropertyId, node);
417       break;
418     case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
419       FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, node);
420       FireUiaPropertyChangedEvent(UIA_ScrollHorizontalScrollPercentPropertyId,
421                                   node);
422       break;
423     case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
424       FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, node);
425       FireUiaPropertyChangedEvent(UIA_ScrollVerticalScrollPercentPropertyId,
426                                   node);
427       break;
428     case ui::AXEventGenerator::Event::SELECTED_CHANGED:
429       HandleSelectedStateChanged(ia2_selection_events_, node,
430                                  IsIA2NodeSelected(node));
431       HandleSelectedStateChanged(uia_selection_events_, node,
432                                  IsUIANodeSelected(node));
433       HandleAriaPropertiesChangedEvent(*node);
434       break;
435     case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
436       FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONWITHIN, node);
437       break;
438     case ui::AXEventGenerator::Event::SELECTED_VALUE_CHANGED:
439       DCHECK(ui::IsSelectElement(node->GetRole()));
440       FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, node);
441       FireUiaPropertyChangedEvent(UIA_ValueValuePropertyId, node);
442       // By changing the value of a combo box, the document's text contents will
443       // also have changed.
444       HandleTextChangedEvent(*node);
445       break;
446     case ui::AXEventGenerator::Event::SET_SIZE_CHANGED:
447       FireUiaPropertyChangedEvent(UIA_SizeOfSetPropertyId, node);
448       HandleAriaPropertiesChangedEvent(*node);
449       break;
450     case ui::AXEventGenerator::Event::SORT_CHANGED:
451       FireWinAccessibilityEvent(IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, node);
452       HandleAriaPropertiesChangedEvent(*node);
453       break;
454     case ui::AXEventGenerator::Event::SUBTREE_CREATED:
455       FireWinAccessibilityEvent(EVENT_OBJECT_SHOW, node);
456       FireUiaStructureChangedEvent(StructureChangeType_ChildAdded, node);
457       if (node->GetRole() == ax::mojom::Role::kMenu) {
458         FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPSTART, node);
459         FireUiaAccessibilityEvent(UIA_MenuOpenedEventId, node);
460       }
461       break;
462     case ui::AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED:
463       FireWinAccessibilityEvent(IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, node);
464       HandleTextChangedEvent(*node);
465       break;
466     case ui::AXEventGenerator::Event::VALUE_IN_TEXT_FIELD_CHANGED:
467       DCHECK(node->IsTextField());
468       FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, node);
469       FireUiaPropertyChangedEvent(UIA_ValueValuePropertyId, node);
470       HandleTextChangedEvent(*node);
471       break;
472     case ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED:
473       FireWinAccessibilityEvent(EVENT_OBJECT_STATECHANGE, node);
474       break;
475 
476     // Currently unused events on this platform.
477     case ui::AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED:
478     case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
479     case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
480     case ui::AXEventGenerator::Event::FOCUS_CHANGED:
481     case ui::AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED:
482     case ui::AXEventGenerator::Event::LOAD_START:
483     case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED:
484     case ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
485     case ui::AXEventGenerator::Event::PARENT_CHANGED:
486     case ui::AXEventGenerator::Event::PORTAL_ACTIVATED:
487     case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED:
488     case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED:
489     case ui::AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED:
490     case ui::AXEventGenerator::Event::STATE_CHANGED:
491       break;
492   }
493 }
494 
FireWinAccessibilityEvent(LONG win_event_type,BrowserAccessibility * node)495 void BrowserAccessibilityManagerWin::FireWinAccessibilityEvent(
496     LONG win_event_type,
497     BrowserAccessibility* node) {
498   if (!ShouldFireEventForNode(node))
499     return;
500   // Suppress events when |IGNORED_CHANGED| except for related SHOW / HIDE.
501   // Also include MENUPOPUPSTART / MENUPOPUPEND since a change in the ignored
502   // state may show / hide a popup by exposing it to the tree or not.
503   // Also include focus events since a node may become visible at the same time
504   // it receives focus It's never good to suppress a po
505   if (base::Contains(ignored_changed_nodes_, node)) {
506     switch (win_event_type) {
507       case EVENT_OBJECT_HIDE:
508       case EVENT_OBJECT_SHOW:
509       case EVENT_OBJECT_FOCUS:
510       case EVENT_SYSTEM_MENUPOPUPEND:
511       case EVENT_SYSTEM_MENUPOPUPSTART:
512         break;
513       default:
514         return;
515     }
516   } else if (node->IsIgnored()) {
517     return;
518   }
519 
520   HWND hwnd = GetParentHWND();
521   if (!hwnd)
522     return;
523 
524   // Pass the negation of this node's unique id in the |child_id|
525   // argument to NotifyWinEvent; the AT client will then call get_accChild
526   // on the HWND's accessibility object and pass it that same id, which
527   // we can use to retrieve the IAccessible for this node.
528   LONG child_id = -(ToBrowserAccessibilityWin(node)->GetCOM()->GetUniqueId());
529   ::NotifyWinEvent(win_event_type, hwnd, OBJID_CLIENT, child_id);
530 }
531 
FireUiaAccessibilityEvent(LONG uia_event,BrowserAccessibility * node)532 void BrowserAccessibilityManagerWin::FireUiaAccessibilityEvent(
533     LONG uia_event,
534     BrowserAccessibility* node) {
535   if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
536     return;
537   if (!ShouldFireEventForNode(node))
538     return;
539   // Suppress events when |IGNORED_CHANGED| except for MenuClosed / MenuOpen
540   // since a change in the ignored state may show / hide a popup by exposing
541   // it to the tree or not.
542   if (base::Contains(ignored_changed_nodes_, node)) {
543     switch (uia_event) {
544       case UIA_MenuClosedEventId:
545       case UIA_MenuOpenedEventId:
546         break;
547       default:
548         return;
549     }
550   } else if (node->IsIgnored()) {
551     return;
552   }
553 
554   ::UiaRaiseAutomationEvent(ToBrowserAccessibilityWin(node)->GetCOM(),
555                             uia_event);
556 }
557 
FireUiaPropertyChangedEvent(LONG uia_property,BrowserAccessibility * node)558 void BrowserAccessibilityManagerWin::FireUiaPropertyChangedEvent(
559     LONG uia_property,
560     BrowserAccessibility* node) {
561   if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
562     return;
563   if (!ShouldFireEventForNode(node))
564     return;
565   // Suppress events when |IGNORED_CHANGED| with the exception for firing
566   // UIA_AriaPropertiesPropertyId-hidden event on non-text node marked as
567   // ignored.
568   if (node->IsIgnored() || base::Contains(ignored_changed_nodes_, node)) {
569     if (uia_property != UIA_AriaPropertiesPropertyId || node->IsText())
570       return;
571   }
572 
573   // The old value is not used by the system
574   VARIANT old_value = {};
575   old_value.vt = VT_EMPTY;
576 
577   auto* provider = ToBrowserAccessibilityWin(node)->GetCOM();
578   base::win::ScopedVariant new_value;
579   if (SUCCEEDED(
580           provider->GetPropertyValueImpl(uia_property, new_value.Receive()))) {
581     ::UiaRaiseAutomationPropertyChangedEvent(provider, uia_property, old_value,
582                                              new_value);
583   }
584 }
585 
FireUiaStructureChangedEvent(StructureChangeType change_type,BrowserAccessibility * node)586 void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent(
587     StructureChangeType change_type,
588     BrowserAccessibility* node) {
589   if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
590     return;
591   if (!ShouldFireEventForNode(node))
592     return;
593   // Suppress events when |IGNORED_CHANGED| except for related structure changes
594   if (base::Contains(ignored_changed_nodes_, node)) {
595     switch (change_type) {
596       case StructureChangeType_ChildRemoved:
597       case StructureChangeType_ChildAdded:
598         break;
599       default:
600         return;
601     }
602   } else if (node->IsIgnored()) {
603     return;
604   }
605 
606   auto* provider = ToBrowserAccessibilityWin(node);
607   auto* provider_com = provider ? provider->GetCOM() : nullptr;
608   if (!provider || !provider_com)
609     return;
610 
611   switch (change_type) {
612     case StructureChangeType_ChildRemoved: {
613       // 'ChildRemoved' fires on the parent and provides the runtime ID of
614       // the removed child (which was passed as |node|).
615       auto* parent = ToBrowserAccessibilityWin(node->PlatformGetParent());
616       auto* parent_com = parent ? parent->GetCOM() : nullptr;
617       if (parent && parent_com) {
618         ui::AXPlatformNodeWin::RuntimeIdArray runtime_id;
619         provider_com->GetRuntimeIdArray(runtime_id);
620         UiaRaiseStructureChangedEvent(parent_com, change_type,
621                                       runtime_id.data(), runtime_id.size());
622       }
623       break;
624     }
625 
626     default: {
627       // All other types are fired on |node|.  For 'ChildAdded' |node| is the
628       // child that was added; for other types, it's the parent container.
629       UiaRaiseStructureChangedEvent(provider_com, change_type, nullptr, 0);
630     }
631   }
632 }
633 
CanFireEvents() const634 bool BrowserAccessibilityManagerWin::CanFireEvents() const {
635   return BrowserAccessibilityManager::CanFireEvents() &&
636          GetDelegateFromRootManager() &&
637          GetDelegateFromRootManager()->AccessibilityGetAcceleratedWidget();
638 }
639 
GetViewBoundsInScreenCoordinates() const640 gfx::Rect BrowserAccessibilityManagerWin::GetViewBoundsInScreenCoordinates()
641     const {
642   // We have to take the device scale factor into account on Windows.
643   BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
644   if (delegate) {
645     gfx::Rect bounds = delegate->AccessibilityGetViewBounds();
646 
647     // http://www.chromium.org/developers/design-documents/blink-coordinate-spaces
648     // The bounds returned by the delegate are always in device-independent
649     // pixels (DIPs), meaning physical pixels divided by device scale factor
650     // (DSF). However, if UseZoomForDSF is enabled, then Blink does not apply
651     // DSF when going from physical to screen pixels. In that case, we need to
652     // multiply DSF back in to get to Blink's notion of "screen pixels."
653     if (IsUseZoomForDSFEnabled() && device_scale_factor() > 0.0 &&
654         device_scale_factor() != 1.0) {
655       bounds = ScaleToEnclosingRect(bounds, device_scale_factor());
656     }
657     return bounds;
658   }
659   return gfx::Rect();
660 }
661 
OnSubtreeWillBeDeleted(ui::AXTree * tree,ui::AXNode * node)662 void BrowserAccessibilityManagerWin::OnSubtreeWillBeDeleted(ui::AXTree* tree,
663                                                             ui::AXNode* node) {
664   BrowserAccessibility* obj = GetFromAXNode(node);
665   DCHECK(obj);
666   if (obj) {
667     FireWinAccessibilityEvent(EVENT_OBJECT_HIDE, obj);
668     FireUiaStructureChangedEvent(StructureChangeType_ChildRemoved, obj);
669   }
670 }
671 
OnNodeWillBeDeleted(ui::AXTree * tree,ui::AXNode * node)672 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXTree* tree,
673                                                          ui::AXNode* node) {
674   if (node->data().role == ax::mojom::Role::kMenu) {
675     BrowserAccessibility* obj = GetFromAXNode(node);
676     DCHECK(obj);
677     FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPEND, obj);
678     FireUiaAccessibilityEvent(UIA_MenuClosedEventId, obj);
679   }
680 }
681 
OnAtomicUpdateFinished(ui::AXTree * tree,bool root_changed,const std::vector<ui::AXTreeObserver::Change> & changes)682 void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
683     ui::AXTree* tree,
684     bool root_changed,
685     const std::vector<ui::AXTreeObserver::Change>& changes) {
686   BrowserAccessibilityManager::OnAtomicUpdateFinished(tree, root_changed,
687                                                       changes);
688 
689   // Do a sequence of Windows-specific updates on each node. Each one is
690   // done in a single pass that must complete before the next step starts.
691   // The nodes that need to be updated are all of the nodes that were changed,
692   // plus some parents.
693   std::set<ui::AXPlatformNode*> objs_to_update;
694   CollectChangedNodesAndParentsForAtomicUpdate(tree, changes, &objs_to_update);
695 
696   // The first step moves win_attributes_ to old_win_attributes_ and then
697   // recomputes all of win_attributes_ other than IAccessibleText.
698   for (auto* node : objs_to_update) {
699     static_cast<BrowserAccessibilityComWin*>(node)
700         ->UpdateStep1ComputeWinAttributes();
701   }
702 
703   // The next step updates the hypertext of each node, which is a
704   // concatenation of all of its child text nodes, so it can't run until
705   // the text of all of the nodes was computed in the previous step.
706   for (auto* node : objs_to_update) {
707     static_cast<BrowserAccessibilityComWin*>(node)
708         ->UpdateStep2ComputeHypertext();
709   }
710 
711   // The third step fires events on nodes based on what's changed - like
712   // if the name, value, or description changed, or if the hypertext had
713   // text inserted or removed. It's able to figure out exactly what changed
714   // because we still have old_win_attributes_ populated.
715   // This step has to run after the previous two steps complete because the
716   // client may walk the tree when it receives any of these events.
717   // At the end, it deletes old_win_attributes_ since they're not needed
718   // anymore.
719   for (auto* node : objs_to_update) {
720     static_cast<BrowserAccessibilityComWin*>(node)->UpdateStep3FireEvents();
721   }
722 }
723 
724 // static
IsIA2NodeSelected(BrowserAccessibility * node)725 bool BrowserAccessibilityManagerWin::IsIA2NodeSelected(
726     BrowserAccessibility* node) {
727   return node->GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
728 }
729 
730 // static
IsUIANodeSelected(BrowserAccessibility * node)731 bool BrowserAccessibilityManagerWin::IsUIANodeSelected(
732     BrowserAccessibility* node) {
733   // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
734   // SelectionItem.IsSelected is set according to the True or False value of
735   // aria-checked for 'radio' and 'menuitemradio' roles.
736   if (ui::IsRadio(node->GetRole()))
737     return node->GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue;
738 
739   return node->GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
740 }
741 
FireIA2SelectionEvents(BrowserAccessibility * container,BrowserAccessibility * only_selected_child,const SelectionEvents & changes)742 void BrowserAccessibilityManagerWin::FireIA2SelectionEvents(
743     BrowserAccessibility* container,
744     BrowserAccessibility* only_selected_child,
745     const SelectionEvents& changes) {
746   if (only_selected_child) {
747     // Fire 'ElementSelected' on the only selected child.
748     FireWinAccessibilityEvent(EVENT_OBJECT_SELECTION, only_selected_child);
749   } else {
750     const bool container_is_multiselectable =
751         container && container->HasState(ax::mojom::State::kMultiselectable);
752     for (auto* item : changes.added) {
753       if (container_is_multiselectable)
754         FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONADD, item);
755       else
756         FireWinAccessibilityEvent(EVENT_OBJECT_SELECTION, item);
757     }
758     for (auto* item : changes.removed)
759       FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONREMOVE, item);
760   }
761 }
762 
FireUIASelectionEvents(BrowserAccessibility * container,BrowserAccessibility * only_selected_child,const SelectionEvents & changes)763 void BrowserAccessibilityManagerWin::FireUIASelectionEvents(
764     BrowserAccessibility* container,
765     BrowserAccessibility* only_selected_child,
766     const SelectionEvents& changes) {
767   if (only_selected_child) {
768     // Fire 'ElementSelected' on the only selected child.
769     FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId,
770                               only_selected_child);
771     FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId,
772                                 only_selected_child);
773     for (auto* item : changes.removed)
774       FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId, item);
775   } else {
776     // Per UIA documentation, beyond the "invalidate limit" we're supposed to
777     // fire a 'SelectionInvalidated' event.  The exact value isn't specified,
778     // but System.Windows.Automation.Provider uses a value of 20.
779     static const size_t kInvalidateLimit = 20;
780     if ((changes.added.size() + changes.removed.size()) > kInvalidateLimit) {
781       DCHECK_NE(container, nullptr);
782       FireUiaAccessibilityEvent(UIA_Selection_InvalidatedEventId, container);
783     } else {
784       const bool container_is_multiselectable =
785           container && container->HasState(ax::mojom::State::kMultiselectable);
786       for (auto* item : changes.added) {
787         if (container_is_multiselectable) {
788           FireUiaAccessibilityEvent(
789               UIA_SelectionItem_ElementAddedToSelectionEventId, item);
790         } else {
791           FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId,
792                                     item);
793         }
794         FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId,
795                                     item);
796       }
797       for (auto* item : changes.removed) {
798         FireUiaAccessibilityEvent(
799             UIA_SelectionItem_ElementRemovedFromSelectionEventId, item);
800         FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId,
801                                     item);
802       }
803     }
804   }
805 }
806 
807 // static
HandleSelectedStateChanged(SelectionEventsMap & selection_events_map,BrowserAccessibility * node,bool is_selected)808 void BrowserAccessibilityManagerWin::HandleSelectedStateChanged(
809     SelectionEventsMap& selection_events_map,
810     BrowserAccessibility* node,
811     bool is_selected) {
812   // If |node| belongs to a selection container, then map the events with the
813   // selection container as the key because |FinalizeSelectionEvents| needs to
814   // determine whether or not there is only one element selected in order to
815   // optimize what platform events are sent.
816   BrowserAccessibility* key = node;
817   if (auto* selection_container = node->PlatformGetSelectionContainer())
818     key = selection_container;
819 
820   if (is_selected)
821     selection_events_map[key].added.push_back(node);
822   else
823     selection_events_map[key].removed.push_back(node);
824 }
825 
826 // static
FinalizeSelectionEvents(SelectionEventsMap & selection_events_map,IsSelectedPredicate is_selected_predicate,FirePlatformSelectionEventsCallback fire_platform_events_callback)827 void BrowserAccessibilityManagerWin::FinalizeSelectionEvents(
828     SelectionEventsMap& selection_events_map,
829     IsSelectedPredicate is_selected_predicate,
830     FirePlatformSelectionEventsCallback fire_platform_events_callback) {
831   for (auto&& selected : selection_events_map) {
832     BrowserAccessibility* key_node = selected.first;
833     SelectionEvents& changes = selected.second;
834 
835     // Determine if |node| is a selection container with one selected child in
836     // order to optimize what platform events are sent.
837     BrowserAccessibility* container = nullptr;
838     BrowserAccessibility* only_selected_child = nullptr;
839     if (ui::IsContainerWithSelectableChildren(key_node->GetRole())) {
840       container = key_node;
841       for (auto it = container->InternalChildrenBegin();
842            it != container->InternalChildrenEnd(); ++it) {
843         auto* child = it.get();
844         if (is_selected_predicate.Run(child)) {
845           if (!only_selected_child) {
846             only_selected_child = child;
847             continue;
848           }
849 
850           only_selected_child = nullptr;
851           break;
852         }
853       }
854     }
855 
856     fire_platform_events_callback.Run(container, only_selected_child, changes);
857   }
858 
859   selection_events_map.clear();
860 }
861 
HandleAriaPropertiesChangedEvent(BrowserAccessibility & node)862 void BrowserAccessibilityManagerWin::HandleAriaPropertiesChangedEvent(
863     BrowserAccessibility& node) {
864   DCHECK_IN_ON_ACCESSIBILITY_EVENTS();
865   aria_properties_events_.insert(&node);
866 }
867 
HandleTextChangedEvent(BrowserAccessibility & node)868 void BrowserAccessibilityManagerWin::HandleTextChangedEvent(
869     BrowserAccessibility& node) {
870   DCHECK_IN_ON_ACCESSIBILITY_EVENTS();
871   if (BrowserAccessibility* text_provider = GetUiaTextPatternProvider(node))
872     text_changed_events_.insert(text_provider);
873 }
874 
HandleTextSelectionChangedEvent(BrowserAccessibility & node)875 void BrowserAccessibilityManagerWin::HandleTextSelectionChangedEvent(
876     BrowserAccessibility& node) {
877   DCHECK_IN_ON_ACCESSIBILITY_EVENTS();
878   if (BrowserAccessibility* text_provider = GetUiaTextPatternProvider(node))
879     text_selection_changed_events_.insert(text_provider);
880 }
881 
BeforeAccessibilityEvents()882 void BrowserAccessibilityManagerWin::BeforeAccessibilityEvents() {
883   BrowserAccessibilityManager::BeforeAccessibilityEvents();
884 
885   DCHECK(aria_properties_events_.empty());
886   DCHECK(text_changed_events_.empty());
887   DCHECK(text_selection_changed_events_.empty());
888   DCHECK(ignored_changed_nodes_.empty());
889 
890   for (const auto& targeted_event : event_generator()) {
891     if (targeted_event.event_params.event ==
892         ui::AXEventGenerator::Event::IGNORED_CHANGED) {
893       BrowserAccessibility* event_target = GetFromAXNode(targeted_event.node);
894       if (!event_target)
895         continue;
896 
897       const auto insert_pair = ignored_changed_nodes_.insert(event_target);
898 
899       // Expect that |IGNORED_CHANGED| only fires once for a given
900       // node in a given event frame.
901       DCHECK(insert_pair.second);
902     }
903   }
904 }
905 
FinalizeAccessibilityEvents()906 void BrowserAccessibilityManagerWin::FinalizeAccessibilityEvents() {
907   BrowserAccessibilityManager::FinalizeAccessibilityEvents();
908 
909   // Finalize aria properties events.
910   for (BrowserAccessibility* event_node : aria_properties_events_)
911     FireUiaPropertyChangedEvent(UIA_AriaPropertiesPropertyId, event_node);
912   aria_properties_events_.clear();
913 
914   // Finalize selection changed events.
915   for (BrowserAccessibility* event_node : text_selection_changed_events_) {
916     DCHECK(event_node);
917     if (ToBrowserAccessibilityWin(event_node)
918             ->GetCOM()
919             ->IsPatternProviderSupported(UIA_TextPatternId)) {
920       FireUiaAccessibilityEvent(UIA_Text_TextSelectionChangedEventId,
921                                 event_node);
922     }
923   }
924   text_selection_changed_events_.clear();
925 
926   // Finalize text changed events.
927   for (BrowserAccessibility* event_node : text_changed_events_)
928     FireUiaAccessibilityEvent(UIA_Text_TextChangedEventId, event_node);
929   text_changed_events_.clear();
930 
931   // Finalize selection item events.
932   FinalizeSelectionEvents(
933       ia2_selection_events_, base::BindRepeating(&IsIA2NodeSelected),
934       base::BindRepeating(
935           &BrowserAccessibilityManagerWin::FireIA2SelectionEvents,
936           base::Unretained(this)));
937   FinalizeSelectionEvents(
938       uia_selection_events_, base::BindRepeating(&IsUIANodeSelected),
939       base::BindRepeating(
940           &BrowserAccessibilityManagerWin::FireUIASelectionEvents,
941           base::Unretained(this)));
942 
943   ignored_changed_nodes_.clear();
944 }
945 
946 BrowserAccessibilityManagerWin::SelectionEvents::SelectionEvents() = default;
947 BrowserAccessibilityManagerWin::SelectionEvents::~SelectionEvents() = default;
948 
949 }  // namespace content
950