Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
ax_platform_node_win.cc File Reference
#include "ax_platform_node_win.h"
#include <wrl/client.h>
#include <wrl/implements.h>
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include "ax/ax_action_data.h"
#include "ax/ax_active_popup.h"
#include "ax/ax_enum_util.h"
#include "ax/ax_mode_observer.h"
#include "ax/ax_node_data.h"
#include "ax/ax_node_position.h"
#include "ax/ax_role_properties.h"
#include "ax/ax_tree_data.h"
#include "base/win/enum_variant.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "base/win/variant_vector.h"
#include "ax_fragment_root_win.h"
#include "ax_platform_node_delegate.h"
#include "ax_platform_node_delegate_utils_win.h"
#include "ax_platform_node_textprovider_win.h"
#include "shellscalingapi.h"
#include "uia_registrar_win.h"
#include "base/logging.h"
#include "base/win/atl_module.h"
#include "base/win/display.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "gfx/geometry/rect_conversions.h"

Go to the source code of this file.

Namespaces

namespace  ax
 
namespace  ax::mojom
 
namespace  ui
 

Macros

#define COM_OBJECT_VALIDATE()
 
#define COM_OBJECT_VALIDATE_1_ARG(arg)
 
#define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2)
 
#define COM_OBJECT_VALIDATE_3_ARGS(arg1, arg2, arg3)
 
#define COM_OBJECT_VALIDATE_4_ARGS(arg1, arg2, arg3, arg4)
 
#define COM_OBJECT_VALIDATE_5_ARGS(arg1, arg2, arg3, arg4, arg5)
 
#define COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target)
 
#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, arg, target)
 
#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, arg1, arg2, target)
 
#define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS_AND_GET_TARGET(var_id, arg1, arg2, arg3, target)
 
#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET(var_id, arg1, arg2, arg3, arg4, target)
 

Variables

const uint32_t ui::kScreenReaderAndHTMLAccessibilityModes
 

Macro Definition Documentation

◆ COM_OBJECT_VALIDATE

#define COM_OBJECT_VALIDATE ( )
Value:
if (!GetDelegate()) \
return E_FAIL;

Definition at line 58 of file ax_platform_node_win.cc.

66 {};
67#define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2) \
68 if (!GetDelegate()) \
69 return E_FAIL; \
70 if (!arg1) \
71 return E_INVALIDARG; \
72 *arg1 = {}; \
73 if (!arg2) \
74 return E_INVALIDARG; \
75 *arg2 = {};
76#define COM_OBJECT_VALIDATE_3_ARGS(arg1, arg2, arg3) \
77 if (!GetDelegate()) \
78 return E_FAIL; \
79 if (!arg1) \
80 return E_INVALIDARG; \
81 *arg1 = {}; \
82 if (!arg2) \
83 return E_INVALIDARG; \
84 *arg2 = {}; \
85 if (!arg3) \
86 return E_INVALIDARG; \
87 *arg3 = {};
88#define COM_OBJECT_VALIDATE_4_ARGS(arg1, arg2, arg3, arg4) \
89 if (!GetDelegate()) \
90 return E_FAIL; \
91 if (!arg1) \
92 return E_INVALIDARG; \
93 *arg1 = {}; \
94 if (!arg2) \
95 return E_INVALIDARG; \
96 *arg2 = {}; \
97 if (!arg3) \
98 return E_INVALIDARG; \
99 *arg3 = {}; \
100 if (!arg4) \
101 return E_INVALIDARG; \
102 *arg4 = {};
103#define COM_OBJECT_VALIDATE_5_ARGS(arg1, arg2, arg3, arg4, arg5) \
104 if (!GetDelegate()) \
105 return E_FAIL; \
106 if (!arg1) \
107 return E_INVALIDARG; \
108 *arg1 = {}; \
109 if (!arg2) \
110 return E_INVALIDARG; \
111 *arg2 = {}; \
112 if (!arg3) \
113 return E_INVALIDARG; \
114 *arg3 = {}; \
115 if (!arg4) \
116 return E_INVALIDARG; \
117 *arg4 = {}; \
118 if (!arg5) \
119 return E_INVALIDARG; \
120 *arg5 = {};
121#define COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target) \
122 if (!GetDelegate()) \
123 return E_FAIL; \
124 target = GetTargetFromChildID(var_id); \
125 if (!target) \
126 return E_INVALIDARG; \
127 if (!target->GetDelegate()) \
128 return E_INVALIDARG;
129#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, arg, target) \
130 if (!GetDelegate()) \
131 return E_FAIL; \
132 if (!arg) \
133 return E_INVALIDARG; \
134 *arg = {}; \
135 target = GetTargetFromChildID(var_id); \
136 if (!target) \
137 return E_INVALIDARG; \
138 if (!target->GetDelegate()) \
139 return E_INVALIDARG;
140#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
141 target) \
142 if (!GetDelegate()) \
143 return E_FAIL; \
144 if (!arg1) \
145 return E_INVALIDARG; \
146 *arg1 = {}; \
147 if (!arg2) \
148 return E_INVALIDARG; \
149 *arg2 = {}; \
150 target = GetTargetFromChildID(var_id); \
151 if (!target) \
152 return E_INVALIDARG; \
153 if (!target->GetDelegate()) \
154 return E_INVALIDARG;
155#define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
156 arg3, target) \
157 if (!GetDelegate()) \
158 return E_FAIL; \
159 if (!arg1) \
160 return E_INVALIDARG; \
161 *arg1 = {}; \
162 if (!arg2) \
163 return E_INVALIDARG; \
164 *arg2 = {}; \
165 if (!arg3) \
166 return E_INVALIDARG; \
167 *arg3 = {}; \
168 target = GetTargetFromChildID(var_id); \
169 if (!target) \
170 return E_INVALIDARG; \
171 if (!target->GetDelegate()) \
172 return E_INVALIDARG;
173#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET(var_id, arg1, arg2, \
174 arg3, arg4, target) \
175 if (!GetDelegate()) \
176 return E_FAIL; \
177 if (!arg1) \
178 return E_INVALIDARG; \
179 *arg1 = {}; \
180 if (!arg2) \
181 return E_INVALIDARG; \
182 *arg2 = {}; \
183 if (!arg3) \
184 return E_INVALIDARG; \
185 *arg3 = {}; \
186 if (!arg4) \
187 return E_INVALIDARG; \
188 *arg4 = {}; \
189 target = GetTargetFromChildID(var_id); \
190 if (!target) \
191 return E_INVALIDARG; \
192 if (!target->GetDelegate()) \
193 return E_INVALIDARG;
194
195namespace ui {
196
197namespace {
198
199typedef std::unordered_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
200
201// Sets the multiplier by which large changes to a RangeValueProvider are
202// greater than small changes.
203constexpr int kLargeChangeScaleFactor = 10;
204
205// The amount to scroll when UI Automation asks to scroll by a small increment.
206// Value is in device independent pixels and is the same used by Blink when
207// cursor keys are used to scroll a webpage.
208constexpr float kSmallScrollIncrement = 40.0f;
209
210// The filename of the DLL to load for UIA.
211constexpr wchar_t kUIADLLFilename[] = L"uiautomationcore.dll";
212
213void AppendTextToString(std::u16string extra_text, std::u16string* string) {
214 if (extra_text.empty())
215 return;
216
217 if (string->empty()) {
218 *string = extra_text;
219 return;
220 }
221
222 *string += std::u16string(u". ") + extra_text;
223}
224
225// Helper function to GetPatternProviderFactoryMethod that, given a node,
226// will return a pattern interface through result based on the provided type T.
227template <typename T>
228void PatternProvider(AXPlatformNodeWin* node, IUnknown** result) {
229 node->AddRef();
230 *result = static_cast<T*>(node);
231}
232
233} // namespace
234
235void AXPlatformNodeWin::AddAttributeToList(const char* name,
236 const char* value,
237 PlatformAttributeList* attributes) {
238 std::string str_value = value;
239 SanitizeStringAttribute(str_value, &str_value);
240 attributes->push_back(base::UTF8ToUTF16(name) + u":" +
241 base::UTF8ToUTF16(str_value));
242}
243
244// There is no easy way to decouple |kScreenReader| and |kHTML| accessibility
245// modes when Windows screen readers are used. For example, certain roles use
246// the HTML tag name. Input fields require their type attribute to be exposed.
249
250//
251// AXPlatformNode::Create
252//
253
254// static
255AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) {
256 // Make sure ATL is initialized in this module.
258
259 CComObject<AXPlatformNodeWin>* instance = nullptr;
260 HRESULT hr = CComObject<AXPlatformNodeWin>::CreateInstance(&instance);
262 instance->Init(delegate);
263 instance->AddRef();
264 return instance;
265}
266
267// static
270 if (!accessible)
271 return nullptr;
272 Microsoft::WRL::ComPtr<AXPlatformNodeWin> ax_platform_node;
273 accessible->QueryInterface(IID_PPV_ARGS(&ax_platform_node));
274 return ax_platform_node.Get();
275}
276
277//
278// AXPlatformNodeWin
279//
280
281AXPlatformNodeWin::AXPlatformNodeWin() {}
282
283AXPlatformNodeWin::~AXPlatformNodeWin() {}
284
285void AXPlatformNodeWin::Init(AXPlatformNodeDelegate* delegate) {
286 AXPlatformNodeBase::Init(delegate);
287}
288
289// Static
290void AXPlatformNodeWin::SanitizeStringAttributeForUIAAriaProperty(
291 const std::u16string& input,
292 std::u16string* output) {
293 BASE_DCHECK(output);
294 // According to the UIA Spec, these characters need to be escaped with a
295 // backslash in an AriaProperties string: backslash, equals and semicolon.
296 // Note that backslash must be replaced first.
297 base::ReplaceChars(input, u"\\", u"\\\\", output);
298 base::ReplaceChars(*output, u"=", u"\\=", output);
299 base::ReplaceChars(*output, u";", u"\\;", output);
300}
301
302void AXPlatformNodeWin::StringAttributeToUIAAriaProperty(
303 std::vector<std::u16string>& properties,
305 const char* uia_aria_property) {
306 std::u16string value;
307 if (GetString16Attribute(attribute, &value)) {
308 SanitizeStringAttributeForUIAAriaProperty(value, &value);
309 properties.push_back(base::ASCIIToUTF16(uia_aria_property) + u"=" + value);
310 }
311}
312
313void AXPlatformNodeWin::BoolAttributeToUIAAriaProperty(
314 std::vector<std::u16string>& properties,
315 ax::mojom::BoolAttribute attribute,
316 const char* uia_aria_property) {
317 bool value;
318 if (GetBoolAttribute(attribute, &value)) {
319 properties.push_back((base::ASCIIToUTF16(uia_aria_property) + u"=") +
320 (value ? u"true" : u"false"));
321 }
322}
323
324void AXPlatformNodeWin::IntAttributeToUIAAriaProperty(
325 std::vector<std::u16string>& properties,
326 ax::mojom::IntAttribute attribute,
327 const char* uia_aria_property) {
328 int value;
329 if (GetIntAttribute(attribute, &value)) {
330 properties.push_back(base::ASCIIToUTF16(uia_aria_property) + u"=" +
332 }
333}
334
335void AXPlatformNodeWin::FloatAttributeToUIAAriaProperty(
336 std::vector<std::u16string>& properties,
338 const char* uia_aria_property) {
339 float value;
340 if (GetFloatAttribute(attribute, &value)) {
341 properties.push_back(base::ASCIIToUTF16(uia_aria_property) + u"=" +
343 }
344}
345
346void AXPlatformNodeWin::StateToUIAAriaProperty(
347 std::vector<std::u16string>& properties,
348 ax::mojom::State state,
349 const char* uia_aria_property) {
350 const AXNodeData& data = GetData();
351 bool value = data.HasState(state);
352 properties.push_back((base::ASCIIToUTF16(uia_aria_property) + u"=") +
353 (value ? u"true" : u"false"));
354}
355
356void AXPlatformNodeWin::HtmlAttributeToUIAAriaProperty(
357 std::vector<std::u16string>& properties,
358 const char* html_attribute_name,
359 const char* uia_aria_property) {
360 std::u16string html_attribute_value;
361 if (GetData().GetHtmlAttribute(html_attribute_name, &html_attribute_value)) {
362 SanitizeStringAttributeForUIAAriaProperty(html_attribute_value,
363 &html_attribute_value);
364 properties.push_back(base::ASCIIToUTF16(uia_aria_property) + u"=" +
365 html_attribute_value);
366 }
367}
368
369std::vector<AXPlatformNodeWin*>
370AXPlatformNodeWin::CreatePlatformNodeVectorFromRelationIdVector(
371 std::vector<int32_t>& relation_id_list) {
372 std::vector<AXPlatformNodeWin*> platform_node_list;
373
374 for (int32_t id : relation_id_list) {
375 AXPlatformNode* platform_node = GetDelegate()->GetFromNodeID(id);
376 if (IsValidUiaRelationTarget(platform_node)) {
377 platform_node_list.push_back(
378 static_cast<AXPlatformNodeWin*>(platform_node));
379 }
380 }
381
382 return platform_node_list;
383}
384
385SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsSafeArray(
386 std::vector<AXPlatformNodeWin*>& platform_node_list) {
387 if (platform_node_list.empty())
388 return nullptr;
389
390 SAFEARRAY* uia_array =
391 SafeArrayCreateVector(VT_UNKNOWN, 0, platform_node_list.size());
392 LONG i = 0;
393
394 for (AXPlatformNodeWin* platform_node : platform_node_list) {
395 // All incoming ids should already be validated to have a valid relation
396 // targets so that this function does not need to re-check before allocating
397 // the SAFEARRAY.
398 BASE_DCHECK(IsValidUiaRelationTarget(platform_node));
399 SafeArrayPutElement(uia_array, &i,
400 static_cast<IRawElementProviderSimple*>(platform_node));
401 ++i;
402 }
403
404 return uia_array;
405}
406
407SAFEARRAY* AXPlatformNodeWin::CreateUIAControllerForArray() {
408 std::vector<int32_t> relation_id_list =
410
411 std::vector<AXPlatformNodeWin*> platform_node_list =
412 CreatePlatformNodeVectorFromRelationIdVector(relation_id_list);
413
414 if (GetActivePopupAxUniqueId() != std::nullopt) {
415 AXPlatformNodeWin* view_popup_node_win = static_cast<AXPlatformNodeWin*>(
416 GetFromUniqueId(GetActivePopupAxUniqueId().value()));
417
418 if (IsValidUiaRelationTarget(view_popup_node_win))
419 platform_node_list.push_back(view_popup_node_win);
420 }
421
422 return CreateUIAElementsSafeArray(platform_node_list);
423}
424
425SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayForRelation(
426 const ax::mojom::IntListAttribute& attribute) {
427 std::vector<int32_t> relation_id_list = GetIntListAttribute(attribute);
428
429 std::vector<AXPlatformNodeWin*> platform_node_list =
430 CreatePlatformNodeVectorFromRelationIdVector(relation_id_list);
431
432 return CreateUIAElementsSafeArray(platform_node_list);
433}
434
435SAFEARRAY* AXPlatformNodeWin::CreateUIAElementsArrayForReverseRelation(
436 const ax::mojom::IntListAttribute& attribute) {
437 std::set<AXPlatformNode*> reverse_relations =
438 GetDelegate()->GetReverseRelations(attribute);
439
440 std::vector<int32_t> id_list;
441 std::transform(
442 reverse_relations.cbegin(), reverse_relations.cend(),
443 std::back_inserter(id_list), [](AXPlatformNode* platform_node) {
444 return static_cast<AXPlatformNodeWin*>(platform_node)->GetData().id;
445 });
446
447 std::vector<AXPlatformNodeWin*> platform_node_list =
448 CreatePlatformNodeVectorFromRelationIdVector(id_list);
449
450 return CreateUIAElementsSafeArray(platform_node_list);
451}
452
453SAFEARRAY* AXPlatformNodeWin::CreateClickablePointArray() {
454 SAFEARRAY* clickable_point_array = SafeArrayCreateVector(VT_R8, 0, 2);
455 gfx::Point center = GetDelegate()
456 ->GetBoundsRect(AXCoordinateSystem::kScreenDIPs,
458 .CenterPoint();
459
460 double* double_array;
461 SafeArrayAccessData(clickable_point_array,
462 reinterpret_cast<void**>(&double_array));
463 double_array[0] = center.x();
464 double_array[1] = center.y();
465 SafeArrayUnaccessData(clickable_point_array);
466
467 return clickable_point_array;
468}
469
470gfx::Vector2d AXPlatformNodeWin::CalculateUIAScrollPoint(
471 const ScrollAmount horizontal_amount,
472 const ScrollAmount vertical_amount) const {
473 if (!GetDelegate() || !IsScrollable())
474 return {};
475
476 const gfx::Rect bounds = GetDelegate()->GetBoundsRect(
478 const int large_horizontal_change = bounds.width();
479 const int large_vertical_change = bounds.height();
480
481 const HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
482 BASE_DCHECK(hwnd);
483 const float scale_factor = base::win::GetScaleFactorForHWND(hwnd);
484 const int small_change =
485 base::ClampRound(kSmallScrollIncrement * scale_factor);
486
487 const int x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
488 const int x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
489 const int y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
490 const int y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
491
492 int x = GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
493 int y = GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
494
495 switch (horizontal_amount) {
496 case ScrollAmount_LargeDecrement:
497 x -= large_horizontal_change;
498 break;
499 case ScrollAmount_LargeIncrement:
500 x += large_horizontal_change;
501 break;
502 case ScrollAmount_NoAmount:
503 break;
504 case ScrollAmount_SmallDecrement:
505 x -= small_change;
506 break;
507 case ScrollAmount_SmallIncrement:
508 x += small_change;
509 break;
510 }
511 x = std::min(x, x_max);
512 x = std::max(x, x_min);
513
514 switch (vertical_amount) {
515 case ScrollAmount_LargeDecrement:
516 y -= large_vertical_change;
517 break;
518 case ScrollAmount_LargeIncrement:
519 y += large_vertical_change;
520 break;
521 case ScrollAmount_NoAmount:
522 break;
523 case ScrollAmount_SmallDecrement:
524 y -= small_change;
525 break;
526 case ScrollAmount_SmallIncrement:
527 y += small_change;
528 break;
529 }
530 y = std::min(y, y_max);
531 y = std::max(y, y_min);
532
533 return {x, y};
534}
535
536//
537// AXPlatformNodeBase implementation.
538//
539
540void AXPlatformNodeWin::Dispose() {
541 Release();
542}
543
544void AXPlatformNodeWin::Destroy() {
545 RemoveAlertTarget();
546
547 // This will end up calling Dispose() which may result in deleting this object
548 // if there are no more outstanding references.
550}
551
552//
553// AXPlatformNode implementation.
554//
555
556gfx::NativeViewAccessible AXPlatformNodeWin::GetNativeViewAccessible() {
557 return this;
558}
559
560void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
562 // Menu items fire selection events but Windows screen readers work reliably
563 // with focus events. Remap here.
565 // A menu item could have something other than a role of
566 // |ROLE_SYSTEM_MENUITEM|. Zoom modification controls for example have a
567 // role of button.
568 auto* parent =
569 static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(GetParent()));
570 int role = MSAARole();
571 if (role == ROLE_SYSTEM_MENUITEM) {
573 } else if (role == ROLE_SYSTEM_LISTITEM) {
574 if (AXPlatformNodeBase* container = GetSelectionContainer()) {
575 const AXNodeData& data = container->GetData();
576 if (data.role == ax::mojom::Role::kListBox &&
578 GetDelegate()->GetFocus() == GetNativeViewAccessible()) {
580 }
581 }
582 } else if (parent) {
583 int parent_role = parent->MSAARole();
584 if (parent_role == ROLE_SYSTEM_MENUPOPUP ||
585 parent_role == ROLE_SYSTEM_LIST) {
587 }
588 }
589 }
590
592 // For the IAccessibleText interface to work on non-web content nodes, we
593 // need to update the nodes' hypertext
594 // when the value changes. Otherwise, for web and PDF content, this is
595 // handled by "BrowserAccessibilityComWin".
596 if (!GetDelegate()->IsWebContent())
597 UpdateComputedHypertext();
598 }
599
600 if (std::optional<DWORD> native_event = MojoEventToMSAAEvent(event_type)) {
601 HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
602 if (!hwnd)
603 return;
604
605 ::NotifyWinEvent((*native_event), hwnd, OBJID_CLIENT, -GetUniqueId());
606 }
607
608 if (std::optional<PROPERTYID> uia_property =
609 MojoEventToUIAProperty(event_type)) {
610 // For this event, we're not concerned with the old value.
611 base::win::ScopedVariant old_value;
612 ::VariantInit(old_value.Receive());
613 base::win::ScopedVariant new_value;
614 ::VariantInit(new_value.Receive());
615 GetPropertyValueImpl((*uia_property), new_value.Receive());
616 ::UiaRaiseAutomationPropertyChangedEvent(this, (*uia_property), old_value,
617 new_value);
618 }
619
620 if (std::optional<EVENTID> uia_event = MojoEventToUIAEvent(event_type))
621 ::UiaRaiseAutomationEvent(this, (*uia_event));
622
623 // Keep track of objects that are a target of an alert event.
625 AddAlertTarget();
626}
627
628bool AXPlatformNodeWin::HasActiveComposition() const {
629 return active_composition_range_.end() > active_composition_range_.start();
630}
631
632gfx::Range AXPlatformNodeWin::GetActiveCompositionOffsets() const {
633 return active_composition_range_;
634}
635
636void AXPlatformNodeWin::OnActiveComposition(
637 const gfx::Range& range,
638 const std::u16string& active_composition_text,
639 bool is_composition_committed) {
640 // Cache the composition range that will be used when
641 // GetActiveComposition and GetConversionTarget is called in
642 // AXPlatformNodeTextProviderWin
643 active_composition_range_ = range;
644 // Fire the UiaTextEditTextChangedEvent
645 FireUiaTextEditTextChangedEvent(range, active_composition_text,
646 is_composition_committed);
647}
648
649void AXPlatformNodeWin::FireUiaTextEditTextChangedEvent(
650 const gfx::Range& range,
651 const std::u16string& active_composition_text,
652 bool is_composition_committed) {
653 // This API is only supported from Win8.1 onwards
654 // Check if the function pointer is valid or not
655 using UiaRaiseTextEditTextChangedEventFunction = HRESULT(WINAPI*)(
656 IRawElementProviderSimple*, TextEditChangeType, SAFEARRAY*);
657 UiaRaiseTextEditTextChangedEventFunction text_edit_text_changed_func =
658 reinterpret_cast<UiaRaiseTextEditTextChangedEventFunction>(
659 ::GetProcAddress(GetModuleHandleW(kUIADLLFilename),
660 "UiaRaiseTextEditTextChangedEvent"));
661 if (!text_edit_text_changed_func) {
662 return;
663 }
664
665 TextEditChangeType text_edit_change_type =
666 is_composition_committed ? TextEditChangeType_CompositionFinalized
667 : TextEditChangeType_Composition;
668
669 // Composition has been finalized by TSF
670 base::win::ScopedBstr composition_text(
671 (wchar_t*)active_composition_text.data());
672 base::win::ScopedSafearray changed_data(
673 ::SafeArrayCreateVector(VT_BSTR /* element type */, 0 /* lower bound */,
674 1 /* number of elements */));
675 if (!changed_data.Get()) {
676 return;
677 }
678
679 LONG index = 0;
680 HRESULT hr =
681 SafeArrayPutElement(changed_data.Get(), &index, composition_text.Get());
682
683 if (FAILED(hr)) {
684 return;
685 } else {
686 // Fire the UiaRaiseTextEditTextChangedEvent
687 text_edit_text_changed_func(this, text_edit_change_type,
688 changed_data.Release());
689 }
690}
691
692bool AXPlatformNodeWin::IsValidUiaRelationTarget(
693 AXPlatformNode* ax_platform_node) {
694 if (!ax_platform_node)
695 return false;
696 if (!ax_platform_node->GetDelegate())
697 return false;
698
699 // This is needed for get_FragmentRoot.
700 if (!ax_platform_node->GetDelegate()->GetTargetForNativeAccessibilityEvent())
701 return false;
702
703 return true;
704}
705
706//
707// IAccessible implementation.
708//
709
710IFACEMETHODIMP AXPlatformNodeWin::accHitTest(LONG screen_physical_pixel_x,
711 LONG screen_physical_pixel_y,
712 VARIANT* child) {
714
715 gfx::Point point(screen_physical_pixel_x, screen_physical_pixel_y);
716 if (!GetDelegate()
719 .Contains(point)) {
720 // Return S_FALSE and VT_EMPTY when outside the object's boundaries.
721 child->vt = VT_EMPTY;
722 return S_FALSE;
723 }
724
725 AXPlatformNode* current_result = this;
726 while (true) {
727 gfx::NativeViewAccessible hit_child =
728 current_result->GetDelegate()->HitTestSync(screen_physical_pixel_x,
729 screen_physical_pixel_y);
730 if (!hit_child) {
731 child->vt = VT_EMPTY;
732 return S_FALSE;
733 }
734
735 AXPlatformNode* hit_child_node =
737 if (!hit_child_node)
738 break;
739
740 // If we get the same node, we're done.
741 if (hit_child_node == current_result)
742 break;
743
744 // Prevent cycles / loops.
745 //
746 // This is a workaround for a bug where a hit test in web content might
747 // return a node that's not a strict descendant. To catch that case
748 // without disallowing other valid cases of hit testing, add the
749 // following check:
750 //
751 // If the hit child comes from the same HWND, but it's not a descendant,
752 // just ignore the result and stick with the current result. Note that
753 // GetTargetForNativeAccessibilityEvent returns a node's owning HWND.
754 //
755 // Ideally this shouldn't happen - see http://crbug.com/1061323
756 bool is_descendant = hit_child_node->IsDescendantOf(current_result);
757 bool is_same_hwnd =
758 hit_child_node->GetDelegate()->GetTargetForNativeAccessibilityEvent() ==
759 current_result->GetDelegate()->GetTargetForNativeAccessibilityEvent();
760 if (!is_descendant && is_same_hwnd)
761 break;
762
763 // Continue to check recursively. That's because HitTestSync may have
764 // returned the best result within a particular accessibility tree,
765 // but we might need to recurse further in a tree of a different type
766 // (for example, from Views to Web).
767 current_result = hit_child_node;
768 }
769
770 if (current_result == this) {
771 // This object is the best match, so return CHILDID_SELF. It's tempting to
772 // simplify the logic and use VT_DISPATCH everywhere, but the Windows
773 // call AccessibleObjectFromPoint will keep calling accHitTest until some
774 // object returns CHILDID_SELF.
775 child->vt = VT_I4;
776 child->lVal = CHILDID_SELF;
777 return S_OK;
778 }
779
780 child->vt = VT_DISPATCH;
781 child->pdispVal = static_cast<AXPlatformNodeWin*>(current_result);
782 // Always increment ref when returning a reference to a COM object.
783 child->pdispVal->AddRef();
784 return S_OK;
785}
786
787IFACEMETHODIMP AXPlatformNodeWin::accDoDefaultAction(VARIANT var_id) {
788 AXPlatformNodeWin* target;
790 AXActionData data;
792
793 if (target->GetDelegate()->AccessibilityPerformAction(data))
794 return S_OK;
795 return E_FAIL;
796}
797
798IFACEMETHODIMP AXPlatformNodeWin::accLocation(LONG* physical_pixel_left,
799 LONG* physical_pixel_top,
800 LONG* width,
801 LONG* height,
802 VARIANT var_id) {
803 AXPlatformNodeWin* target;
805 var_id, physical_pixel_left, physical_pixel_top, width, height, target);
806
807 gfx::Rect bounds = target->GetDelegate()->GetBoundsRect(
810 *physical_pixel_left = bounds.x();
811 *physical_pixel_top = bounds.y();
812 *width = bounds.width();
813 *height = bounds.height();
814
815 if (bounds.IsEmpty())
816 return S_FALSE;
817
818 return S_OK;
819}
820
821IFACEMETHODIMP AXPlatformNodeWin::accNavigate(LONG nav_dir,
822 VARIANT start,
823 VARIANT* end) {
824 AXPlatformNodeWin* target;
826 end->vt = VT_EMPTY;
827 if ((nav_dir == NAVDIR_FIRSTCHILD || nav_dir == NAVDIR_LASTCHILD) &&
828 V_VT(&start) == VT_I4 && V_I4(&start) != CHILDID_SELF) {
829 // MSAA states that navigating to first/last child can only be from self.
830 return E_INVALIDARG;
831 }
832
833 IAccessible* result = nullptr;
834 switch (nav_dir) {
835 case NAVDIR_FIRSTCHILD:
836 if (GetDelegate()->GetChildCount() > 0)
837 result = GetDelegate()->GetFirstChild();
838 break;
839
840 case NAVDIR_LASTCHILD:
841 if (GetDelegate()->GetChildCount() > 0)
842 result = GetDelegate()->GetLastChild();
843 break;
844
845 case NAVDIR_NEXT: {
846 AXPlatformNodeBase* next = target->GetNextSibling();
847 if (next)
848 result = next->GetNativeViewAccessible();
849 break;
850 }
851
852 case NAVDIR_PREVIOUS: {
853 AXPlatformNodeBase* previous = target->GetPreviousSibling();
854 if (previous)
855 result = previous->GetNativeViewAccessible();
856 break;
857 }
858
859 case NAVDIR_DOWN: {
860 // This direction is not implemented except in tables.
861 if (!GetTableRow() || !GetTableRowSpan() || !GetTableColumn())
862 return E_NOTIMPL;
863
864 AXPlatformNodeBase* next = target->GetTableCell(
865 *GetTableRow() + *GetTableRowSpan(), *GetTableColumn());
866 if (!next)
867 return S_OK;
868
869 result = next->GetNativeViewAccessible();
870 break;
871 }
872
873 case NAVDIR_UP: {
874 // This direction is not implemented except in tables.
875 if (!GetTableRow() || !GetTableColumn())
876 return E_NOTIMPL;
877
878 AXPlatformNodeBase* next =
879 target->GetTableCell(*GetTableRow() - 1, *GetTableColumn());
880 if (!next)
881 return S_OK;
882
883 result = next->GetNativeViewAccessible();
884 break;
885 }
886
887 case NAVDIR_LEFT: {
888 // This direction is not implemented except in tables.
889 if (!GetTableRow() || !GetTableColumn())
890 return E_NOTIMPL;
891
892 AXPlatformNodeBase* next =
893 target->GetTableCell(*GetTableRow(), *GetTableColumn() - 1);
894 if (!next)
895 return S_OK;
896
897 result = next->GetNativeViewAccessible();
898 break;
899 }
900
901 case NAVDIR_RIGHT: {
902 // This direction is not implemented except in tables.
903
904 if (!GetTableRow() || !GetTableColumn() || !GetTableColumnSpan())
905 return E_NOTIMPL;
906
907 AXPlatformNodeBase* next = target->GetTableCell(
908 *GetTableRow(), *GetTableColumn() + *GetTableColumnSpan());
909 if (!next)
910 return S_OK;
911
912 result = next->GetNativeViewAccessible();
913 break;
914 }
915 }
916
917 if (!result)
918 return S_FALSE;
919
920 end->vt = VT_DISPATCH;
921 end->pdispVal = result;
922 // Always increment ref when returning a reference to a COM object.
923 end->pdispVal->AddRef();
924
925 return S_OK;
926}
927
928IFACEMETHODIMP AXPlatformNodeWin::get_accChild(VARIANT var_child,
929 IDispatch** disp_child) {
930 *disp_child = nullptr;
931 AXPlatformNodeWin* target;
933
934 *disp_child = target;
935 (*disp_child)->AddRef();
936 return S_OK;
937}
938
939IFACEMETHODIMP AXPlatformNodeWin::get_accChildCount(LONG* child_count) {
940 COM_OBJECT_VALIDATE_1_ARG(child_count);
941 *child_count = GetDelegate()->GetChildCount();
942 return S_OK;
943}
944
945IFACEMETHODIMP AXPlatformNodeWin::get_accDefaultAction(VARIANT var_id,
946 BSTR* def_action) {
947 AXPlatformNodeWin* target;
950
951 int action;
953 &action)) {
954 *def_action = nullptr;
955 return S_FALSE;
956 }
957
958 // TODO(gw280): https://github.com/flutter/flutter/issues/78799
959 // Use localized strings
960 std::u16string action_verb = base::UTF8ToUTF16(
962 if (action_verb.empty()) {
963 *def_action = nullptr;
964 return S_FALSE;
965 }
966
967 *def_action = ::SysAllocString(fml::Utf16ToWideString(action_verb).c_str());
968 BASE_DCHECK(def_action);
969 return S_OK;
970}
971
972IFACEMETHODIMP AXPlatformNodeWin::get_accDescription(VARIANT var_id,
973 BSTR* desc) {
974 AXPlatformNodeWin* target;
976
977 return target->GetStringAttributeAsBstr(
979}
980
981IFACEMETHODIMP AXPlatformNodeWin::get_accFocus(VARIANT* focus_child) {
982 COM_OBJECT_VALIDATE_1_ARG(focus_child);
983 gfx::NativeViewAccessible focus_accessible = GetDelegate()->GetFocus();
984 if (focus_accessible == this) {
985 focus_child->vt = VT_I4;
986 focus_child->lVal = CHILDID_SELF;
987 } else if (focus_accessible) {
988 Microsoft::WRL::ComPtr<IDispatch> focus_idispatch;
989 if (FAILED(
990 focus_accessible->QueryInterface(IID_PPV_ARGS(&focus_idispatch)))) {
991 focus_child->vt = VT_EMPTY;
992 return E_FAIL;
993 }
994
995 focus_child->vt = VT_DISPATCH;
996 focus_child->pdispVal = focus_idispatch.Detach();
997 } else {
998 focus_child->vt = VT_EMPTY;
999 }
1000
1001 return S_OK;
1002}
1003
1004IFACEMETHODIMP AXPlatformNodeWin::get_accKeyboardShortcut(VARIANT var_id,
1005 BSTR* acc_key) {
1006 AXPlatformNodeWin* target;
1008
1009 return target->GetStringAttributeAsBstr(
1011}
1012
1013IFACEMETHODIMP AXPlatformNodeWin::get_accName(VARIANT var_id, BSTR* name_bstr) {
1014 AXPlatformNodeWin* target;
1016
1017 if (!IsNameExposed())
1018 return S_FALSE;
1019
1020 bool has_name = target->HasStringAttribute(ax::mojom::StringAttribute::kName);
1021 std::u16string name = target->GetNameAsString16();
1022
1023 // Simply appends the tooltip, if any, to the end of the MSAA name.
1024 const std::u16string tooltip =
1025 target->GetString16Attribute(ax::mojom::StringAttribute::kTooltip);
1026 if (!tooltip.empty()) {
1027 AppendTextToString(tooltip, &name);
1028 }
1029
1030 auto status = GetData().GetImageAnnotationStatus();
1031 switch (status) {
1036 break;
1037
1043 AppendTextToString(
1044 GetDelegate()->GetLocalizedStringForImageAnnotationStatus(status),
1045 &name);
1046 break;
1047
1049 AppendTextToString(
1051 &name);
1052 break;
1053 }
1054
1055 if (name.empty() && !has_name)
1056 return S_FALSE;
1057
1058 *name_bstr = ::SysAllocString(fml::Utf16ToWideString(name).c_str());
1059 return S_OK;
1060}
1061
1062IFACEMETHODIMP AXPlatformNodeWin::get_accParent(IDispatch** disp_parent) {
1063 COM_OBJECT_VALIDATE_1_ARG(disp_parent);
1064 *disp_parent = GetParent();
1065 if (*disp_parent) {
1066 (*disp_parent)->AddRef();
1067 return S_OK;
1068 }
1069 IRawElementProviderFragmentRoot* root;
1070 if (SUCCEEDED(get_FragmentRoot(&root))) {
1072 if (SUCCEEDED(root->QueryInterface(IID_PPV_ARGS(&parent)))) {
1073 if (parent && parent != GetNativeViewAccessible()) {
1074 *disp_parent = parent;
1075 parent->AddRef();
1076 return S_OK;
1077 }
1078 }
1079 }
1080 return S_FALSE;
1081}
1082
1083IFACEMETHODIMP AXPlatformNodeWin::get_accRole(VARIANT var_id, VARIANT* role) {
1084 AXPlatformNodeWin* target;
1086
1087 role->vt = VT_I4;
1088 role->lVal = target->MSAARole();
1089 return S_OK;
1090}
1091
1092IFACEMETHODIMP AXPlatformNodeWin::get_accState(VARIANT var_id, VARIANT* state) {
1093 AXPlatformNodeWin* target;
1095 state->vt = VT_I4;
1096 state->lVal = target->MSAAState();
1097 return S_OK;
1098}
1099
1100IFACEMETHODIMP AXPlatformNodeWin::get_accHelp(VARIANT var_id, BSTR* help) {
1102 return S_FALSE;
1103}
1104
1105IFACEMETHODIMP AXPlatformNodeWin::get_accValue(VARIANT var_id, BSTR* value) {
1106 AXPlatformNodeWin* target;
1108 *value = GetValueAttributeAsBstr(target);
1109 return S_OK;
1110}
1111
1112IFACEMETHODIMP AXPlatformNodeWin::put_accValue(VARIANT var_id, BSTR new_value) {
1113 AXPlatformNodeWin* target;
1115 if (!new_value)
1116 return E_INVALIDARG;
1117
1118 std::u16string new_value_utf16((char16_t*)new_value);
1119 AXActionData data;
1121 data.value = base::UTF16ToUTF8(new_value_utf16);
1122 if (target->GetDelegate()->AccessibilityPerformAction(data))
1123 return S_OK;
1124 return E_FAIL;
1125}
1126
1127IFACEMETHODIMP AXPlatformNodeWin::get_accSelection(VARIANT* selected) {
1128 COM_OBJECT_VALIDATE_1_ARG(selected);
1129 std::vector<Microsoft::WRL::ComPtr<IDispatch>> selected_nodes;
1130 for (int i = 0; i < GetDelegate()->GetChildCount(); ++i) {
1131 auto* node = static_cast<AXPlatformNodeWin*>(
1132 FromNativeViewAccessible(GetDelegate()->ChildAtIndex(i)));
1133 if (node &&
1134 node->GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
1135 Microsoft::WRL::ComPtr<IDispatch> node_idispatch;
1136 if (SUCCEEDED(node->QueryInterface(IID_PPV_ARGS(&node_idispatch))))
1137 selected_nodes.push_back(node_idispatch);
1138 }
1139 }
1140
1141 if (selected_nodes.empty()) {
1142 selected->vt = VT_EMPTY;
1143 return S_OK;
1144 }
1145
1146 if (selected_nodes.size() == 1) {
1147 selected->vt = VT_DISPATCH;
1148 selected->pdispVal = selected_nodes[0].Detach();
1149 return S_OK;
1150 }
1151
1152 // Multiple items are selected.
1153 LONG selected_count = static_cast<LONG>(selected_nodes.size());
1154 Microsoft::WRL::ComPtr<base::win::EnumVariant> enum_variant =
1155 Microsoft::WRL::Make<base::win::EnumVariant>(selected_count);
1156 for (LONG i = 0; i < selected_count; ++i) {
1157 enum_variant->ItemAt(i)->vt = VT_DISPATCH;
1158 enum_variant->ItemAt(i)->pdispVal = selected_nodes[i].Detach();
1159 }
1160 selected->vt = VT_UNKNOWN;
1161 return enum_variant.CopyTo(IID_PPV_ARGS(&V_UNKNOWN(selected)));
1162}
1163
1164IFACEMETHODIMP AXPlatformNodeWin::accSelect(LONG flagsSelect, VARIANT var_id) {
1165 AXPlatformNodeWin* target;
1167
1168 if (flagsSelect & SELFLAG_TAKEFOCUS) {
1169 AXActionData action_data;
1170 action_data.action = ax::mojom::Action::kFocus;
1171 target->GetDelegate()->AccessibilityPerformAction(action_data);
1172 return S_OK;
1173 }
1174
1175 return S_FALSE;
1176}
1177
1178IFACEMETHODIMP AXPlatformNodeWin::get_accHelpTopic(BSTR* help_file,
1179 VARIANT var_id,
1180 LONG* topic_id) {
1181 AXPlatformNodeWin* target;
1182 COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, help_file, topic_id,
1183 target);
1184 if (help_file) {
1185 *help_file = nullptr;
1186 }
1187 if (topic_id) {
1188 *topic_id = static_cast<LONG>(-1);
1189 }
1190 return E_NOTIMPL;
1191}
1192
1193IFACEMETHODIMP AXPlatformNodeWin::put_accName(VARIANT var_id, BSTR put_name) {
1194 // TODO(dougt): We may want to collect an API histogram here.
1195 // Deprecated.
1196 return E_NOTIMPL;
1197}
1198
1199//
1200// IAccessibleEx implementation.
1201//
1202
1203IFACEMETHODIMP AXPlatformNodeWin::GetObjectForChild(LONG child_id,
1204 IAccessibleEx** result) {
1205 // No support for child IDs in this implementation.
1207 *result = nullptr;
1208 return S_OK;
1209}
1210
1211IFACEMETHODIMP AXPlatformNodeWin::GetIAccessiblePair(IAccessible** accessible,
1212 LONG* child_id) {
1214 *accessible = static_cast<IAccessible*>(this);
1215 (*accessible)->AddRef();
1216 *child_id = CHILDID_SELF;
1217 return S_OK;
1218}
1219
1220// IAccessibleEx methods not implemented.
1221IFACEMETHODIMP
1222AXPlatformNodeWin::ConvertReturnedElement(IRawElementProviderSimple* element,
1223 IAccessibleEx** acc) {
1224 return E_NOTIMPL;
1225}
1226
1227//
1228// IExpandCollapseProvider implementation.
1229//
1230
1231IFACEMETHODIMP AXPlatformNodeWin::Collapse() {
1233 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1234 return UIA_E_ELEMENTNOTAVAILABLE;
1235
1236 if (GetData().HasState(ax::mojom::State::kCollapsed))
1237 return UIA_E_INVALIDOPERATION;
1238
1239 AXActionData action_data;
1240 action_data.action = ax::mojom::Action::kDoDefault;
1241 if (GetDelegate()->AccessibilityPerformAction(action_data))
1242 return S_OK;
1243 return E_FAIL;
1244}
1245
1246IFACEMETHODIMP AXPlatformNodeWin::Expand() {
1248 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1249 return UIA_E_ELEMENTNOTAVAILABLE;
1250
1251 if (GetData().HasState(ax::mojom::State::kExpanded))
1252 return UIA_E_INVALIDOPERATION;
1253
1254 AXActionData action_data;
1255 action_data.action = ax::mojom::Action::kDoDefault;
1256 if (GetDelegate()->AccessibilityPerformAction(action_data))
1257 return S_OK;
1258 return E_FAIL;
1259}
1260
1261ExpandCollapseState AXPlatformNodeWin::ComputeExpandCollapseState() const {
1262 const AXNodeData& data = GetData();
1263
1264 // Since a menu button implies there is a popup and it is either expanded or
1265 // collapsed, and it should not support ExpandCollapseState_LeafNode.
1266 // According to the UIA spec, ExpandCollapseState_LeafNode indicates that the
1267 // element neither expands nor collapses.
1268 if (data.IsMenuButton()) {
1269 if (data.IsButtonPressed())
1270 return ExpandCollapseState_Expanded;
1271 return ExpandCollapseState_Collapsed;
1272 }
1273
1274 if (data.HasState(ax::mojom::State::kExpanded)) {
1275 return ExpandCollapseState_Expanded;
1276 } else if (data.HasState(ax::mojom::State::kCollapsed)) {
1277 return ExpandCollapseState_Collapsed;
1278 } else {
1279 return ExpandCollapseState_LeafNode;
1280 }
1281}
1282
1283IFACEMETHODIMP AXPlatformNodeWin::get_ExpandCollapseState(
1284 ExpandCollapseState* result) {
1286
1287 *result = ComputeExpandCollapseState();
1288
1289 return S_OK;
1290}
1291
1292//
1293// IGridItemProvider implementation.
1294//
1295
1296IFACEMETHODIMP AXPlatformNodeWin::get_Column(int* result) {
1298 std::optional<int> column = GetTableColumn();
1299 if (!column)
1300 return E_FAIL;
1301 *result = *column;
1302 return S_OK;
1303}
1304
1305IFACEMETHODIMP AXPlatformNodeWin::get_ColumnSpan(int* result) {
1307 std::optional<int> column_span = GetTableColumnSpan();
1308 if (!column_span)
1309 return E_FAIL;
1310 *result = *column_span;
1311 return S_OK;
1312}
1313
1314IFACEMETHODIMP AXPlatformNodeWin::get_ContainingGrid(
1315 IRawElementProviderSimple** result) {
1317
1318 AXPlatformNodeBase* table = GetTable();
1319 if (!table)
1320 return E_FAIL;
1321
1322 auto* node_win = static_cast<AXPlatformNodeWin*>(table);
1323 node_win->AddRef();
1324 *result = static_cast<IRawElementProviderSimple*>(node_win);
1325 return S_OK;
1326}
1327
1328IFACEMETHODIMP AXPlatformNodeWin::get_Row(int* result) {
1330 std::optional<int> row = GetTableRow();
1331 if (!row)
1332 return E_FAIL;
1333 *result = *row;
1334 return S_OK;
1335}
1336
1337IFACEMETHODIMP AXPlatformNodeWin::get_RowSpan(int* result) {
1339 std::optional<int> row_span = GetTableRowSpan();
1340 if (!row_span)
1341 return E_FAIL;
1342 *result = *row_span;
1343 return S_OK;
1344}
1345
1346//
1347// IGridProvider implementation.
1348//
1349
1350IFACEMETHODIMP AXPlatformNodeWin::GetItem(int row,
1351 int column,
1352 IRawElementProviderSimple** result) {
1354
1355 AXPlatformNodeBase* cell = GetTableCell(row, column);
1356 if (!cell)
1357 return E_INVALIDARG;
1358
1359 auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
1360 node_win->AddRef();
1361 *result = static_cast<IRawElementProviderSimple*>(node_win);
1362 return S_OK;
1363}
1364
1365IFACEMETHODIMP AXPlatformNodeWin::get_RowCount(int* result) {
1367
1368 std::optional<int> row_count = GetTableAriaRowCount();
1369 if (!row_count)
1370 row_count = GetTableRowCount();
1371
1372 if (!row_count || *row_count == ax::mojom::kUnknownAriaColumnOrRowCount)
1373 return E_UNEXPECTED;
1374 *result = *row_count;
1375 return S_OK;
1376}
1377
1378IFACEMETHODIMP AXPlatformNodeWin::get_ColumnCount(int* result) {
1380
1381 std::optional<int> column_count = GetTableAriaColumnCount();
1382 if (!column_count)
1383 column_count = GetTableColumnCount();
1384
1385 if (!column_count ||
1386 *column_count == ax::mojom::kUnknownAriaColumnOrRowCount) {
1387 return E_UNEXPECTED;
1388 }
1389 *result = *column_count;
1390 return S_OK;
1391}
1392
1393//
1394// IInvokeProvider implementation.
1395//
1396
1397IFACEMETHODIMP AXPlatformNodeWin::Invoke() {
1399
1400 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1401 return UIA_E_ELEMENTNOTENABLED;
1402
1403 AXActionData action_data;
1404 action_data.action = ax::mojom::Action::kDoDefault;
1405 GetDelegate()->AccessibilityPerformAction(action_data);
1406
1407 return S_OK;
1408}
1409
1410//
1411// IScrollItemProvider implementation.
1412//
1413
1414IFACEMETHODIMP AXPlatformNodeWin::ScrollIntoView() {
1416 gfx::Rect r = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
1417 r -= r.OffsetFromOrigin();
1418
1419 AXActionData action_data;
1420 action_data.target_node_id = GetData().id;
1421 action_data.target_rect = r;
1422 action_data.horizontal_scroll_alignment =
1424 action_data.vertical_scroll_alignment =
1426 action_data.scroll_behavior =
1428 if (GetDelegate()->AccessibilityPerformAction(action_data))
1429 return S_OK;
1430 return E_FAIL;
1431}
1432
1433//
1434// IScrollProvider implementation.
1435//
1436
1437IFACEMETHODIMP AXPlatformNodeWin::Scroll(ScrollAmount horizontal_amount,
1438 ScrollAmount vertical_amount) {
1440 if (!IsScrollable())
1441 return E_FAIL;
1442
1443 AXActionData action_data;
1444 action_data.target_node_id = GetData().id;
1445 action_data.action = ax::mojom::Action::kSetScrollOffset;
1446 action_data.target_point = gfx::PointAtOffsetFromOrigin(
1447 CalculateUIAScrollPoint(horizontal_amount, vertical_amount));
1448 if (GetDelegate()->AccessibilityPerformAction(action_data))
1449 return S_OK;
1450 return E_FAIL;
1451}
1452
1453IFACEMETHODIMP AXPlatformNodeWin::SetScrollPercent(double horizontal_percent,
1454 double vertical_percent) {
1456 if (!IsScrollable())
1457 return E_FAIL;
1458
1459 const double x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
1460 const double x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
1461 const double y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
1462 const double y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
1463 const int x =
1464 base::ClampRound(horizontal_percent / 100.0 * (x_max - x_min) + x_min);
1465 const int y =
1466 base::ClampRound(vertical_percent / 100.0 * (y_max - y_min) + y_min);
1467 const gfx::Point scroll_to(x, y);
1468
1469 AXActionData action_data;
1470 action_data.target_node_id = GetData().id;
1471 action_data.action = ax::mojom::Action::kSetScrollOffset;
1472 action_data.target_point = scroll_to;
1473 if (GetDelegate()->AccessibilityPerformAction(action_data))
1474 return S_OK;
1475 return E_FAIL;
1476}
1477
1478IFACEMETHODIMP AXPlatformNodeWin::get_HorizontallyScrollable(BOOL* result) {
1480 *result = IsHorizontallyScrollable();
1481 return S_OK;
1482}
1483
1484IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalScrollPercent(double* result) {
1486 *result = GetHorizontalScrollPercent();
1487 return S_OK;
1488}
1489
1490// Horizontal size of the viewable region as a percentage of the total content
1491// area.
1492IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalViewSize(double* result) {
1494 if (!IsHorizontallyScrollable()) {
1495 *result = 100.;
1496 return S_OK;
1497 }
1498
1499 gfx::RectF clipped_bounds(GetDelegate()->GetBoundsRect(
1501 float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
1502 float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
1503 float total_width = clipped_bounds.width() + x_max - x_min;
1504 BASE_DCHECK(clipped_bounds.width() <= total_width);
1505 *result = 100.0 * clipped_bounds.width() / total_width;
1506 return S_OK;
1507}
1508
1509IFACEMETHODIMP AXPlatformNodeWin::get_VerticallyScrollable(BOOL* result) {
1511 *result = IsVerticallyScrollable();
1512 return S_OK;
1513}
1514
1515IFACEMETHODIMP AXPlatformNodeWin::get_VerticalScrollPercent(double* result) {
1517 *result = GetVerticalScrollPercent();
1518 return S_OK;
1519}
1520
1521// Vertical size of the viewable region as a percentage of the total content
1522// area.
1523IFACEMETHODIMP AXPlatformNodeWin::get_VerticalViewSize(double* result) {
1525 if (!IsVerticallyScrollable()) {
1526 *result = 100.0;
1527 return S_OK;
1528 }
1529
1530 gfx::RectF clipped_bounds(GetDelegate()->GetBoundsRect(
1532 float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
1533 float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
1534 float total_height = clipped_bounds.height() + y_max - y_min;
1535 BASE_DCHECK(clipped_bounds.height() <= total_height);
1536 *result = 100.0 * clipped_bounds.height() / total_height;
1537 return S_OK;
1538}
1539
1540//
1541// ISelectionItemProvider implementation.
1542//
1543
1544HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(
1545 bool selected) const {
1547 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1548 return UIA_E_ELEMENTNOTENABLED;
1549
1550 // The platform implements selection follows focus for single-selection
1551 // container elements. Focus action can change a node's accessibility selected
1552 // state, but does not cause the actual control to be selected.
1553 // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
1554 // https://www.w3.org/TR/core-aam-1.2/#mapping_events_selection
1555 //
1556 // We don't want to perform |Action::kDoDefault| for an ax node that has
1557 // |kSelected=true| and |kSelectedFromFocus=false|, because perform
1558 // |Action::kDoDefault| may cause the control to be unselected. However, if an
1559 // ax node is selected due to focus, i.e. |kSelectedFromFocus=true|, we need
1560 // to perform |Action::kDoDefault| on the ax node, since focus action only
1561 // changes an ax node's accessibility selected state to |kSelected=true| and
1562 // no |Action::kDoDefault| was performed on that node yet. So we need to
1563 // perform |Action::kDoDefault| on the ax node to cause its associated control
1564 // to be selected.
1565 if (selected == ISelectionItemProviderIsSelected() &&
1567 return S_OK;
1568
1569 AXActionData data;
1571 if (GetDelegate()->AccessibilityPerformAction(data))
1572 return S_OK;
1573 return UIA_E_INVALIDOPERATION;
1574}
1575
1576bool AXPlatformNodeWin::ISelectionItemProviderIsSelected() const {
1577 // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
1578 // SelectionItem.IsSelected is set according to the True or False value of
1579 // aria-checked for 'radio' and 'menuitemradio' roles.
1580 if (GetData().role == ax::mojom::Role::kRadioButton ||
1581 GetData().role == ax::mojom::Role::kMenuItemRadio)
1582 return GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue;
1583
1584 // https://www.w3.org/TR/wai-aria-1.1/#aria-selected
1585 // SelectionItem.IsSelected is set according to the True or False value of
1586 // aria-selected.
1587 return GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
1588}
1589
1590IFACEMETHODIMP AXPlatformNodeWin::AddToSelection() {
1591 return ISelectionItemProviderSetSelected(true);
1592}
1593
1594IFACEMETHODIMP AXPlatformNodeWin::RemoveFromSelection() {
1595 return ISelectionItemProviderSetSelected(false);
1596}
1597
1598IFACEMETHODIMP AXPlatformNodeWin::Select() {
1599 return ISelectionItemProviderSetSelected(true);
1600}
1601
1602IFACEMETHODIMP AXPlatformNodeWin::get_IsSelected(BOOL* result) {
1604 *result = ISelectionItemProviderIsSelected();
1605 return S_OK;
1606}
1607
1608IFACEMETHODIMP AXPlatformNodeWin::get_SelectionContainer(
1609 IRawElementProviderSimple** result) {
1611
1612 auto* node_win = static_cast<AXPlatformNodeWin*>(GetSelectionContainer());
1613 if (!node_win)
1614 return E_FAIL;
1615
1616 node_win->AddRef();
1617 *result = static_cast<IRawElementProviderSimple*>(node_win);
1618 return S_OK;
1619}
1620
1621//
1622// ISelectionProvider implementation.
1623//
1624
1625IFACEMETHODIMP AXPlatformNodeWin::GetSelection(SAFEARRAY** result) {
1627
1628 std::vector<AXPlatformNodeBase*> selected_children;
1629 int max_items = GetMaxSelectableItems();
1630 if (max_items)
1631 GetSelectedItems(max_items, &selected_children);
1632
1633 LONG selected_children_count = selected_children.size();
1634 *result = SafeArrayCreateVector(VT_UNKNOWN, 0, selected_children_count);
1635 if (!*result)
1636 return E_OUTOFMEMORY;
1637
1638 for (LONG i = 0; i < selected_children_count; ++i) {
1639 AXPlatformNodeWin* children =
1640 static_cast<AXPlatformNodeWin*>(selected_children[i]);
1641 HRESULT hr = SafeArrayPutElement(
1642 *result, &i, static_cast<IRawElementProviderSimple*>(children));
1643 if (FAILED(hr)) {
1644 SafeArrayDestroy(*result);
1645 *result = nullptr;
1646 return hr;
1647 }
1648 }
1649 return S_OK;
1650}
1651
1652IFACEMETHODIMP AXPlatformNodeWin::get_CanSelectMultiple(BOOL* result) {
1654 *result = GetData().HasState(ax::mojom::State::kMultiselectable);
1655 return S_OK;
1656}
1657
1658IFACEMETHODIMP AXPlatformNodeWin::get_IsSelectionRequired(BOOL* result) {
1660 *result = GetData().HasState(ax::mojom::State::kRequired);
1661 return S_OK;
1662}
1663
1664//
1665// ITableItemProvider methods.
1666//
1667
1668IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaderItems(SAFEARRAY** result) {
1670
1671 std::optional<int> column = GetTableColumn();
1672 if (!column)
1673 return E_FAIL;
1674
1675 std::vector<int32_t> column_header_ids =
1676 GetDelegate()->GetColHeaderNodeIds(*column);
1677
1678 std::vector<AXPlatformNodeWin*> platform_node_list =
1679 CreatePlatformNodeVectorFromRelationIdVector(column_header_ids);
1680
1681 *result = CreateUIAElementsSafeArray(platform_node_list);
1682 return S_OK;
1683}
1684
1685IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaderItems(SAFEARRAY** result) {
1687
1688 std::optional<int> row = GetTableRow();
1689 if (!row)
1690 return E_FAIL;
1691
1692 std::vector<int32_t> row_header_ids =
1693 GetDelegate()->GetRowHeaderNodeIds(*row);
1694
1695 std::vector<AXPlatformNodeWin*> platform_node_list =
1696 CreatePlatformNodeVectorFromRelationIdVector(row_header_ids);
1697
1698 *result = CreateUIAElementsSafeArray(platform_node_list);
1699 return S_OK;
1700}
1701
1702//
1703// ITableProvider methods.
1704//
1705
1706IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaders(SAFEARRAY** result) {
1708
1709 std::vector<int32_t> column_header_ids = GetDelegate()->GetColHeaderNodeIds();
1710
1711 std::vector<AXPlatformNodeWin*> platform_node_list =
1712 CreatePlatformNodeVectorFromRelationIdVector(column_header_ids);
1713
1714 *result = CreateUIAElementsSafeArray(platform_node_list);
1715 return S_OK;
1716}
1717
1718IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaders(SAFEARRAY** result) {
1720
1721 std::vector<int32_t> row_header_ids = GetDelegate()->GetRowHeaderNodeIds();
1722
1723 std::vector<AXPlatformNodeWin*> platform_node_list =
1724 CreatePlatformNodeVectorFromRelationIdVector(row_header_ids);
1725
1726 *result = CreateUIAElementsSafeArray(platform_node_list);
1727 return S_OK;
1728}
1729
1730IFACEMETHODIMP AXPlatformNodeWin::get_RowOrColumnMajor(
1731 RowOrColumnMajor* result) {
1733
1734 // Tables and ARIA grids are always in row major order
1735 // see AXPlatformNodeBase::GetTableCell
1736 *result = RowOrColumnMajor_RowMajor;
1737 return S_OK;
1738}
1739
1740//
1741// IToggleProvider implementation.
1742//
1743
1744IFACEMETHODIMP AXPlatformNodeWin::Toggle() {
1746 AXActionData action_data;
1747 action_data.action = ax::mojom::Action::kDoDefault;
1748
1749 if (GetDelegate()->AccessibilityPerformAction(action_data))
1750 return S_OK;
1751 return E_FAIL;
1752}
1753
1754IFACEMETHODIMP AXPlatformNodeWin::get_ToggleState(ToggleState* result) {
1756 const auto checked_state = GetData().GetCheckedState();
1757 if (checked_state == ax::mojom::CheckedState::kTrue) {
1758 *result = ToggleState_On;
1759 } else if (checked_state == ax::mojom::CheckedState::kMixed) {
1760 *result = ToggleState_Indeterminate;
1761 } else {
1762 *result = ToggleState_Off;
1763 }
1764 return S_OK;
1765}
1766
1767//
1768// IValueProvider implementation.
1769//
1770
1771IFACEMETHODIMP AXPlatformNodeWin::SetValue(LPCWSTR value) {
1773 if (!value)
1774 return E_INVALIDARG;
1775
1776 if (GetData().IsReadOnlyOrDisabled())
1777 return UIA_E_ELEMENTNOTENABLED;
1778
1779 AXActionData data;
1782 if (GetDelegate()->AccessibilityPerformAction(data))
1783 return S_OK;
1784 return E_FAIL;
1785}
1786
1787IFACEMETHODIMP AXPlatformNodeWin::get_IsReadOnly(BOOL* result) {
1789 *result = GetData().IsReadOnlyOrDisabled();
1790 return S_OK;
1791}
1792
1793IFACEMETHODIMP AXPlatformNodeWin::get_Value(BSTR* result) {
1795 *result = GetValueAttributeAsBstr(this);
1796 return S_OK;
1797}
1798
1799//
1800// IWindowProvider implementation.
1801//
1802
1803IFACEMETHODIMP AXPlatformNodeWin::SetVisualState(
1804 WindowVisualState window_visual_state) {
1806 return UIA_E_NOTSUPPORTED;
1807}
1808
1809IFACEMETHODIMP AXPlatformNodeWin::Close() {
1811 return UIA_E_NOTSUPPORTED;
1812}
1813
1814IFACEMETHODIMP AXPlatformNodeWin::WaitForInputIdle(int milliseconds,
1815 BOOL* result) {
1817 return UIA_E_NOTSUPPORTED;
1818}
1819
1820IFACEMETHODIMP AXPlatformNodeWin::get_CanMaximize(BOOL* result) {
1822 return UIA_E_NOTSUPPORTED;
1823}
1824
1825IFACEMETHODIMP AXPlatformNodeWin::get_CanMinimize(BOOL* result) {
1827 return UIA_E_NOTSUPPORTED;
1828}
1829
1830IFACEMETHODIMP AXPlatformNodeWin::get_IsModal(BOOL* result) {
1832
1833 *result = GetBoolAttribute(ax::mojom::BoolAttribute::kModal);
1834
1835 return S_OK;
1836}
1837
1838IFACEMETHODIMP AXPlatformNodeWin::get_WindowVisualState(
1839 WindowVisualState* result) {
1841 return UIA_E_NOTSUPPORTED;
1842}
1843
1844IFACEMETHODIMP AXPlatformNodeWin::get_WindowInteractionState(
1845 WindowInteractionState* result) {
1847 return UIA_E_NOTSUPPORTED;
1848}
1849
1850IFACEMETHODIMP AXPlatformNodeWin::get_IsTopmost(BOOL* result) {
1852 return UIA_E_NOTSUPPORTED;
1853}
1854
1855//
1856// IRangeValueProvider implementation.
1857//
1858
1859IFACEMETHODIMP AXPlatformNodeWin::SetValue(double value) {
1861 AXActionData data;
1863 data.value = base::NumberToString(value);
1864 if (GetDelegate()->AccessibilityPerformAction(data))
1865 return S_OK;
1866 return E_FAIL;
1867}
1868
1869IFACEMETHODIMP AXPlatformNodeWin::get_LargeChange(double* result) {
1871 float attribute;
1873 &attribute)) {
1874 *result = attribute * kLargeChangeScaleFactor;
1875 return S_OK;
1876 }
1877 return E_FAIL;
1878}
1879
1880IFACEMETHODIMP AXPlatformNodeWin::get_Maximum(double* result) {
1882 float attribute;
1884 &attribute)) {
1885 *result = attribute;
1886 return S_OK;
1887 }
1888 return E_FAIL;
1889}
1890
1891IFACEMETHODIMP AXPlatformNodeWin::get_Minimum(double* result) {
1893 float attribute;
1895 &attribute)) {
1896 *result = attribute;
1897 return S_OK;
1898 }
1899 return E_FAIL;
1900}
1901
1902IFACEMETHODIMP AXPlatformNodeWin::get_SmallChange(double* result) {
1904 float attribute;
1906 &attribute)) {
1907 *result = attribute;
1908 return S_OK;
1909 }
1910 return E_FAIL;
1911}
1912
1913IFACEMETHODIMP AXPlatformNodeWin::get_Value(double* result) {
1915 float attribute;
1916 if (GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
1917 &attribute)) {
1918 *result = attribute;
1919 return S_OK;
1920 }
1921 return E_FAIL;
1922}
1923
1924//
1925// IRawElementProviderFragment implementation.
1926//
1927
1928IFACEMETHODIMP AXPlatformNodeWin::Navigate(
1929 NavigateDirection direction,
1930 IRawElementProviderFragment** element_provider) {
1931 UIA_VALIDATE_CALL_1_ARG(element_provider);
1932
1933 *element_provider = nullptr;
1934
1935 //
1936 // Navigation to a fragment root node:
1937 //
1938 // In order for the platform-neutral accessibility tree to support IA2 and UIA
1939 // simultaneously, we handle navigation to and from fragment roots in UIA
1940 // specific code. Consider the following platform-neutral tree:
1941 //
1942 // N1
1943 // _____/ \_____
1944 // / \
1945 // N2---N3---N4---N5
1946 // / \ / \
1947 // N6---N7 N8---N9
1948 //
1949 // N3 and N5 are nodes for which we need a fragment root. This will correspond
1950 // to the following tree in UIA:
1951 //
1952 // U1
1953 // _____/ \_____
1954 // / \
1955 // U2---R3---U4---R5
1956 // | |
1957 // U3 U5
1958 // / \ / \
1959 // U6---U7 U8---U9
1960 //
1961 // Ux is the platform node for Nx.
1962 // R3 and R5 are the fragment root nodes for U3 and U5 respectively.
1963 //
1964 // Navigation has the following behaviors:
1965 //
1966 // 1. Parent navigation: If source node Ux is the child of a fragment root,
1967 // return Rx. Otherwise, consult the platform-neutral tree.
1968 // 2. First/last child navigation: If target node Ux is the child of a
1969 // fragment root and the source node isn't Rx, return Rx. Otherwise, return
1970 // Ux.
1971 // 3. Next/previous sibling navigation:
1972 // a. If source node Ux is the child of a fragment root, return nullptr.
1973 // b. If target node Ux is the child of a fragment root, return Rx.
1974 // Otherwise, return Ux.
1975 //
1976 // Note that the condition in 3b is a special case of the condition in 2. In
1977 // 3b, the source node is never Rx. So in the code, we collapse them to a
1978 // common implementation.
1979 //
1980 // Navigation from an Rx node is set up by delegate APIs on AXFragmentRootWin.
1981 //
1982 gfx::NativeViewAccessible neighbor = nullptr;
1983 switch (direction) {
1984 case NavigateDirection_Parent: {
1985 // 1. If source node Ux is the child of a fragment root, return Rx.
1986 // Otherwise, consult the platform-neutral tree.
1987 AXFragmentRootWin* fragment_root =
1988 AXFragmentRootWin::GetFragmentRootParentOf(GetNativeViewAccessible());
1989 if (BASE_UNLIKELY(fragment_root)) {
1990 neighbor = fragment_root->GetNativeViewAccessible();
1991 } else {
1992 neighbor = GetParent();
1993 }
1994 } break;
1995
1996 case NavigateDirection_FirstChild:
1997 if (GetChildCount() > 0)
1998 neighbor = GetFirstChild()->GetNativeViewAccessible();
1999 break;
2000
2001 case NavigateDirection_LastChild:
2002 if (GetChildCount() > 0)
2003 neighbor = GetLastChild()->GetNativeViewAccessible();
2004 break;
2005
2006 case NavigateDirection_NextSibling:
2007 // 3a. If source node Ux is the child of a fragment root, return nullptr.
2009 GetNativeViewAccessible()) == nullptr) {
2010 AXPlatformNodeBase* neighbor_node = GetNextSibling();
2011 if (neighbor_node)
2012 neighbor = neighbor_node->GetNativeViewAccessible();
2013 }
2014 break;
2015
2016 case NavigateDirection_PreviousSibling:
2017 // 3a. If source node Ux is the child of a fragment root, return nullptr.
2019 GetNativeViewAccessible()) == nullptr) {
2020 AXPlatformNodeBase* neighbor_node = GetPreviousSibling();
2021 if (neighbor_node)
2022 neighbor = neighbor_node->GetNativeViewAccessible();
2023 }
2024 break;
2025
2026 default:
2028 break;
2029 }
2030
2031 if (neighbor) {
2032 if (direction != NavigateDirection_Parent) {
2033 // 2 / 3b. If target node Ux is the child of a fragment root and the
2034 // source node isn't Rx, return Rx.
2035 AXFragmentRootWin* fragment_root =
2037 if (BASE_UNLIKELY(fragment_root && fragment_root != GetDelegate()))
2038 neighbor = fragment_root->GetNativeViewAccessible();
2039 }
2040 neighbor->QueryInterface(IID_PPV_ARGS(element_provider));
2041 }
2042
2043 return S_OK;
2044}
2045
2046void AXPlatformNodeWin::GetRuntimeIdArray(
2047 AXPlatformNodeWin::RuntimeIdArray& runtime_id) {
2048 runtime_id[0] = UiaAppendRuntimeId;
2049 runtime_id[1] = GetUniqueId();
2050}
2051
2052IFACEMETHODIMP AXPlatformNodeWin::GetRuntimeId(SAFEARRAY** runtime_id) {
2053 UIA_VALIDATE_CALL_1_ARG(runtime_id);
2054
2055 RuntimeIdArray id_array;
2056 GetRuntimeIdArray(id_array);
2057 *runtime_id = ::SafeArrayCreateVector(VT_I4, 0, id_array.size());
2058
2059 int* array_data = nullptr;
2060 ::SafeArrayAccessData(*runtime_id, reinterpret_cast<void**>(&array_data));
2061
2062 size_t runtime_id_byte_count = id_array.size() * sizeof(int);
2063 memcpy_s(array_data, runtime_id_byte_count, id_array.data(),
2064 runtime_id_byte_count);
2065
2066 ::SafeArrayUnaccessData(*runtime_id);
2067
2068 return S_OK;
2069}
2070
2071IFACEMETHODIMP AXPlatformNodeWin::get_BoundingRectangle(
2072 UiaRect* screen_physical_pixel_bounds) {
2073 UIA_VALIDATE_CALL_1_ARG(screen_physical_pixel_bounds);
2074
2075 gfx::Rect bounds =
2078 screen_physical_pixel_bounds->left = bounds.x();
2079 screen_physical_pixel_bounds->top = bounds.y();
2080 screen_physical_pixel_bounds->width = bounds.width();
2081 screen_physical_pixel_bounds->height = bounds.height();
2082 return S_OK;
2083}
2084
2085IFACEMETHODIMP AXPlatformNodeWin::GetEmbeddedFragmentRoots(
2086 SAFEARRAY** embedded_fragment_roots) {
2087 UIA_VALIDATE_CALL_1_ARG(embedded_fragment_roots);
2088
2089 *embedded_fragment_roots = nullptr;
2090 return S_OK;
2091}
2092
2093IFACEMETHODIMP AXPlatformNodeWin::SetFocus() {
2095
2096 AXActionData action_data;
2097 action_data.action = ax::mojom::Action::kFocus;
2098 delegate_->AccessibilityPerformAction(action_data);
2099 return S_OK;
2100}
2101
2102IFACEMETHODIMP AXPlatformNodeWin::get_FragmentRoot(
2103 IRawElementProviderFragmentRoot** fragment_root) {
2104 UIA_VALIDATE_CALL_1_ARG(fragment_root);
2105
2106 gfx::AcceleratedWidget widget =
2107 delegate_->GetTargetForNativeAccessibilityEvent();
2108 if (widget) {
2109 AXFragmentRootWin* root =
2111 if (root != nullptr) {
2112 root->GetNativeViewAccessible()->QueryInterface(
2113 IID_PPV_ARGS(fragment_root));
2114 BASE_DCHECK(*fragment_root);
2115 return S_OK;
2116 }
2117 }
2118
2119 *fragment_root = nullptr;
2120 return UIA_E_ELEMENTNOTAVAILABLE;
2121}
2122
2123//
2124// IRawElementProviderSimple implementation.
2125//
2126
2127IFACEMETHODIMP AXPlatformNodeWin::GetPatternProvider(PATTERNID pattern_id,
2128 IUnknown** result) {
2129 return GetPatternProviderImpl(pattern_id, result);
2130}
2131
2132HRESULT AXPlatformNodeWin::GetPatternProviderImpl(PATTERNID pattern_id,
2133 IUnknown** result) {
2135
2136 *result = nullptr;
2137
2138 PatternProviderFactoryMethod factory_method =
2139 GetPatternProviderFactoryMethod(pattern_id);
2140 if (factory_method)
2141 (*factory_method)(this, result);
2142
2143 return S_OK;
2144}
2145
2146IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
2147 VARIANT* result) {
2148 return GetPropertyValueImpl(property_id, result);
2149}
2150
2151HRESULT AXPlatformNodeWin::GetPropertyValueImpl(PROPERTYID property_id,
2152 VARIANT* result) {
2154
2155 result->vt = VT_EMPTY;
2156
2157 int int_attribute;
2158 const AXNodeData& data = GetData();
2159
2160 // Default UIA Property Ids.
2161 switch (property_id) {
2162 case UIA_AriaPropertiesPropertyId:
2163 result->vt = VT_BSTR;
2164 result->bstrVal = ::SysAllocString(
2165 fml::Utf16ToWideString(ComputeUIAProperties()).c_str());
2166 break;
2167
2168 case UIA_AriaRolePropertyId:
2169 result->vt = VT_BSTR;
2170 result->bstrVal =
2171 ::SysAllocString(fml::Utf16ToWideString(UIAAriaRole()).c_str());
2172 break;
2173
2174 case UIA_AutomationIdPropertyId:
2175 V_VT(result) = VT_BSTR;
2176 V_BSTR(result) = ::SysAllocString(
2177 fml::Utf16ToWideString(GetDelegate()->GetAuthorUniqueId()).c_str());
2178 break;
2179
2180 case UIA_ClassNamePropertyId:
2181 result->vt = VT_BSTR;
2182 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kClassName,
2183 &result->bstrVal);
2184 break;
2185
2186 case UIA_ClickablePointPropertyId:
2187 result->vt = VT_ARRAY | VT_R8;
2188 result->parray = CreateClickablePointArray();
2189 break;
2190
2191 case UIA_ControllerForPropertyId:
2192 result->vt = VT_ARRAY | VT_UNKNOWN;
2193 result->parray = CreateUIAControllerForArray();
2194 break;
2195
2196 case UIA_ControlTypePropertyId:
2197 result->vt = VT_I4;
2198 result->lVal = ComputeUIAControlType();
2199 break;
2200
2201 case UIA_CulturePropertyId: {
2202 std::optional<LCID> lcid = GetCultureAttributeAsLCID();
2203 if (!lcid)
2204 return E_FAIL;
2205 result->vt = VT_I4;
2206 result->lVal = lcid.value();
2207 break;
2208 }
2209
2210 case UIA_DescribedByPropertyId:
2211 result->vt = VT_ARRAY | VT_UNKNOWN;
2212 result->parray = CreateUIAElementsArrayForRelation(
2214 break;
2215
2216 case UIA_FlowsFromPropertyId:
2217 V_VT(result) = VT_ARRAY | VT_UNKNOWN;
2218 V_ARRAY(result) = CreateUIAElementsArrayForReverseRelation(
2220 break;
2221
2222 case UIA_FlowsToPropertyId:
2223 result->vt = VT_ARRAY | VT_UNKNOWN;
2224 result->parray = CreateUIAElementsArrayForRelation(
2226 break;
2227
2228 case UIA_FrameworkIdPropertyId:
2229 V_VT(result) = VT_BSTR;
2230 V_BSTR(result) = SysAllocString(FRAMEWORK_ID);
2231 break;
2232
2233 case UIA_HasKeyboardFocusPropertyId:
2234 result->vt = VT_BOOL;
2235 result->boolVal = (delegate_->GetFocus() == GetNativeViewAccessible())
2236 ? VARIANT_TRUE
2237 : VARIANT_FALSE;
2238 break;
2239
2240 case UIA_FullDescriptionPropertyId:
2241 result->vt = VT_BSTR;
2242 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kDescription,
2243 &result->bstrVal);
2244 break;
2245
2246 case UIA_HelpTextPropertyId:
2247 if (HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder)) {
2248 V_VT(result) = VT_BSTR;
2249 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kPlaceholder,
2250 &V_BSTR(result));
2251 } else if (data.GetNameFrom() == ax::mojom::NameFrom::kPlaceholder ||
2252 data.GetNameFrom() == ax::mojom::NameFrom::kTitle) {
2253 V_VT(result) = VT_BSTR;
2254 GetNameAsBstr(&V_BSTR(result));
2255 } else if (HasStringAttribute(ax::mojom::StringAttribute::kTooltip)) {
2256 V_VT(result) = VT_BSTR;
2257 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kTooltip,
2258 &V_BSTR(result));
2259 }
2260 break;
2261
2262 case UIA_IsContentElementPropertyId:
2263 case UIA_IsControlElementPropertyId:
2264 result->vt = VT_BOOL;
2265 result->boolVal = IsUIAControl() ? VARIANT_TRUE : VARIANT_FALSE;
2266 break;
2267
2268 case UIA_IsDataValidForFormPropertyId:
2269 if (data.GetIntAttribute(ax::mojom::IntAttribute::kInvalidState,
2270 &int_attribute)) {
2271 result->vt = VT_BOOL;
2272 result->boolVal =
2273 (static_cast<int>(ax::mojom::InvalidState::kFalse) == int_attribute)
2274 ? VARIANT_TRUE
2275 : VARIANT_FALSE;
2276 }
2277 break;
2278
2279 case UIA_IsDialogPropertyId:
2280 result->vt = VT_BOOL;
2281 result->boolVal = IsDialog(data.role) ? VARIANT_TRUE : VARIANT_FALSE;
2282 break;
2283
2284 case UIA_IsKeyboardFocusablePropertyId:
2285 result->vt = VT_BOOL;
2286 result->boolVal =
2287 ShouldNodeHaveFocusableState(data) ? VARIANT_TRUE : VARIANT_FALSE;
2288 break;
2289
2290 case UIA_IsOffscreenPropertyId:
2291 result->vt = VT_BOOL;
2292 result->boolVal =
2293 GetDelegate()->IsOffscreen() ? VARIANT_TRUE : VARIANT_FALSE;
2294 break;
2295
2296 case UIA_IsRequiredForFormPropertyId:
2297 result->vt = VT_BOOL;
2298 if (data.HasState(ax::mojom::State::kRequired)) {
2299 result->boolVal = VARIANT_TRUE;
2300 } else {
2301 result->boolVal = VARIANT_FALSE;
2302 }
2303 break;
2304
2305 case UIA_ItemStatusPropertyId: {
2306 // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
2307 // aria-sort='ascending|descending|other' is mapped for the
2308 // HeaderItem Control Type.
2309 int32_t sort_direction;
2310 if (IsTableHeader(data.role) &&
2312 &sort_direction)) {
2313 switch (static_cast<ax::mojom::SortDirection>(sort_direction)) {
2316 break;
2318 V_VT(result) = VT_BSTR;
2319 V_BSTR(result) = SysAllocString(L"ascending");
2320 break;
2322 V_VT(result) = VT_BSTR;
2323 V_BSTR(result) = SysAllocString(L"descending");
2324 break;
2326 V_VT(result) = VT_BSTR;
2327 V_BSTR(result) = SysAllocString(L"other");
2328 break;
2329 }
2330 }
2331 break;
2332 }
2333
2334 case UIA_LabeledByPropertyId:
2335 if (AXPlatformNodeWin* node = ComputeUIALabeledBy()) {
2336 result->vt = VT_UNKNOWN;
2337 result->punkVal = node->GetNativeViewAccessible();
2338 result->punkVal->AddRef();
2339 }
2340 break;
2341
2342 case UIA_LocalizedControlTypePropertyId: {
2343 std::u16string localized_control_type = GetRoleDescription();
2344 if (!localized_control_type.empty()) {
2345 result->vt = VT_BSTR;
2346 result->bstrVal = ::SysAllocString(
2347 fml::Utf16ToWideString(localized_control_type).c_str());
2348 }
2349 // If a role description has not been provided, leave as VT_EMPTY.
2350 // UIA core handles Localized Control type for some built-in types and
2351 // also has a mapping for ARIA roles. To get these defaults, we need to
2352 // have returned VT_EMPTY.
2353 } break;
2354
2355 case UIA_NamePropertyId:
2356 if (IsNameExposed()) {
2357 result->vt = VT_BSTR;
2358 GetNameAsBstr(&result->bstrVal);
2359 }
2360 break;
2361
2362 case UIA_OrientationPropertyId:
2363 if (SupportsOrientation(data.role)) {
2364 if (data.HasState(ax::mojom::State::kHorizontal) &&
2366 BASE_UNREACHABLE(); // << "An accessibility object cannot have a
2367 // horizontal "
2368 //"and a vertical orientation at the same time.";
2369 }
2370 if (data.HasState(ax::mojom::State::kHorizontal)) {
2371 result->vt = VT_I4;
2372 result->intVal = OrientationType_Horizontal;
2373 }
2374 if (data.HasState(ax::mojom::State::kVertical)) {
2375 result->vt = VT_I4;
2376 result->intVal = OrientationType_Vertical;
2377 }
2378 } else {
2379 result->vt = VT_I4;
2380 result->intVal = OrientationType_None;
2381 }
2382 break;
2383
2384 case UIA_IsEnabledPropertyId:
2385 V_VT(result) = VT_BOOL;
2386 switch (data.GetRestriction()) {
2388 V_BOOL(result) = VARIANT_FALSE;
2389 break;
2390
2393 V_BOOL(result) = VARIANT_TRUE;
2394 break;
2395 }
2396 break;
2397
2398 case UIA_IsPasswordPropertyId:
2399 result->vt = VT_BOOL;
2400 result->boolVal = data.HasState(ax::mojom::State::kProtected)
2401 ? VARIANT_TRUE
2402 : VARIANT_FALSE;
2403 break;
2404
2405 case UIA_AcceleratorKeyPropertyId:
2406 if (data.HasStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts)) {
2407 result->vt = VT_BSTR;
2408 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kKeyShortcuts,
2409 &result->bstrVal);
2410 }
2411 break;
2412
2413 case UIA_AccessKeyPropertyId:
2414 if (data.HasStringAttribute(ax::mojom::StringAttribute::kAccessKey)) {
2415 result->vt = VT_BSTR;
2416 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kAccessKey,
2417 &result->bstrVal);
2418 }
2419 break;
2420
2421 case UIA_IsPeripheralPropertyId:
2422 result->vt = VT_BOOL;
2423 result->boolVal = VARIANT_FALSE;
2424 break;
2425
2426 case UIA_LevelPropertyId:
2428 &int_attribute)) {
2429 result->vt = VT_I4;
2430 result->intVal = int_attribute;
2431 }
2432 break;
2433
2434 case UIA_LiveSettingPropertyId: {
2435 result->vt = VT_I4;
2436 result->intVal = LiveSetting::Off;
2437
2438 std::string string_attribute;
2439 if (data.GetStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
2440 &string_attribute)) {
2441 if (string_attribute == "polite")
2442 result->intVal = LiveSetting::Polite;
2443 else if (string_attribute == "assertive")
2444 result->intVal = LiveSetting::Assertive;
2445 }
2446 break;
2447 }
2448
2449 case UIA_OptimizeForVisualContentPropertyId:
2450 result->vt = VT_BOOL;
2451 result->boolVal = VARIANT_FALSE;
2452 break;
2453
2454 case UIA_PositionInSetPropertyId: {
2455 std::optional<int> pos_in_set = GetPosInSet();
2456 if (pos_in_set) {
2457 result->vt = VT_I4;
2458 result->intVal = *pos_in_set;
2459 }
2460 } break;
2461
2462 case UIA_ScrollHorizontalScrollPercentPropertyId: {
2463 V_VT(result) = VT_R8;
2464 V_R8(result) = GetHorizontalScrollPercent();
2465 break;
2466 }
2467
2468 case UIA_ScrollVerticalScrollPercentPropertyId: {
2469 V_VT(result) = VT_R8;
2470 V_R8(result) = GetVerticalScrollPercent();
2471 break;
2472 }
2473
2474 case UIA_SizeOfSetPropertyId: {
2475 std::optional<int> set_size = GetSetSize();
2476 if (set_size) {
2477 result->vt = VT_I4;
2478 result->intVal = *set_size;
2479 }
2480 break;
2481 }
2482
2483 case UIA_LandmarkTypePropertyId: {
2484 std::optional<LONG> landmark_type = ComputeUIALandmarkType();
2485 if (landmark_type) {
2486 result->vt = VT_I4;
2487 result->intVal = landmark_type.value();
2488 }
2489 break;
2490 }
2491
2492 case UIA_LocalizedLandmarkTypePropertyId: {
2493 std::u16string localized_landmark_type =
2494 GetDelegate()->GetLocalizedStringForLandmarkType();
2495 if (!localized_landmark_type.empty()) {
2496 result->vt = VT_BSTR;
2497 result->bstrVal = ::SysAllocString(
2498 fml::Utf16ToWideString(localized_landmark_type).c_str());
2499 }
2500 break;
2501 }
2502
2503 case UIA_ExpandCollapseExpandCollapseStatePropertyId:
2504 result->vt = VT_I4;
2505 result->intVal = static_cast<int>(ComputeExpandCollapseState());
2506 break;
2507
2508 case UIA_ToggleToggleStatePropertyId: {
2509 ToggleState state;
2510 get_ToggleState(&state);
2511 result->vt = VT_I4;
2512 result->lVal = state;
2513 break;
2514 }
2515
2516 case UIA_ValueValuePropertyId:
2517 result->vt = VT_BSTR;
2518 result->bstrVal = GetValueAttributeAsBstr(this);
2519 break;
2520
2521 // Not currently implemented.
2522 case UIA_AnnotationObjectsPropertyId:
2523 case UIA_AnnotationTypesPropertyId:
2524 case UIA_CenterPointPropertyId:
2525 case UIA_FillColorPropertyId:
2526 case UIA_FillTypePropertyId:
2527 case UIA_HeadingLevelPropertyId:
2528 case UIA_ItemTypePropertyId:
2529 case UIA_OutlineColorPropertyId:
2530 case UIA_OutlineThicknessPropertyId:
2531 case UIA_RotationPropertyId:
2532 case UIA_SizePropertyId:
2533 case UIA_VisualEffectsPropertyId:
2534 break;
2535
2536 // Provided by UIA Core; we should not implement.
2537 case UIA_BoundingRectanglePropertyId:
2538 case UIA_NativeWindowHandlePropertyId:
2539 case UIA_ProcessIdPropertyId:
2540 case UIA_ProviderDescriptionPropertyId:
2541 case UIA_RuntimeIdPropertyId:
2542 break;
2543 } // End of default UIA property ids.
2544
2545 // Custom UIA Property Ids.
2546 if (property_id ==
2547 UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId()) {
2548 // We want to negate the unique id for it to be consistent across different
2549 // Windows accessiblity APIs. The negative unique id convention originated
2550 // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
2551 // self, and a positive child id means child #n. In order to fire an event
2552 // for an arbitrary descendant of the window, Firefox started the practice
2553 // of using a negative unique id. We follow the same negative unique id
2554 // convention here and when we fire events via ::NotifyWinEvent().
2555 result->vt = VT_BSTR;
2556 result->bstrVal = ::SysAllocString(
2557 fml::Utf16ToWideString(base::NumberToString16(-GetUniqueId())).c_str());
2558 }
2559
2560 return S_OK;
2561}
2562
2563IFACEMETHODIMP AXPlatformNodeWin::get_ProviderOptions(ProviderOptions* ret) {
2565
2566 *ret = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading |
2567 ProviderOptions_RefuseNonClientSupport |
2568 ProviderOptions_HasNativeIAccessible;
2569 return S_OK;
2570}
2571
2572IFACEMETHODIMP AXPlatformNodeWin::get_HostRawElementProvider(
2573 IRawElementProviderSimple** provider) {
2574 UIA_VALIDATE_CALL_1_ARG(provider);
2575
2576 *provider = nullptr;
2577 return S_OK;
2578}
2579
2580//
2581// IRawElementProviderSimple2 implementation.
2582//
2583
2584IFACEMETHODIMP AXPlatformNodeWin::ShowContextMenu() {
2586
2587 AXActionData action_data;
2588 action_data.action = ax::mojom::Action::kShowContextMenu;
2589 delegate_->AccessibilityPerformAction(action_data);
2590 return S_OK;
2591}
2592
2593//
2594// IServiceProvider implementation.
2595//
2596
2597IFACEMETHODIMP AXPlatformNodeWin::QueryService(REFGUID guidService,
2598 REFIID riid,
2599 void** object) {
2601
2602 if (guidService == IID_IAccessible || (guidService == IID_IAccessibleEx && delegate_->IsIAccessibleExEnabled())) {
2603 return QueryInterface(riid, object);
2604 }
2605
2606 // TODO(suproteem): Include IAccessibleEx in the list, potentially checking
2607 // for version.
2608
2609 *object = nullptr;
2610 return E_FAIL;
2611}
2612
2613//
2614// Methods used by the ATL COM map.
2615//
2616
2617// static
2618STDMETHODIMP AXPlatformNodeWin::InternalQueryInterface(
2619 void* this_ptr,
2620 const _ATL_INTMAP_ENTRY* entries,
2621 REFIID riid,
2622 void** object) {
2623 if (!object)
2624 return E_INVALIDARG;
2625 *object = nullptr;
2626 AXPlatformNodeWin* accessible =
2627 reinterpret_cast<AXPlatformNodeWin*>(this_ptr);
2629
2630 return CComObjectRootBase::InternalQueryInterface(this_ptr, entries, riid,
2631 object);
2632}
2633
2634HRESULT AXPlatformNodeWin::GetTextAttributeValue(
2635 TEXTATTRIBUTEID attribute_id,
2636 const std::optional<int>& start_offset,
2637 const std::optional<int>& end_offset,
2638 base::win::VariantVector* result) {
2639 BASE_DCHECK(!start_offset || start_offset.value() >= 0);
2640 BASE_DCHECK(!end_offset || end_offset.value() >= 0);
2641
2642 switch (attribute_id) {
2643 case UIA_AnnotationTypesAttributeId:
2644 return GetAnnotationTypesAttribute(start_offset, end_offset, result);
2645 case UIA_BackgroundColorAttributeId:
2646 result->Insert<VT_I4>(
2647 GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute::kBackgroundColor));
2648 break;
2649 case UIA_BulletStyleAttributeId:
2650 result->Insert<VT_I4>(ComputeUIABulletStyle());
2651 break;
2652 case UIA_CultureAttributeId: {
2653 std::optional<LCID> lcid = GetCultureAttributeAsLCID();
2654 if (!lcid)
2655 return E_FAIL;
2656 result->Insert<VT_I4>(lcid.value());
2657 break;
2658 }
2659 case UIA_FontNameAttributeId:
2660 result->Insert<VT_BSTR>(GetFontNameAttributeAsBSTR());
2661 break;
2662 case UIA_FontSizeAttributeId: {
2663 std::optional<float> font_size_in_points = GetFontSizeInPoints();
2664 if (font_size_in_points) {
2665 result->Insert<VT_R8>(*font_size_in_points);
2666 }
2667 break;
2668 }
2669 case UIA_FontWeightAttributeId:
2670 result->Insert<VT_I4>(
2671 GetFloatAttribute(ax::mojom::FloatAttribute::kFontWeight));
2672 break;
2673 case UIA_ForegroundColorAttributeId:
2674 result->Insert<VT_I4>(
2675 GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute::kColor));
2676 break;
2677 case UIA_IsHiddenAttributeId:
2678 result->Insert<VT_BOOL>(IsInvisibleOrIgnored());
2679 break;
2680 case UIA_IsItalicAttributeId:
2681 result->Insert<VT_BOOL>(
2682 GetData().HasTextStyle(ax::mojom::TextStyle::kItalic));
2683 break;
2684 case UIA_IsReadOnlyAttributeId:
2685 // Placeholder text should return the enclosing element's read-only value.
2686 if (IsPlaceholderText()) {
2687 AXPlatformNodeWin* parent_platform_node =
2688 static_cast<AXPlatformNodeWin*>(
2689 FromNativeViewAccessible(GetParent()));
2690 return parent_platform_node->GetTextAttributeValue(
2691 attribute_id, start_offset, end_offset, result);
2692 }
2693 result->Insert<VT_BOOL>(GetData().IsReadOnlyOrDisabled());
2694 break;
2695 case UIA_IsSubscriptAttributeId:
2696 result->Insert<VT_BOOL>(GetData().GetTextPosition() ==
2698 break;
2699 case UIA_IsSuperscriptAttributeId:
2700 result->Insert<VT_BOOL>(GetData().GetTextPosition() ==
2702 break;
2703 case UIA_OverlineStyleAttributeId:
2704 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2706 break;
2707 case UIA_StrikethroughStyleAttributeId:
2708 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2710 break;
2711 case UIA_StyleNameAttributeId:
2712 result->Insert<VT_BSTR>(GetStyleNameAttributeAsBSTR());
2713 break;
2714 case UIA_StyleIdAttributeId:
2715 result->Insert<VT_I4>(ComputeUIAStyleId());
2716 break;
2717 case UIA_HorizontalTextAlignmentAttributeId: {
2718 std::optional<HorizontalTextAlignment> horizontal_text_alignment =
2719 AXTextAlignToUIAHorizontalTextAlignment(GetData().GetTextAlign());
2720 if (horizontal_text_alignment)
2721 result->Insert<VT_I4>(*horizontal_text_alignment);
2722 break;
2723 }
2724 case UIA_UnderlineStyleAttributeId:
2725 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2727 break;
2728 case UIA_TextFlowDirectionsAttributeId:
2729 result->Insert<VT_I4>(
2730 TextDirectionToFlowDirections(GetData().GetTextDirection()));
2731 break;
2732 default: {
2733 Microsoft::WRL::ComPtr<IUnknown> not_supported_value;
2734 HRESULT hr = ::UiaGetReservedNotSupportedValue(&not_supported_value);
2735 if (SUCCEEDED(hr))
2736 result->Insert<VT_UNKNOWN>(not_supported_value.Get());
2737 return hr;
2738 } break;
2739 }
2740
2741 return S_OK;
2742}
2743
2744HRESULT AXPlatformNodeWin::GetAnnotationTypesAttribute(
2745 const std::optional<int>& start_offset,
2746 const std::optional<int>& end_offset,
2747 base::win::VariantVector* result) {
2748 base::win::VariantVector variant_vector;
2749
2750 MarkerTypeRangeResult grammar_result = MarkerTypeRangeResult::kNone;
2751 MarkerTypeRangeResult spelling_result = MarkerTypeRangeResult::kNone;
2752
2753 if (IsText() || IsPlainTextField()) {
2754 grammar_result = GetMarkerTypeFromRange(start_offset, end_offset,
2756 spelling_result = GetMarkerTypeFromRange(start_offset, end_offset,
2758 }
2759
2760 if (grammar_result == MarkerTypeRangeResult::kMixed ||
2761 spelling_result == MarkerTypeRangeResult::kMixed) {
2762 Microsoft::WRL::ComPtr<IUnknown> mixed_attribute_value;
2763 HRESULT hr = ::UiaGetReservedMixedAttributeValue(&mixed_attribute_value);
2764 if (SUCCEEDED(hr))
2765 result->Insert<VT_UNKNOWN>(mixed_attribute_value.Get());
2766 return hr;
2767 }
2768
2769 if (spelling_result == MarkerTypeRangeResult::kMatch)
2770 result->Insert<VT_I4>(AnnotationType_SpellingError);
2771 if (grammar_result == MarkerTypeRangeResult::kMatch)
2772 result->Insert<VT_I4>(AnnotationType_GrammarError);
2773
2774 return S_OK;
2775}
2776
2777std::optional<LCID> AXPlatformNodeWin::GetCultureAttributeAsLCID() const {
2778 const std::u16string language =
2779 GetInheritedString16Attribute(ax::mojom::StringAttribute::kLanguage);
2780 const LCID lcid =
2781 LocaleNameToLCID((wchar_t*)language.c_str(), LOCALE_ALLOW_NEUTRAL_NAMES);
2782 if (!lcid)
2783 return std::nullopt;
2784
2785 return lcid;
2786}
2787
2788COLORREF AXPlatformNodeWin::GetIntAttributeAsCOLORREF(
2789 ax::mojom::IntAttribute attribute) const {
2790 uint32_t color = GetIntAttribute(attribute);
2791 // From skia_utils_win.cc
2792 return (_byteswap_ulong(color) >> 8);
2793}
2794
2795BulletStyle AXPlatformNodeWin::ComputeUIABulletStyle() const {
2796 // UIA expects the list style of a non-list-item to be none however the
2797 // default list style cascaded is disc not none. Therefore we must ensure that
2798 // this node is contained within a list-item to distinguish non-list-items and
2799 // disc styled list items.
2800 const AXPlatformNodeBase* current_node = this;
2801 while (current_node &&
2802 current_node->GetData().role != ax::mojom::Role::kListItem) {
2803 current_node = FromNativeViewAccessible(current_node->GetParent());
2804 }
2805
2806 const ax::mojom::ListStyle list_style =
2807 current_node ? current_node->GetData().GetListStyle()
2809
2810 switch (list_style) {
2812 return BulletStyle::BulletStyle_None;
2814 return BulletStyle::BulletStyle_HollowRoundBullet;
2816 return BulletStyle::BulletStyle_FilledRoundBullet;
2818 return BulletStyle::BulletStyle_Other;
2821 return BulletStyle::BulletStyle_None;
2823 return BulletStyle::BulletStyle_FilledSquareBullet;
2824 }
2825}
2826
2827LONG AXPlatformNodeWin::ComputeUIAStyleId() const {
2828 const AXPlatformNodeBase* current_node = this;
2829 do {
2830 switch (current_node->GetData().role) {
2832 return AXHierarchicalLevelToUIAStyleId(current_node->GetIntAttribute(
2835 return AXListStyleToUIAStyleId(current_node->GetData().GetListStyle());
2837 return StyleId_Custom;
2839 return StyleId_Quote;
2840 default:
2841 break;
2842 }
2843 current_node = FromNativeViewAccessible(current_node->GetParent());
2844 } while (current_node);
2845
2846 return StyleId_Normal;
2847}
2848
2849// static
2850std::optional<HorizontalTextAlignment>
2851AXPlatformNodeWin::AXTextAlignToUIAHorizontalTextAlignment(
2852 ax::mojom::TextAlign text_align) {
2853 switch (text_align) {
2855 return std::nullopt;
2857 return HorizontalTextAlignment_Left;
2859 return HorizontalTextAlignment_Right;
2861 return HorizontalTextAlignment_Centered;
2863 return HorizontalTextAlignment_Justified;
2864 }
2865}
2866
2867// static
2868LONG AXPlatformNodeWin::AXHierarchicalLevelToUIAStyleId(
2869 int32_t hierarchical_level) {
2870 switch (hierarchical_level) {
2871 case 0:
2872 return StyleId_Normal;
2873 case 1:
2874 return StyleId_Heading1;
2875 case 2:
2876 return StyleId_Heading2;
2877 case 3:
2878 return StyleId_Heading3;
2879 case 4:
2880 return StyleId_Heading4;
2881 case 5:
2882 return StyleId_Heading5;
2883 case 6:
2884 return StyleId_Heading6;
2885 case 7:
2886 return StyleId_Heading7;
2887 case 8:
2888 return StyleId_Heading8;
2889 case 9:
2890 return StyleId_Heading9;
2891 default:
2892 return StyleId_Custom;
2893 }
2894}
2895
2896// static
2897LONG AXPlatformNodeWin::AXListStyleToUIAStyleId(
2898 ax::mojom::ListStyle list_style) {
2899 switch (list_style) {
2901 return StyleId_Normal;
2906 return StyleId_BulletedList;
2909 return StyleId_NumberedList;
2910 }
2911}
2912
2913// static
2914FlowDirections AXPlatformNodeWin::TextDirectionToFlowDirections(
2916 switch (text_direction) {
2918 return FlowDirections::FlowDirections_Default;
2920 return FlowDirections::FlowDirections_Default;
2922 return FlowDirections::FlowDirections_RightToLeft;
2924 return FlowDirections::FlowDirections_Vertical;
2926 return FlowDirections::FlowDirections_BottomToTop;
2927 }
2928}
2929
2930// static
2931void AXPlatformNodeWin::AggregateRangesForMarkerType(
2932 AXPlatformNodeBase* node,
2933 ax::mojom::MarkerType marker_type,
2934 int offset_ranges_amount,
2935 std::vector<std::pair<int, int>>* ranges) {
2936 BASE_DCHECK(node->IsText());
2937 const std::vector<int32_t>& marker_types =
2938 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
2939 const std::vector<int>& marker_starts =
2940 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
2941 const std::vector<int>& marker_ends =
2942 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
2943
2944 for (size_t i = 0; i < marker_types.size(); ++i) {
2945 if (static_cast<ax::mojom::MarkerType>(marker_types[i]) != marker_type)
2946 continue;
2947
2948 const int marker_start = marker_starts[i] + offset_ranges_amount;
2949 const int marker_end = marker_ends[i] + offset_ranges_amount;
2950 ranges->emplace_back(std::make_pair(marker_start, marker_end));
2951 }
2952}
2953
2954AXPlatformNodeWin::MarkerTypeRangeResult
2955AXPlatformNodeWin::GetMarkerTypeFromRange(
2956 const std::optional<int>& start_offset,
2957 const std::optional<int>& end_offset,
2958 ax::mojom::MarkerType marker_type) {
2959 BASE_DCHECK(IsText() || IsPlainTextField());
2960 std::vector<std::pair<int, int>> relevant_ranges;
2961
2962 if (IsText()) {
2963 AggregateRangesForMarkerType(this, marker_type, /*offset_ranges_amount=*/0,
2964 &relevant_ranges);
2965 } else if (IsPlainTextField()) {
2966 int offset_ranges_amount = 0;
2967 for (AXPlatformNodeBase* static_text = GetFirstTextOnlyDescendant();
2968 static_text; static_text = static_text->GetNextSibling()) {
2969 const int child_offset_ranges_amount = offset_ranges_amount;
2970 if (start_offset || end_offset) {
2971 // Break if the current node is after the desired |end_offset|.
2972 if (end_offset && child_offset_ranges_amount > end_offset.value())
2973 break;
2974
2975 // Skip over nodes preceding the desired |start_offset|.
2976 offset_ranges_amount += static_text->GetHypertext().length();
2977 if (start_offset && offset_ranges_amount < start_offset.value())
2978 continue;
2979 }
2980
2981 AggregateRangesForMarkerType(static_text, marker_type,
2982 child_offset_ranges_amount,
2983 &relevant_ranges);
2984 }
2985 }
2986
2987 // Sort the ranges by their start offset.
2988 const auto sort_ranges_by_start_offset = [](const std::pair<int, int>& a,
2989 const std::pair<int, int>& b) {
2990 return a.first < b.first;
2991 };
2992 std::sort(relevant_ranges.begin(), relevant_ranges.end(),
2993 sort_ranges_by_start_offset);
2994
2995 // Validate that the desired range has a contiguous MarkerType.
2996 std::optional<std::pair<int, int>> contiguous_range;
2997 for (const std::pair<int, int>& range : relevant_ranges) {
2998 if (end_offset && range.first > end_offset.value())
2999 break;
3000 if (start_offset && range.second < start_offset.value())
3001 continue;
3002
3003 if (!contiguous_range) {
3004 contiguous_range = range;
3005 continue;
3006 }
3007
3008 // If there is a gap, then the range must be mixed.
3009 if ((range.first - contiguous_range->second) > 1)
3010 return MarkerTypeRangeResult::kMixed;
3011
3012 // Expand the range if possible.
3013 contiguous_range->second = std::max(contiguous_range->second, range.second);
3014 }
3015
3016 // The desired range does not overlap with |marker_type|.
3017 if (!contiguous_range)
3018 return MarkerTypeRangeResult::kNone;
3019
3020 // If there is a partial overlap, then the desired range must be mixed.
3021 // 1. The |start_offset| is not specified, treat it as offset 0.
3022 if (!start_offset && contiguous_range->first > 0)
3023 return MarkerTypeRangeResult::kMixed;
3024 // 2. The |end_offset| is not specified, treat it as max text offset.
3025 if (!end_offset && contiguous_range->second < GetHypertext().length())
3026 return MarkerTypeRangeResult::kMixed;
3027 // 3. The |start_offset| is specified, but is before the first matching range.
3028 if (start_offset && start_offset.value() < contiguous_range->first)
3029 return MarkerTypeRangeResult::kMixed;
3030 // 4. The |end_offset| is specified, but is after the last matching range.
3031 if (end_offset && end_offset.value() > contiguous_range->second)
3032 return MarkerTypeRangeResult::kMixed;
3033
3034 // The desired range is a complete match for |marker_type|.
3035 return MarkerTypeRangeResult::kMatch;
3036}
3037
3038// IRawElementProviderSimple support methods.
3039
3040bool AXPlatformNodeWin::IsPatternProviderSupported(PATTERNID pattern_id) {
3041 return GetPatternProviderFactoryMethod(pattern_id);
3042}
3043
3044//
3045// Private member functions.
3046//
3047int AXPlatformNodeWin::MSAARole() {
3048 // If this is a web area for a presentational iframe, give it a role of
3049 // something other than DOCUMENT so that the fact that it's a separate doc
3050 // is not exposed to AT.
3051 if (IsWebAreaForPresentationalIframe())
3052 return ROLE_SYSTEM_GROUPING;
3053
3054 switch (GetData().role) {
3056 return ROLE_SYSTEM_ALERT;
3057
3059 return ROLE_SYSTEM_DIALOG;
3060
3062 return ROLE_SYSTEM_LINK;
3063
3066 return ROLE_SYSTEM_GROUPING;
3067
3069 return ROLE_SYSTEM_APPLICATION;
3070
3072 return ROLE_SYSTEM_DOCUMENT;
3073
3075 return ROLE_SYSTEM_GROUPING;
3076
3079 return ROLE_SYSTEM_GROUPING;
3080
3082 return ROLE_SYSTEM_GROUPING;
3083
3085 return ROLE_SYSTEM_PUSHBUTTON;
3086
3088 return ROLE_SYSTEM_GRAPHIC;
3089
3091 return ROLE_SYSTEM_TEXT;
3092
3094 return ROLE_SYSTEM_CARET;
3095
3097 return ROLE_SYSTEM_CELL;
3098
3100 return ROLE_SYSTEM_CHECKBUTTON;
3101
3103 return ROLE_SYSTEM_PANE;
3104
3106 return ROLE_SYSTEM_TEXT;
3107
3109 return ROLE_SYSTEM_COLUMN;
3110
3112 return ROLE_SYSTEM_COLUMNHEADER;
3113
3116 return ROLE_SYSTEM_COMBOBOX;
3117
3119 return ROLE_SYSTEM_GROUPING;
3120
3123 return ROLE_SYSTEM_GROUPING;
3124
3127 return ROLE_SYSTEM_GROUPING;
3128
3131 return ROLE_SYSTEM_DROPLIST;
3132
3134 return ROLE_SYSTEM_GROUPING;
3135
3137 return ROLE_SYSTEM_TEXT;
3138
3140 return ROLE_SYSTEM_LIST;
3141
3143 return ROLE_SYSTEM_LISTITEM;
3144
3146 return ROLE_SYSTEM_PANE;
3147
3149 return ROLE_SYSTEM_GROUPING;
3150
3152 return ROLE_SYSTEM_DIALOG;
3153
3155 return ROLE_SYSTEM_PUSHBUTTON;
3156
3158 return ROLE_SYSTEM_LIST;
3159
3161 return ROLE_SYSTEM_GRAPHIC;
3162
3167 return ROLE_SYSTEM_LINK;
3168
3172 return ROLE_SYSTEM_LISTITEM;
3173
3175 return ROLE_SYSTEM_SEPARATOR;
3176
3207 return ROLE_SYSTEM_GROUPING;
3208
3212 return ROLE_SYSTEM_DOCUMENT;
3213
3215 // Even though the HTML-AAM has ROLE_SYSTEM_CLIENT for <embed>, we are
3216 // forced to use ROLE_SYSTEM_GROUPING when the <embed> has children in the
3217 // accessibility tree.
3218 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
3219 //
3220 // Screen readers Jaws and NVDA do not "see" any of the <embed>'s contents
3221 // if they are represented as its children in the accessibility tree. For
3222 // example, one of the places that would be negatively impacted is the
3223 // reading of PDFs.
3224 if (GetDelegate()->GetChildCount()) {
3225 return ROLE_SYSTEM_GROUPING;
3226 } else {
3227 return ROLE_SYSTEM_CLIENT;
3228 }
3229
3231 return ROLE_SYSTEM_GROUPING;
3232
3234 return ROLE_SYSTEM_GROUPING;
3235
3237 return ROLE_SYSTEM_GROUPING;
3238
3241 return ROLE_SYSTEM_GROUPING;
3242
3244 return ROLE_SYSTEM_GROUPING;
3245
3247 return ROLE_SYSTEM_GROUPING;
3248
3250 return ROLE_SYSTEM_DOCUMENT;
3251
3253 return ROLE_SYSTEM_PANE;
3254
3256 return ROLE_SYSTEM_GRAPHIC;
3257
3259 return ROLE_SYSTEM_TABLE;
3260
3262 return ROLE_SYSTEM_GROUPING;
3263
3265 return ROLE_SYSTEM_GROUPING;
3266
3268 return ROLE_SYSTEM_DOCUMENT;
3269
3271 return ROLE_SYSTEM_GROUPING;
3272
3275 return ROLE_SYSTEM_GRAPHIC;
3276
3278 return ROLE_SYSTEM_GROUPING;
3279
3281 return ROLE_SYSTEM_STATICTEXT;
3282
3285 return ROLE_SYSTEM_TEXT;
3286
3288 return ROLE_SYSTEM_TABLE;
3289
3291 return ROLE_SYSTEM_CELL;
3292
3294 return ROLE_SYSTEM_ROW;
3295
3297 return ROLE_SYSTEM_LINK;
3298
3300 return ROLE_SYSTEM_LIST;
3301
3303 return ROLE_SYSTEM_LIST;
3304
3306 return ROLE_SYSTEM_LISTITEM;
3307
3309 return ROLE_SYSTEM_LIST;
3310
3312 return ROLE_SYSTEM_LISTITEM;
3313
3315 if (!GetDelegate()->GetChildCount()) {
3316 // There's only a name attribute when using Legacy layout. With Legacy
3317 // layout, list markers have no child and are considered as StaticText.
3318 // We consider a list marker as a group in LayoutNG since it has
3319 // a text child node.
3320 return ROLE_SYSTEM_STATICTEXT;
3321 }
3322 return ROLE_SYSTEM_GROUPING;
3323
3325 return ROLE_SYSTEM_GROUPING;
3326
3328 return ROLE_SYSTEM_GROUPING;
3329
3331 return ROLE_SYSTEM_GROUPING;
3332
3334 return ROLE_SYSTEM_ANIMATION;
3335
3337 return ROLE_SYSTEM_EQUATION;
3338
3340 return ROLE_SYSTEM_MENUPOPUP;
3341
3343 return ROLE_SYSTEM_MENUBAR;
3344
3348 return ROLE_SYSTEM_MENUITEM;
3349
3351 return ROLE_SYSTEM_LIST;
3352
3354 return ROLE_SYSTEM_LISTITEM;
3355
3357 return ROLE_SYSTEM_PROGRESSBAR;
3358
3360 return ROLE_SYSTEM_GROUPING;
3361
3363 return ROLE_SYSTEM_GROUPING;
3364
3366 return ROLE_SYSTEM_GROUPING;
3367
3369 return ROLE_SYSTEM_PUSHBUTTON;
3370
3372 // See also case ax::mojom::Role::kEmbeddedObject.
3373 if (GetDelegate()->GetChildCount()) {
3374 return ROLE_SYSTEM_GROUPING;
3375 } else {
3376 return ROLE_SYSTEM_CLIENT;
3377 }
3378
3380 std::string html_tag =
3381 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
3382 if (html_tag == "select")
3383 return ROLE_SYSTEM_COMBOBOX;
3384 return ROLE_SYSTEM_BUTTONMENU;
3385 }
3386
3388 return ROLE_SYSTEM_PUSHBUTTON;
3389
3391 return ROLE_SYSTEM_TEXT;
3392
3394 return ROLE_SYSTEM_PROGRESSBAR;
3395
3397 return ROLE_SYSTEM_RADIOBUTTON;
3398
3400 return ROLE_SYSTEM_GROUPING;
3401
3403 return ROLE_SYSTEM_PANE;
3404
3405 case ax::mojom::Role::kRow: {
3406 // Role changes depending on whether row is inside a treegrid
3407 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
3408 return IsInTreeGrid() ? ROLE_SYSTEM_OUTLINEITEM : ROLE_SYSTEM_ROW;
3409 }
3410
3412 return ROLE_SYSTEM_GROUPING;
3413
3415 return ROLE_SYSTEM_ROWHEADER;
3416
3418 return ROLE_SYSTEM_TEXT;
3419
3421 if (GetNameAsString16().empty()) {
3422 // Do not use ARIA mapping for nameless <section>.
3423 return ROLE_SYSTEM_GROUPING;
3424 }
3425 // Use ARIA mapping.
3426 return ROLE_SYSTEM_PANE;
3427 }
3428
3430 return ROLE_SYSTEM_SCROLLBAR;
3431
3433 return ROLE_SYSTEM_PANE;
3434
3436 return ROLE_SYSTEM_GROUPING;
3437
3439 return ROLE_SYSTEM_SLIDER;
3440
3442 return ROLE_SYSTEM_SLIDER;
3443
3445 return ROLE_SYSTEM_SPINBUTTON;
3446
3448 return ROLE_SYSTEM_CHECKBUTTON;
3449
3452 return ROLE_SYSTEM_STATICTEXT;
3453
3455 return ROLE_SYSTEM_STATUSBAR;
3456
3458 return ROLE_SYSTEM_SEPARATOR;
3459
3461 return ROLE_SYSTEM_GRAPHIC;
3462
3464 return ROLE_SYSTEM_PAGETAB;
3465
3467 return ROLE_SYSTEM_TABLE;
3468
3470 return ROLE_SYSTEM_GROUPING;
3471
3473 return ROLE_SYSTEM_PAGETABLIST;
3474
3476 return ROLE_SYSTEM_PROPERTYPAGE;
3477
3479 return ROLE_SYSTEM_LISTITEM;
3480
3482 return ROLE_SYSTEM_TITLEBAR;
3483
3485 return ROLE_SYSTEM_CHECKBUTTON;
3486
3489 return ROLE_SYSTEM_TEXT;
3490
3492 return ROLE_SYSTEM_COMBOBOX;
3493
3499 return ROLE_SYSTEM_TEXT;
3500
3502 return ROLE_SYSTEM_CLOCK;
3503
3505 return ROLE_SYSTEM_TOOLBAR;
3506
3508 return ROLE_SYSTEM_TOOLTIP;
3509
3511 return ROLE_SYSTEM_OUTLINE;
3512
3514 return ROLE_SYSTEM_OUTLINE;
3515
3517 return ROLE_SYSTEM_OUTLINEITEM;
3518
3520 return ROLE_SYSTEM_WHITESPACE;
3521
3523 return ROLE_SYSTEM_GROUPING;
3524
3526 return ROLE_SYSTEM_CLIENT;
3527
3530 // Do not return ROLE_SYSTEM_WINDOW as that is a special MSAA system
3531 // role used to indicate a real native window object. It is
3532 // automatically created by oleacc.dll as a parent of the root of our
3533 // hierarchy, matching the HWND.
3534 return ROLE_SYSTEM_PANE;
3535
3542 return ROLE_SYSTEM_PANE;
3543 }
3544
3546 return ROLE_SYSTEM_GROUPING;
3547}
3548
3549bool AXPlatformNodeWin::IsWebAreaForPresentationalIframe() {
3550 if (GetData().role != ax::mojom::Role::kWebArea &&
3551 GetData().role != ax::mojom::Role::kRootWebArea) {
3552 return false;
3553 }
3554
3555 AXPlatformNodeBase* parent = FromNativeViewAccessible(GetParent());
3556 if (!parent)
3557 return false;
3558
3559 return parent->GetData().role == ax::mojom::Role::kIframePresentational;
3560}
3561
3562std::u16string AXPlatformNodeWin::UIAAriaRole() {
3563 // If this is a web area for a presentational iframe, give it a role of
3564 // something other than document so that the fact that it's a separate doc
3565 // is not exposed to AT.
3566 if (IsWebAreaForPresentationalIframe())
3567 return u"group";
3568
3569 switch (GetData().role) {
3571 return u"alert";
3572
3574 // Our MSAA implementation suggests the use of
3575 // "alert", not "alertdialog" because some
3576 // Windows screen readers are not compatible with
3577 // |ax::mojom::Role::kAlertDialog| yet.
3578 return u"alert";
3579
3581 return u"link";
3582
3585 return u"group";
3586
3588 return u"application";
3589
3591 return u"article";
3592
3594 return u"group";
3595
3598 return u"banner";
3599
3601 return u"group";
3602
3604 return u"button";
3605
3607 return u"img";
3608
3610 return u"description";
3611
3613 return u"region";
3614
3616 return u"gridcell";
3617
3619 return u"code";
3620
3622 return u"checkbox";
3623
3625 return u"region";
3626
3628 return u"textbox";
3629
3631 return u"region";
3632
3634 return u"columnheader";
3635
3638 return u"combobox";
3639
3641 return u"complementary";
3642
3645 return u"group";
3646
3649 return u"contentinfo";
3650
3653 return u"textbox";
3654
3656 return u"definition";
3657
3659 return u"description";
3660
3662 return u"list";
3663
3665 return u"listitem";
3666
3668 return u"document";
3669
3671 return u"group";
3672
3674 return u"dialog";
3675
3677 return u"button";
3678
3680 return u"directory";
3681
3683 return u"img";
3684
3689 return u"link";
3690
3694 return u"listitem";
3695
3697 return u"separator";
3698
3729 return u"group";
3730
3734 return u"document";
3735
3737 if (GetDelegate()->GetChildCount()) {
3738 return u"group";
3739 } else {
3740 return u"document";
3741 }
3742
3744 return u"emphasis";
3745
3747 return u"group";
3748
3750 return u"description";
3751
3753 return u"group";
3754
3757 return u"group";
3758
3760 return u"form";
3761
3763 return u"group";
3764
3766 return u"document";
3767
3769 return u"region";
3770
3772 return u"img";
3773
3775 return u"grid";
3776
3778 return u"group";
3779
3781 return u"heading";
3782
3784 return u"document";
3785
3787 return u"group";
3788
3790 return u"img";
3791
3793 return u"document";
3794
3796 // Internal role, not used on Windows.
3797 return u"group";
3798
3800 return u"group";
3801
3803 return u"textbox";
3804
3806 return u"group";
3807
3810 return u"description";
3811
3813 return u"grid";
3814
3816 return u"gridcell";
3817
3819 return u"row";
3820
3822 return u"link";
3823
3825 return u"list";
3826
3828 return u"listbox";
3829
3831 return u"option";
3832
3834 return u"listview";
3835
3837 return u"listitem";
3838
3840 if (!GetDelegate()->GetChildCount()) {
3841 // There's only a name attribute when using Legacy layout. With Legacy
3842 // layout, list markers have no child and are considered as StaticText.
3843 // We consider a list marker as a group in LayoutNG since it has
3844 // a text child node.
3845 return u"description";
3846 }
3847 return u"group";
3848
3850 return u"log";
3851
3853 return u"main";
3854
3856 return u"description";
3857
3859 return u"marquee";
3860
3862 return u"group";
3863
3865 return u"menu";
3866
3868 return u"menubar";
3869
3871 return u"menuitem";
3872
3874 return u"menuitemcheckbox";
3875
3877 return u"menuitemradio";
3878
3880 return u"list";
3881
3883 return u"listitem";
3884
3886 return u"progressbar";
3887
3889 return u"navigation";
3890
3892 return u"note";
3893
3895 return u"group";
3896
3898 return u"button";
3899
3901 if (GetDelegate()->GetChildCount()) {
3902 return u"group";
3903 } else {
3904 return u"document";
3905 }
3906
3908 std::string html_tag =
3909 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
3910 if (html_tag == "select")
3911 return u"combobox";
3912 return u"button";
3913 }
3914
3916 return u"button";
3917
3919 return u"region";
3920
3922 return u"progressbar";
3923
3925 return u"radio";
3926
3928 return u"radiogroup";
3929
3931 return u"region";
3932
3933 case ax::mojom::Role::kRow: {
3934 // Role changes depending on whether row is inside a treegrid
3935 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
3936 return IsInTreeGrid() ? u"treeitem" : u"row";
3937 }
3938
3940 return u"rowgroup";
3941
3943 return u"rowheader";
3944
3946 return u"region";
3947
3949 if (GetNameAsString16().empty()) {
3950 // Do not use ARIA mapping for nameless <section>.
3951 return u"group";
3952 }
3953 // Use ARIA mapping.
3954 return u"region";
3955 }
3956
3958 return u"scrollbar";
3959
3961 return u"region";
3962
3964 return u"search";
3965
3967 return u"slider";
3968
3970 return u"slider";
3971
3973 return u"spinbutton";
3974
3976 return u"strong";
3977
3979 return u"switch";
3980
3983 return u"description";
3984
3986 return u"status";
3987
3989 return u"separator";
3990
3992 return u"img";
3993
3995 return u"tab";
3996
3998 return u"grid";
3999
4001 return u"group";
4002
4004 return u"tablist";
4005
4007 return u"tabpanel";
4008
4010 return u"listitem";
4011
4013 return u"document";
4014
4016 return u"button";
4017
4019 return u"textbox";
4020
4022 return u"searchbox";
4023
4025 return u"combobox";
4026
4028 return u"description";
4029
4031 return u"time";
4032
4034 return u"timer";
4035
4037 return u"toolbar";
4038
4040 return u"tooltip";
4041
4043 return u"tree";
4044
4046 return u"treegrid";
4047
4049 return u"treeitem";
4050
4052 return u"separator";
4053
4055 return u"group";
4056
4058 return u"document";
4059
4066 return u"region";
4067 }
4068
4070 return u"document";
4071}
4072
4073std::u16string AXPlatformNodeWin::ComputeUIAProperties() {
4074 std::vector<std::u16string> properties;
4075 const AXNodeData& data = GetData();
4076
4077 BoolAttributeToUIAAriaProperty(
4078 properties, ax::mojom::BoolAttribute::kLiveAtomic, "atomic");
4079 BoolAttributeToUIAAriaProperty(properties, ax::mojom::BoolAttribute::kBusy,
4080 "busy");
4081
4082 switch (data.GetCheckedState()) {
4084 break;
4087 properties.emplace_back(u"pressed=false");
4088 } else if (data.role == ax::mojom::Role::kSwitch) {
4089 // ARIA switches are exposed to Windows accessibility as toggle
4090 // buttons. For maximum compatibility with ATs, we expose both the
4091 // pressed and checked states.
4092 properties.emplace_back(u"pressed=false");
4093 properties.emplace_back(u"checked=false");
4094 } else {
4095 properties.emplace_back(u"checked=false");
4096 }
4097 break;
4100 properties.emplace_back(u"pressed=true");
4101 } else if (data.role == ax::mojom::Role::kSwitch) {
4102 // ARIA switches are exposed to Windows accessibility as toggle
4103 // buttons. For maximum compatibility with ATs, we expose both the
4104 // pressed and checked states.
4105 properties.emplace_back(u"pressed=true");
4106 properties.emplace_back(u"checked=true");
4107 } else {
4108 properties.emplace_back(u"checked=true");
4109 }
4110 break;
4113 properties.emplace_back(u"pressed=mixed");
4114 } else if (data.role == ax::mojom::Role::kSwitch) {
4115 // This is disallowed both by the ARIA standard and by Blink.
4117 } else {
4118 properties.emplace_back(u"checked=mixed");
4119 }
4120 break;
4121 }
4122
4123 const auto restriction = static_cast<ax::mojom::Restriction>(
4124 GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
4125 if (restriction == ax::mojom::Restriction::kDisabled) {
4126 properties.push_back(u"disabled=true");
4127 } else {
4128 // The readonly property is complex on Windows. We set "readonly=true"
4129 // on *some* document structure roles such as paragraph, heading or list
4130 // even if the node data isn't marked as read only, as long as the
4131 // node is not editable.
4132 if (GetData().IsReadOnlyOrDisabled())
4133 properties.push_back(u"readonly=true");
4134 }
4135
4136 // aria-dropeffect is deprecated in WAI-ARIA 1.1.
4137 if (data.HasIntAttribute(ax::mojom::IntAttribute::kDropeffect)) {
4138 properties.push_back(u"dropeffect=" +
4139 base::UTF8ToUTF16(data.DropeffectBitfieldToString()));
4140 }
4141 StateToUIAAriaProperty(properties, ax::mojom::State::kExpanded, "expanded");
4142 BoolAttributeToUIAAriaProperty(properties, ax::mojom::BoolAttribute::kGrabbed,
4143 "grabbed");
4144
4145 switch (static_cast<ax::mojom::HasPopup>(
4146 data.GetIntAttribute(ax::mojom::IntAttribute::kHasPopup))) {
4148 break;
4150 properties.push_back(u"haspopup=true");
4151 break;
4153 properties.push_back(u"haspopup=menu");
4154 break;
4156 properties.push_back(u"haspopup=listbox");
4157 break;
4159 properties.push_back(u"haspopup=tree");
4160 break;
4162 properties.push_back(u"haspopup=grid");
4163 break;
4165 properties.push_back(u"haspopup=dialog");
4166 break;
4167 }
4168
4169 if (IsInvisibleOrIgnored())
4170 properties.push_back(u"hidden=true");
4171
4172 if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState) &&
4173 GetIntAttribute(ax::mojom::IntAttribute::kInvalidState) !=
4174 static_cast<int32_t>(ax::mojom::InvalidState::kFalse)) {
4175 properties.push_back(u"invalid=true");
4176 }
4177
4178 IntAttributeToUIAAriaProperty(
4179 properties, ax::mojom::IntAttribute::kHierarchicalLevel, "level");
4180 StringAttributeToUIAAriaProperty(
4181 properties, ax::mojom::StringAttribute::kLiveStatus, "live");
4182 StateToUIAAriaProperty(properties, ax::mojom::State::kMultiline, "multiline");
4183 StateToUIAAriaProperty(properties, ax::mojom::State::kMultiselectable,
4184 "multiselectable");
4185 IntAttributeToUIAAriaProperty(properties, ax::mojom::IntAttribute::kPosInSet,
4186 "posinset");
4187 StringAttributeToUIAAriaProperty(
4188 properties, ax::mojom::StringAttribute::kLiveRelevant, "relevant");
4189 StateToUIAAriaProperty(properties, ax::mojom::State::kRequired, "required");
4190 BoolAttributeToUIAAriaProperty(
4191 properties, ax::mojom::BoolAttribute::kSelected, "selected");
4192 IntAttributeToUIAAriaProperty(properties, ax::mojom::IntAttribute::kSetSize,
4193 "setsize");
4194
4195 int32_t sort_direction;
4196 if (IsTableHeader(data.role) &&
4198 &sort_direction)) {
4199 switch (static_cast<ax::mojom::SortDirection>(sort_direction)) {
4201 break;
4203 properties.push_back(u"sort=none");
4204 break;
4206 properties.push_back(u"sort=ascending");
4207 break;
4209 properties.push_back(u"sort=descending");
4210 break;
4212 properties.push_back(u"sort=other");
4213 break;
4214 }
4215 }
4216
4217 if (data.IsRangeValueSupported()) {
4218 FloatAttributeToUIAAriaProperty(
4219 properties, ax::mojom::FloatAttribute::kMaxValueForRange, "valuemax");
4220 FloatAttributeToUIAAriaProperty(
4221 properties, ax::mojom::FloatAttribute::kMinValueForRange, "valuemin");
4222 StringAttributeToUIAAriaProperty(
4223 properties, ax::mojom::StringAttribute::kValue, "valuetext");
4224
4225 std::u16string value_now = GetRangeValueText();
4226 SanitizeStringAttributeForUIAAriaProperty(value_now, &value_now);
4227 if (!value_now.empty())
4228 properties.push_back(u"valuenow=" + value_now);
4229 }
4230
4231 std::u16string result = base::JoinString(properties, u";");
4232 return result;
4233}
4234
4235LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
4236 // If this is a web area for a presentational iframe, give it a role of
4237 // something other than document so that the fact that it's a separate doc
4238 // is not exposed to AT.
4239 if (IsWebAreaForPresentationalIframe())
4240 return UIA_GroupControlTypeId;
4241
4242 switch (GetData().role) {
4244 return UIA_TextControlTypeId;
4245
4247 // Our MSAA implementation suggests the use of
4248 // |UIA_TextControlTypeId|, not |UIA_PaneControlTypeId| because some
4249 // Windows screen readers are not compatible with
4250 // |ax::mojom::Role::kAlertDialog| yet.
4251 return UIA_TextControlTypeId;
4252
4254 return UIA_HyperlinkControlTypeId;
4255
4258 return ROLE_SYSTEM_GROUPING;
4259
4261 return UIA_PaneControlTypeId;
4262
4264 return UIA_GroupControlTypeId;
4265
4267 return UIA_GroupControlTypeId;
4268
4271 return UIA_GroupControlTypeId;
4272
4274 return UIA_GroupControlTypeId;
4275
4277 return UIA_ButtonControlTypeId;
4278
4280 return UIA_ImageControlTypeId;
4281
4283 return UIA_TextControlTypeId;
4284
4286 return UIA_PaneControlTypeId;
4287
4289 return UIA_DataItemControlTypeId;
4290
4292 return UIA_CheckBoxControlTypeId;
4293
4295 return UIA_PaneControlTypeId;
4296
4298 return UIA_TextControlTypeId;
4299
4301 return UIA_ButtonControlTypeId;
4302
4304 return UIA_PaneControlTypeId;
4305
4307 return UIA_DataItemControlTypeId;
4308
4311 return UIA_ComboBoxControlTypeId;
4312
4314 return UIA_GroupControlTypeId;
4315
4318 return UIA_GroupControlTypeId;
4319
4322 return UIA_GroupControlTypeId;
4323
4326 return UIA_EditControlTypeId;
4327
4329 return UIA_GroupControlTypeId;
4330
4332 return UIA_TextControlTypeId;
4333
4335 return UIA_ListControlTypeId;
4336
4338 return UIA_ListItemControlTypeId;
4339
4341 return UIA_DocumentControlTypeId;
4342
4344 return UIA_GroupControlTypeId;
4345
4347 return UIA_PaneControlTypeId;
4348
4350 return UIA_ButtonControlTypeId;
4351
4353 return UIA_ListControlTypeId;
4354
4356 return UIA_ImageControlTypeId;
4357
4362 return UIA_HyperlinkControlTypeId;
4363
4367 return UIA_ListItemControlTypeId;
4368
4370 return UIA_SeparatorControlTypeId;
4371
4402 return UIA_GroupControlTypeId;
4403
4407 return UIA_DocumentControlTypeId;
4408
4410 return UIA_PaneControlTypeId;
4411
4413 return UIA_TextControlTypeId;
4414
4416 return UIA_GroupControlTypeId;
4417
4419 return UIA_TextControlTypeId;
4420
4422 return UIA_GroupControlTypeId;
4423
4426 return UIA_GroupControlTypeId;
4427
4429 return UIA_GroupControlTypeId;
4430
4432 return UIA_GroupControlTypeId;
4433
4435 return UIA_DocumentControlTypeId;
4436
4438 return UIA_PaneControlTypeId;
4439
4441 return UIA_ImageControlTypeId;
4442
4444 return UIA_DataGridControlTypeId;
4445
4447 return UIA_GroupControlTypeId;
4448
4450 return UIA_TextControlTypeId;
4451
4453 return UIA_DocumentControlTypeId;
4454
4456 return UIA_GroupControlTypeId;
4457
4459 return UIA_ImageControlTypeId;
4460
4462 return UIA_DocumentControlTypeId;
4463
4465 return UIA_GroupControlTypeId;
4466
4468 return UIA_DocumentControlTypeId;
4469
4472 return UIA_TextControlTypeId;
4473
4475 return UIA_TableControlTypeId;
4476
4478 return UIA_DataItemControlTypeId;
4479
4481 return UIA_DataItemControlTypeId;
4482
4484 return UIA_HyperlinkControlTypeId;
4485
4487 return UIA_ListControlTypeId;
4488
4490 return UIA_ListControlTypeId;
4491
4493 return UIA_ListItemControlTypeId;
4494
4496 return UIA_DataGridControlTypeId;
4497
4499 return UIA_ListItemControlTypeId;
4500
4502 if (!GetDelegate()->GetChildCount()) {
4503 // There's only a name attribute when using Legacy layout. With Legacy
4504 // layout, list markers have no child and are considered as StaticText.
4505 // We consider a list marker as a group in LayoutNG since it has
4506 // a text child node.
4507 return UIA_TextControlTypeId;
4508 }
4509 return UIA_GroupControlTypeId;
4510
4512 return UIA_GroupControlTypeId;
4513
4515 return UIA_GroupControlTypeId;
4516
4518 return UIA_TextControlTypeId;
4519
4521 return UIA_TextControlTypeId;
4522
4524 return UIA_GroupControlTypeId;
4525
4527 return UIA_MenuControlTypeId;
4528
4530 return UIA_MenuBarControlTypeId;
4531
4533 return UIA_MenuItemControlTypeId;
4534
4536 return UIA_CheckBoxControlTypeId;
4537
4539 return UIA_RadioButtonControlTypeId;
4540
4542 return UIA_ListControlTypeId;
4543
4545 return UIA_ListItemControlTypeId;
4546
4548 return UIA_ProgressBarControlTypeId;
4549
4551 return UIA_GroupControlTypeId;
4552
4554 return UIA_GroupControlTypeId;
4555
4557 return UIA_GroupControlTypeId;
4558
4560 return UIA_CustomControlTypeId;
4561
4563 if (GetDelegate()->GetChildCount()) {
4564 return UIA_GroupControlTypeId;
4565 } else {
4566 return UIA_DocumentControlTypeId;
4567 }
4568
4570 std::string html_tag =
4571 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
4572 if (html_tag == "select")
4573 return UIA_ComboBoxControlTypeId;
4574 return UIA_ButtonControlTypeId;
4575 }
4576
4578 return UIA_ButtonControlTypeId;
4579
4581 return UIA_PaneControlTypeId;
4582
4584 return UIA_ProgressBarControlTypeId;
4585
4587 return UIA_RadioButtonControlTypeId;
4588
4590 return UIA_GroupControlTypeId;
4591
4593 return UIA_GroupControlTypeId;
4594
4595 case ax::mojom::Role::kRow: {
4596 // Role changes depending on whether row is inside a treegrid
4597 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
4598 return IsInTreeGrid() ? UIA_TreeItemControlTypeId
4599 : UIA_DataItemControlTypeId;
4600 }
4601
4603 return UIA_GroupControlTypeId;
4604
4606 return UIA_DataItemControlTypeId;
4607
4609 return UIA_PaneControlTypeId;
4610
4612 return UIA_GroupControlTypeId;
4613
4615 return UIA_ScrollBarControlTypeId;
4616
4618 return UIA_PaneControlTypeId;
4619
4621 return UIA_GroupControlTypeId;
4622
4624 return UIA_SliderControlTypeId;
4625
4627 return UIA_SliderControlTypeId;
4628
4630 return UIA_SpinnerControlTypeId;
4631
4633 return UIA_ButtonControlTypeId;
4634
4637 return UIA_TextControlTypeId;
4638
4640 return UIA_StatusBarControlTypeId;
4641
4643 return UIA_TextControlTypeId;
4644
4646 return UIA_SeparatorControlTypeId;
4647
4649 return UIA_ImageControlTypeId;
4650
4652 return UIA_TabItemControlTypeId;
4653
4655 return UIA_TableControlTypeId;
4656
4658 return UIA_GroupControlTypeId;
4659
4661 return UIA_TabControlTypeId;
4662
4664 return UIA_PaneControlTypeId;
4665
4667 return UIA_ListItemControlTypeId;
4668
4670 return UIA_DocumentControlTypeId;
4671
4673 return UIA_ButtonControlTypeId;
4674
4677 return UIA_EditControlTypeId;
4678
4680 return UIA_ComboBoxControlTypeId;
4681
4684 return UIA_TextControlTypeId;
4685
4687 return UIA_PaneControlTypeId;
4688
4690 return UIA_ToolBarControlTypeId;
4691
4693 return UIA_ToolTipControlTypeId;
4694
4696 return UIA_TreeControlTypeId;
4697
4699 return UIA_DataGridControlTypeId;
4700
4702 return UIA_TreeItemControlTypeId;
4703
4705 return UIA_SeparatorControlTypeId;
4706
4708 return UIA_GroupControlTypeId;
4709
4711 return UIA_DocumentControlTypeId;
4712
4721 return UIA_PaneControlTypeId;
4722 }
4723
4725 return UIA_DocumentControlTypeId;
4726}
4727
4728AXPlatformNodeWin* AXPlatformNodeWin::ComputeUIALabeledBy() {
4729 // Not all control types expect a value for this property.
4730 if (!CanHaveUIALabeledBy())
4731 return nullptr;
4732
4733 // This property only accepts static text elements to be returned. Find the
4734 // first static text used to label this node.
4735 for (int32_t id : GetData().GetIntListAttribute(
4737 auto* node_win =
4738 static_cast<AXPlatformNodeWin*>(GetDelegate()->GetFromNodeID(id));
4739 if (!node_win)
4740 continue;
4741
4742 // If this node is a static text, then simply return the node itself.
4743 if (IsValidUiaRelationTarget(node_win) &&
4744 node_win->GetData().role == ax::mojom::Role::kStaticText) {
4745 return node_win;
4746 }
4747
4748 // Otherwise, find the first static text node in its descendants.
4749 for (auto iter = node_win->GetDelegate()->ChildrenBegin();
4750 *iter != *node_win->GetDelegate()->ChildrenEnd(); ++(*iter)) {
4751 AXPlatformNodeWin* child = static_cast<AXPlatformNodeWin*>(
4753 iter->GetNativeViewAccessible()));
4754 if (IsValidUiaRelationTarget(child) &&
4755 child->GetData().role == ax::mojom::Role::kStaticText) {
4756 return child;
4757 }
4758 }
4759 }
4760
4761 return nullptr;
4762}
4763
4764bool AXPlatformNodeWin::CanHaveUIALabeledBy() {
4765 // Not all control types expect a value for this property. See
4766 // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-supportinguiautocontroltypes
4767 // for a complete list of control types. Each one of them has specific
4768 // expectations regarding the UIA_LabeledByPropertyId.
4769 switch (ComputeUIAControlType()) {
4770 case UIA_ButtonControlTypeId:
4771 case UIA_CheckBoxControlTypeId:
4772 case UIA_DataItemControlTypeId:
4773 case UIA_MenuControlTypeId:
4774 case UIA_MenuBarControlTypeId:
4775 case UIA_RadioButtonControlTypeId:
4776 case UIA_ScrollBarControlTypeId:
4777 case UIA_SeparatorControlTypeId:
4778 case UIA_StatusBarControlTypeId:
4779 case UIA_TabItemControlTypeId:
4780 case UIA_TextControlTypeId:
4781 case UIA_ToolBarControlTypeId:
4782 case UIA_ToolTipControlTypeId:
4783 case UIA_TreeItemControlTypeId:
4784 return false;
4785 default:
4786 return true;
4787 }
4788}
4789
4790bool AXPlatformNodeWin::IsNameExposed() const {
4791 const AXNodeData& data = GetData();
4792 switch (data.role) {
4794 return !GetDelegate()->GetChildCount();
4795 default:
4796 return true;
4797 }
4798}
4799
4800bool AXPlatformNodeWin::IsUIAControl() const {
4801 // UIA provides multiple "views": raw, content and control. We only want to
4802 // populate the content and control views with items that make sense to
4803 // traverse over.
4804
4805 if (GetDelegate()->IsWebContent()) {
4806 // Invisible or ignored elements should not show up in control view at all.
4807 if (IsInvisibleOrIgnored())
4808 return false;
4809
4810 if (IsText()) {
4811 // A text leaf can be a UIAControl, but text inside of a heading, link,
4812 // button, etc. where the role allows the name to be generated from the
4813 // content is not. We want to avoid reading out a button, moving to the
4814 // next item, and then reading out the button's text child, causing the
4815 // text to be effectively repeated.
4816 auto* parent = FromNativeViewAccessible(GetDelegate()->GetParent());
4817 while (parent) {
4818 const AXNodeData& data = parent->GetData();
4819 if (IsCellOrTableHeader(data.role))
4820 return false;
4821 switch (data.role) {
4843 return false;
4844 default:
4845 break;
4846 }
4847 parent = FromNativeViewAccessible(parent->GetParent());
4848 }
4849 } // end of text only case.
4850
4851 const AXNodeData& data = GetData();
4852 // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-treeoverview#control-view
4853 // The control view also includes noninteractive UI items that contribute
4854 // to the logical structure of the UI.
4855 if (IsControl(data.role) || ComputeUIALandmarkType() ||
4856 IsTableLike(data.role) || IsList(data.role)) {
4857 return true;
4858 }
4859 if (IsImage(data.role)) {
4860 // If the author provides an explicitly empty alt text attribute then
4861 // the image is decorational and should not be considered as a control.
4862 if (data.role == ax::mojom::Role::kImage &&
4863 data.GetNameFrom() ==
4865 return false;
4866 }
4867 return true;
4868 }
4869 switch (data.role) {
4886 return true;
4887 default:
4888 break;
4889 }
4890 // Classify generic containers that are not clickable or focusable and have
4891 // no name, description, landmark type, and is not the root of editable
4892 // content as not controls.
4893 // Doing so helps Narrator find all the content of live regions.
4894 if (!data.GetBoolAttribute(ax::mojom::BoolAttribute::kHasAriaAttribute) &&
4895 !data.GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
4896 GetNameAsString16().empty() &&
4898 .empty() &&
4899 !data.HasState(ax::mojom::State::kFocusable) && !data.IsClickable()) {
4900 return false;
4901 }
4902
4903 return true;
4904 } // end of web-content only case.
4905
4906 const AXNodeData& data = GetData();
4907 return !((IsReadOnlySupported(data.role) && data.IsReadOnlyOrDisabled()) ||
4909 (data.IsIgnored() && !data.HasState(ax::mojom::State::kFocusable)));
4910}
4911
4912std::optional<LONG> AXPlatformNodeWin::ComputeUIALandmarkType() const {
4913 const AXNodeData& data = GetData();
4914 switch (data.role) {
4920 return UIA_CustomLandmarkTypeId;
4921
4923 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
4924 // https://w3c.github.io/core-aam/#mapping_role_table
4925 // While the HTML-AAM spec states that <form> without an accessible name
4926 // should have no corresponding role, removing the role breaks both
4927 // aria-setsize and aria-posinset.
4928 // The only other difference for UIA is that it should not be a landmark.
4929 // If the author provided an accessible name, or the role was explicit,
4930 // then allow the form landmark.
4931 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName) ||
4932 data.HasStringAttribute(ax::mojom::StringAttribute::kRole)) {
4933 return UIA_FormLandmarkTypeId;
4934 }
4935 return {};
4936
4938 return UIA_MainLandmarkTypeId;
4939
4941 return UIA_NavigationLandmarkTypeId;
4942
4944 return UIA_SearchLandmarkTypeId;
4945
4948 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
4949 return UIA_CustomLandmarkTypeId;
4951
4952 default:
4953 return {};
4954 }
4955}
4956
4957bool AXPlatformNodeWin::IsInaccessibleDueToAncestor() const {
4958 AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
4960 while (parent) {
4961 if (parent->ShouldHideChildrenForUIA())
4962 return true;
4963 parent = static_cast<AXPlatformNodeWin*>(
4964 FromNativeViewAccessible(parent->GetParent()));
4965 }
4966 return false;
4967}
4968
4969bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
4970 if (IsPlainTextField())
4971 return true;
4972
4973 auto role = GetData().role;
4974 if (HasPresentationalChildren(role))
4975 return true;
4976
4977 switch (role) {
4978 // Other elements that are expected by UIA to hide their children without
4979 // having "Children Presentational: True".
4980 //
4981 // TODO(bebeaudr): We might be able to remove ax::mojom::Role::kLink once
4982 // http://crbug.com/1054514 is fixed. Links should not have to hide their
4983 // children.
4984 // TODO(virens): |kPdfActionableHighlight| needs to follow a fix similar to
4985 // links. At present Pdf highlghts have text nodes as children. But, we may
4986 // enable pdf highlights to have complex children like links based on user
4987 // feedback.
4989 // Links with a single text-only child should hide their subtree.
4990 if (GetChildCount() == 1) {
4991 AXPlatformNodeBase* only_child = GetFirstChild();
4992 return only_child && only_child->IsText();
4993 }
4994 return false;
4996 return true;
4997 default:
4998 return false;
4999 }
5000}
5001
5002std::u16string AXPlatformNodeWin::GetValue() const {
5003 std::u16string value = AXPlatformNodeBase::GetValue();
5004
5005 // If this doesn't have a value and is linked then set its value to the URL
5006 // attribute. This allows screen readers to read an empty link's
5007 // destination.
5008 // TODO(dougt): Look into ensuring that on click handlers correctly provide
5009 // a value here.
5010 if (value.empty() && (MSAAState() & STATE_SYSTEM_LINKED))
5011 value = GetString16Attribute(ax::mojom::StringAttribute::kUrl);
5012
5013 return value;
5014}
5015
5016bool AXPlatformNodeWin::IsPlatformCheckable() const {
5017 if (GetData().role == ax::mojom::Role::kToggleButton)
5018 return false;
5019
5021}
5022
5023bool AXPlatformNodeWin::ShouldNodeHaveFocusableState(
5024 const AXNodeData& data) const {
5025 switch (data.role) {
5029 return true;
5030
5032 AXPlatformNodeBase* parent = FromNativeViewAccessible(GetParent());
5033 return !parent || parent->GetData().role != ax::mojom::Role::kPortal;
5034 }
5035
5037 return false;
5038
5041 if (data.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected))
5042 return true;
5043 break;
5044
5045 default:
5046 break;
5047 }
5048
5049 return data.HasState(ax::mojom::State::kFocusable);
5050}
5051
5052int AXPlatformNodeWin::MSAAState() const {
5053 const AXNodeData& data = GetData();
5054 int msaa_state = 0;
5055
5056 // Map the ax::mojom::State to MSAA state. Note that some of the states are
5057 // not currently handled.
5058
5059 if (data.GetBoolAttribute(ax::mojom::BoolAttribute::kBusy))
5060 msaa_state |= STATE_SYSTEM_BUSY;
5061
5062 if (data.HasState(ax::mojom::State::kCollapsed))
5063 msaa_state |= STATE_SYSTEM_COLLAPSED;
5064
5065 if (data.HasState(ax::mojom::State::kDefault))
5066 msaa_state |= STATE_SYSTEM_DEFAULT;
5067
5068 // TODO(dougt) unhandled ux::ax::mojom::State::kEditable
5069
5070 if (data.HasState(ax::mojom::State::kExpanded))
5071 msaa_state |= STATE_SYSTEM_EXPANDED;
5072
5073 if (ShouldNodeHaveFocusableState(data))
5074 msaa_state |= STATE_SYSTEM_FOCUSABLE;
5075
5076 // Built-in autofill and autocomplete wil also set has popup.
5077 if (data.HasIntAttribute(ax::mojom::IntAttribute::kHasPopup))
5078 msaa_state |= STATE_SYSTEM_HASPOPUP;
5079
5080 // TODO(dougt) unhandled ux::ax::mojom::State::kHorizontal
5081
5082 if (data.HasState(ax::mojom::State::kHovered)) {
5083 // Expose whether or not the mouse is over an element, but suppress
5084 // this for tests because it can make the test results flaky depending
5085 // on the position of the mouse.
5086 if (GetDelegate()->ShouldIgnoreHoveredStateForTesting())
5087 msaa_state |= STATE_SYSTEM_HOTTRACKED;
5088 }
5089
5090 // If the role is IGNORED, we want these elements to be invisible so that
5091 // these nodes are hidden from the screen reader.
5092 if (IsInvisibleOrIgnored())
5093 msaa_state |= STATE_SYSTEM_INVISIBLE;
5094
5095 if (data.HasState(ax::mojom::State::kLinked))
5096 msaa_state |= STATE_SYSTEM_LINKED;
5097
5098 // TODO(dougt) unhandled ux::ax::mojom::State::kMultiline
5099
5101 msaa_state |= STATE_SYSTEM_EXTSELECTABLE;
5102 msaa_state |= STATE_SYSTEM_MULTISELECTABLE;
5103 }
5104
5105 if (GetDelegate()->IsOffscreen())
5106 msaa_state |= STATE_SYSTEM_OFFSCREEN;
5107
5108 if (data.HasState(ax::mojom::State::kProtected))
5109 msaa_state |= STATE_SYSTEM_PROTECTED;
5110
5111 // TODO(dougt) unhandled ux::ax::mojom::State::kRequired
5112 // TODO(dougt) unhandled ux::ax::mojom::State::kRichlyEditable
5113
5114 if (data.IsSelectable())
5115 msaa_state |= STATE_SYSTEM_SELECTABLE;
5116
5117 if (data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected))
5118 msaa_state |= STATE_SYSTEM_SELECTED;
5119
5120 // TODO(dougt) unhandled VERTICAL
5121
5122 if (data.HasState(ax::mojom::State::kVisited))
5123 msaa_state |= STATE_SYSTEM_TRAVERSED;
5124
5125 //
5126 // Checked state
5127 //
5128
5129 switch (data.GetCheckedState()) {
5132 break;
5135 msaa_state |= STATE_SYSTEM_PRESSED;
5136 } else if (data.role == ax::mojom::Role::kSwitch) {
5137 // ARIA switches are exposed to Windows accessibility as toggle
5138 // buttons. For maximum compatibility with ATs, we expose both the
5139 // pressed and checked states.
5140 msaa_state |= STATE_SYSTEM_PRESSED | STATE_SYSTEM_CHECKED;
5141 } else {
5142 msaa_state |= STATE_SYSTEM_CHECKED;
5143 }
5144 break;
5146 msaa_state |= STATE_SYSTEM_MIXED;
5147 break;
5148 }
5149
5150 const auto restriction = static_cast<ax::mojom::Restriction>(
5151 GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
5152 switch (restriction) {
5154 msaa_state |= STATE_SYSTEM_UNAVAILABLE;
5155 break;
5157 msaa_state |= STATE_SYSTEM_READONLY;
5158 break;
5159 default:
5160 // READONLY state is complex on Windows. We set STATE_SYSTEM_READONLY
5161 // on *some* document structure roles such as paragraph, heading or list
5162 // even if the node data isn't marked as read only, as long as the
5163 // node is not editable.
5164 if (!data.HasState(ax::mojom::State::kRichlyEditable) &&
5166 msaa_state |= STATE_SYSTEM_READONLY;
5167 }
5168 break;
5169 }
5170
5171 // Windowless plugins should have STATE_SYSTEM_UNAVAILABLE.
5172 //
5173 // (All of our plugins are windowless.)
5176 msaa_state |= STATE_SYSTEM_UNAVAILABLE;
5177 }
5178
5179 //
5180 // Handle STATE_SYSTEM_FOCUSED
5181 //
5182 gfx::NativeViewAccessible focus = GetDelegate()->GetFocus();
5183 if (focus == const_cast<AXPlatformNodeWin*>(this)->GetNativeViewAccessible())
5184 msaa_state |= STATE_SYSTEM_FOCUSED;
5185
5186 // In focused single selection UI menus and listboxes, mirror item selection
5187 // to focus. This helps NVDA read the selected option as it changes.
5188 if ((data.role == ax::mojom::Role::kListBoxOption || IsMenuItem(data.role)) &&
5189 data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
5190 AXPlatformNodeBase* container = FromNativeViewAccessible(GetParent());
5191 if (container && container->GetParent() == focus) {
5192 AXNodeData container_data = container->GetData();
5193 if ((container_data.role == ax::mojom::Role::kListBox ||
5194 container_data.role == ax::mojom::Role::kMenu) &&
5195 !container_data.HasState(ax::mojom::State::kMultiselectable)) {
5196 msaa_state |= STATE_SYSTEM_FOCUSED;
5197 }
5198 }
5199 }
5200
5201 // On Windows, the "focus" bit should be set on certain containers, like
5202 // menu bars, when visible.
5203 //
5204 // TODO(dmazzoni): this should probably check if focus is actually inside
5205 // the menu bar, but we don't currently track focus inside menu pop-ups,
5206 // and Chrome only has one menu visible at a time so this works for now.
5207 if (data.role == ax::mojom::Role::kMenuBar &&
5208 !(data.HasState(ax::mojom::State::kInvisible))) {
5209 msaa_state |= STATE_SYSTEM_FOCUSED;
5210 }
5211
5212 // Handle STATE_SYSTEM_LINKED
5213 if (GetData().role == ax::mojom::Role::kLink)
5214 msaa_state |= STATE_SYSTEM_LINKED;
5215
5216 // Special case for indeterminate progressbar.
5217 if (GetData().role == ax::mojom::Role::kProgressIndicator &&
5219 msaa_state |= STATE_SYSTEM_MIXED;
5220
5221 return msaa_state;
5222}
5223
5224// static
5225std::optional<DWORD> AXPlatformNodeWin::MojoEventToMSAAEvent(
5226 ax::mojom::Event event) {
5227 switch (event) {
5229 return EVENT_SYSTEM_ALERT;
5233 return EVENT_OBJECT_STATECHANGE;
5236 return EVENT_OBJECT_FOCUS;
5238 return EVENT_OBJECT_LIVEREGIONCHANGED;
5240 return EVENT_SYSTEM_MENUSTART;
5242 return EVENT_SYSTEM_MENUEND;
5244 return EVENT_SYSTEM_MENUPOPUPSTART;
5246 return EVENT_SYSTEM_MENUPOPUPEND;
5248 return EVENT_OBJECT_SELECTION;
5250 return EVENT_OBJECT_SELECTIONADD;
5252 return EVENT_OBJECT_SELECTIONREMOVE;
5254 return EVENT_OBJECT_NAMECHANGE;
5256 return EVENT_OBJECT_HIDE;
5258 return EVENT_OBJECT_SHOW;
5260 return EVENT_OBJECT_VALUECHANGE;
5262 return EVENT_OBJECT_TEXTSELECTIONCHANGED;
5263 default:
5264 return std::nullopt;
5265 }
5266}
5267
5268// static
5269std::optional<EVENTID> AXPlatformNodeWin::MojoEventToUIAEvent(
5270 ax::mojom::Event event) {
5271 switch (event) {
5273 return UIA_SystemAlertEventId;
5275 return UIA_Text_TextChangedEventId;
5279 return UIA_AutomationFocusChangedEventId;
5281 return UIA_LiveRegionChangedEventId;
5283 return UIA_SelectionItem_ElementSelectedEventId;
5285 return UIA_SelectionItem_ElementAddedToSelectionEventId;
5287 return UIA_SelectionItem_ElementRemovedFromSelectionEventId;
5289 return UIA_ToolTipClosedEventId;
5291 return UIA_ToolTipOpenedEventId;
5292 default:
5293 return std::nullopt;
5294 }
5295}
5296
5297std::optional<PROPERTYID> AXPlatformNodeWin::MojoEventToUIAProperty(
5298 ax::mojom::Event event) {
5299 switch (event) {
5301 return UIA_ControllerForPropertyId;
5303 return UIA_ToggleToggleStatePropertyId;
5306 return UIA_ExpandCollapseExpandCollapseStatePropertyId;
5310 return UIA_SelectionItemIsSelectedPropertyId;
5312 if (SupportsToggle(GetData().role)) {
5313 return UIA_ToggleToggleStatePropertyId;
5314 }
5315 return std::nullopt;
5316 default:
5317 return std::nullopt;
5318 }
5319}
5320
5321// static
5322BSTR AXPlatformNodeWin::GetValueAttributeAsBstr(AXPlatformNodeWin* target) {
5323 // GetValueAttributeAsBstr() has two sets of special cases depending on the
5324 // node's role.
5325 // The first set apply without regard for the nodes |value| attribute. That is
5326 // the nodes value attribute isn't consider for the first set of special
5327 // cases. For example, if the node role is ax::mojom::Role::kColorWell, we do
5328 // not care at all about the node's ax::mojom::StringAttribute::kValue
5329 // attribute. The second set of special cases only apply if the value
5330 // attribute for the node is empty. That is, if
5331 // ax::mojom::StringAttribute::kValue is empty, we do something special.
5332 std::u16string result;
5333
5334 //
5335 // Color Well special case (Use ax::mojom::IntAttribute::kColorValue)
5336 //
5337 if (target->GetData().role == ax::mojom::Role::kColorWell) {
5338 // static cast because SkColor is a 4-byte unsigned int
5339 unsigned int color = static_cast<unsigned int>(
5341
5342 // This is just ARGB
5343 unsigned int red = (((color) >> 16) & 0xFF);
5344 unsigned int green = (((color) >> 8) & 0xFF);
5345 unsigned int blue = (((color) >> 0) & 0xFF);
5346 std::u16string value_text;
5347 value_text = base::NumberToString16(red * 100 / 255) + u"% red " +
5348 base::NumberToString16(green * 100 / 255) + u"% green " +
5349 base::NumberToString16(blue * 100 / 255) + u"% blue";
5350 BSTR value = ::SysAllocString(fml::Utf16ToWideString(value_text).c_str());
5351 BASE_DCHECK(value);
5352 return value;
5353 }
5354
5355 //
5356 // Document special case (Use the document's URL)
5357 //
5358 if (target->GetData().role == ax::mojom::Role::kRootWebArea ||
5359 target->GetData().role == ax::mojom::Role::kWebArea) {
5360 result = base::UTF8ToUTF16(target->GetDelegate()->GetTreeData().url);
5361 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5362 BASE_DCHECK(value);
5363 return value;
5364 }
5365
5366 //
5367 // Links (Use ax::mojom::StringAttribute::kUrl)
5368 //
5369 if (target->GetData().role == ax::mojom::Role::kLink) {
5370 result = target->GetString16Attribute(ax::mojom::StringAttribute::kUrl);
5371 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5372 BASE_DCHECK(value);
5373 return value;
5374 }
5375
5376 // For range controls, e.g. sliders and spin buttons, |ax_attr_value| holds
5377 // the aria-valuetext if present but not the inner text. The actual value,
5378 // provided either via aria-valuenow or the actual control's value is held in
5379 // |ax::mojom::FloatAttribute::kValueForRange|.
5380 result = target->GetString16Attribute(ax::mojom::StringAttribute::kValue);
5381 if (result.empty() && target->GetData().IsRangeValueSupported()) {
5382 float fval;
5384 &fval)) {
5385 result = base::NumberToString16(fval);
5386 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5387 BASE_DCHECK(value);
5388 return value;
5389 }
5390 }
5391
5392 if (result.empty() && target->IsRichTextField())
5393 result = target->GetInnerText();
5394
5395 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5396 BASE_DCHECK(value);
5397 return value;
5398}
5399
5400HRESULT AXPlatformNodeWin::GetStringAttributeAsBstr(
5402 BSTR* value_bstr) const {
5403 std::u16string str;
5404
5405 if (!GetString16Attribute(attribute, &str))
5406 return S_FALSE;
5407
5408 *value_bstr = ::SysAllocString(fml::Utf16ToWideString(str).c_str());
5409 BASE_DCHECK(*value_bstr);
5410
5411 return S_OK;
5412}
5413
5414HRESULT AXPlatformNodeWin::GetNameAsBstr(BSTR* value_bstr) const {
5415 std::u16string str = GetNameAsString16();
5416 *value_bstr = ::SysAllocString(fml::Utf16ToWideString(str).c_str());
5417 BASE_DCHECK(*value_bstr);
5418 return S_OK;
5419}
5420
5421// TODO(gw280): https://github.com/flutter/flutter/issues/78800
5422// Alert targets
5423void AXPlatformNodeWin::AddAlertTarget() {}
5424
5425void AXPlatformNodeWin::RemoveAlertTarget() {}
5426
5427AXPlatformNodeWin* AXPlatformNodeWin::GetTargetFromChildID(
5428 const VARIANT& var_id) {
5429 if (V_VT(&var_id) != VT_I4)
5430 return nullptr;
5431
5432 LONG child_id = V_I4(&var_id);
5433 if (child_id == CHILDID_SELF)
5434 return this;
5435
5436 if (child_id >= 1 && child_id <= GetDelegate()->GetChildCount()) {
5437 // Positive child ids are a 1-based child index, used by clients
5438 // that want to enumerate all immediate children.
5439 AXPlatformNodeBase* base =
5440 FromNativeViewAccessible(GetDelegate()->ChildAtIndex(child_id - 1));
5441 return static_cast<AXPlatformNodeWin*>(base);
5442 }
5443
5444 if (child_id >= 0)
5445 return nullptr;
5446
5447 // Negative child ids can be used to map to any descendant.
5448 AXPlatformNode* node = GetFromUniqueId(-child_id);
5449 if (!node)
5450 return nullptr;
5451
5452 AXPlatformNodeBase* base =
5453 FromNativeViewAccessible(node->GetNativeViewAccessible());
5454 if (base && !base->IsDescendantOf(this))
5455 base = nullptr;
5456
5457 return static_cast<AXPlatformNodeWin*>(base);
5458}
5459
5460bool AXPlatformNodeWin::IsInTreeGrid() {
5461 AXPlatformNodeBase* container = FromNativeViewAccessible(GetParent());
5462
5463 // If parent was a rowgroup, we need to look at the grandparent
5464 if (container && container->GetData().role == ax::mojom::Role::kRowGroup)
5465 container = FromNativeViewAccessible(container->GetParent());
5466
5467 if (!container)
5468 return false;
5469
5470 return container->GetData().role == ax::mojom::Role::kTreeGrid;
5471}
5472
5473HRESULT AXPlatformNodeWin::AllocateComArrayFromVector(
5474 std::vector<LONG>& results,
5475 LONG max,
5476 LONG** selected,
5477 LONG* n_selected) {
5478 BASE_DCHECK(max > 0);
5479 BASE_DCHECK(selected);
5480 BASE_DCHECK(n_selected);
5481
5482 auto count = std::min((LONG)results.size(), max);
5483 *n_selected = count;
5484 *selected = static_cast<LONG*>(CoTaskMemAlloc(sizeof(LONG) * count));
5485
5486 for (LONG i = 0; i < count; i++)
5487 (*selected)[i] = results[i];
5488 return S_OK;
5489}
5490
5491bool AXPlatformNodeWin::IsPlaceholderText() const {
5492 if (GetData().role != ax::mojom::Role::kStaticText)
5493 return false;
5494 AXPlatformNodeWin* parent =
5495 static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(GetParent()));
5496 // Static text nodes are always expected to have a parent.
5497 BASE_DCHECK(parent);
5498 return parent->IsTextField() &&
5499 parent->HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder);
5500}
5501
5502double AXPlatformNodeWin::GetHorizontalScrollPercent() {
5503 if (!IsHorizontallyScrollable())
5504 return UIA_ScrollPatternNoScroll;
5505
5506 float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
5507 float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
5508 float x = GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
5509 return 100.0 * (x - x_min) / (x_max - x_min);
5510}
5511
5512double AXPlatformNodeWin::GetVerticalScrollPercent() {
5513 if (!IsVerticallyScrollable())
5514 return UIA_ScrollPatternNoScroll;
5515
5516 float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
5517 float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
5518 float y = GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
5519 return 100.0 * (y - y_min) / (y_max - y_min);
5520}
5521
5522BSTR AXPlatformNodeWin::GetFontNameAttributeAsBSTR() const {
5523 const std::u16string string =
5524 GetInheritedString16Attribute(ax::mojom::StringAttribute::kFontFamily);
5525
5526 return ::SysAllocString(fml::Utf16ToWideString(string).c_str());
5527}
5528
5529BSTR AXPlatformNodeWin::GetStyleNameAttributeAsBSTR() const {
5530 std::u16string style_name =
5531 GetDelegate()->GetStyleNameAttributeAsLocalizedString();
5532
5533 return ::SysAllocString(fml::Utf16ToWideString(style_name).c_str());
5534}
5535
5536TextDecorationLineStyle AXPlatformNodeWin::GetUIATextDecorationStyle(
5537 const ax::mojom::IntAttribute int_attribute) const {
5538 const ax::mojom::TextDecorationStyle text_decoration_style =
5539 static_cast<ax::mojom::TextDecorationStyle>(
5540 GetIntAttribute(int_attribute));
5541
5542 switch (text_decoration_style) {
5544 return TextDecorationLineStyle::TextDecorationLineStyle_None;
5546 return TextDecorationLineStyle::TextDecorationLineStyle_Dot;
5548 return TextDecorationLineStyle::TextDecorationLineStyle_Dash;
5550 return TextDecorationLineStyle::TextDecorationLineStyle_Single;
5552 return TextDecorationLineStyle::TextDecorationLineStyle_Double;
5554 return TextDecorationLineStyle::TextDecorationLineStyle_Wavy;
5555 }
5556}
5557
5558// IRawElementProviderSimple support methods.
5559
5560AXPlatformNodeWin::PatternProviderFactoryMethod
5561AXPlatformNodeWin::GetPatternProviderFactoryMethod(PATTERNID pattern_id) {
5562 const AXNodeData& data = GetData();
5563
5564 switch (pattern_id) {
5565 case UIA_ExpandCollapsePatternId:
5566 if (data.SupportsExpandCollapse()) {
5567 return &PatternProvider<IExpandCollapseProvider>;
5568 }
5569 break;
5570
5571 case UIA_GridPatternId:
5572 if (IsTableLike(data.role)) {
5573 return &PatternProvider<IGridProvider>;
5574 }
5575 break;
5576
5577 case UIA_GridItemPatternId:
5578 if (IsCellOrTableHeader(data.role)) {
5579 return &PatternProvider<IGridItemProvider>;
5580 }
5581 break;
5582
5583 case UIA_InvokePatternId:
5584 if (data.IsInvocable()) {
5585 return &PatternProvider<IInvokeProvider>;
5586 }
5587 break;
5588
5589 case UIA_RangeValuePatternId:
5590 if (data.IsRangeValueSupported()) {
5591 return &PatternProvider<IRangeValueProvider>;
5592 }
5593 break;
5594
5595 case UIA_ScrollPatternId:
5596 if (IsScrollable()) {
5597 return &PatternProvider<IScrollProvider>;
5598 }
5599 break;
5600
5601 case UIA_ScrollItemPatternId:
5602 return &PatternProvider<IScrollItemProvider>;
5603 break;
5604
5605 case UIA_SelectionItemPatternId:
5606 if (IsSelectionItemSupported()) {
5607 return &PatternProvider<ISelectionItemProvider>;
5608 }
5609 break;
5610
5611 case UIA_SelectionPatternId:
5613 return &PatternProvider<ISelectionProvider>;
5614 }
5615 break;
5616
5617 case UIA_TablePatternId:
5618 // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itableprovider
5619 // This control pattern is analogous to IGridProvider with the distinction
5620 // that any control implementing ITableProvider must also expose a column
5621 // and/or row header relationship for each child element.
5622 if (IsTableLike(data.role)) {
5623 std::optional<bool> table_has_headers =
5624 GetDelegate()->GetTableHasColumnOrRowHeaderNode();
5625 if (table_has_headers.has_value() && table_has_headers.value()) {
5626 return &PatternProvider<ITableProvider>;
5627 }
5628 }
5629 break;
5630
5631 case UIA_TableItemPatternId:
5632 // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itableitemprovider
5633 // This control pattern is analogous to IGridItemProvider with the
5634 // distinction that any control implementing ITableItemProvider must
5635 // expose the relationship between the individual cell and its row and
5636 // column information.
5637 if (IsCellOrTableHeader(data.role)) {
5638 std::optional<bool> table_has_headers =
5639 GetDelegate()->GetTableHasColumnOrRowHeaderNode();
5640 if (table_has_headers.has_value() && table_has_headers.value()) {
5641 return &PatternProvider<ITableItemProvider>;
5642 }
5643 }
5644 break;
5645
5646 case UIA_TextEditPatternId:
5647 case UIA_TextPatternId:
5648 if (IsText() || IsTextField() ||
5650 return &AXPlatformNodeTextProviderWin::CreateIUnknown;
5651 }
5652 break;
5653
5654 case UIA_TogglePatternId:
5655 if (SupportsToggle(data.role)) {
5656 return &PatternProvider<IToggleProvider>;
5657 }
5658 break;
5659
5660 case UIA_ValuePatternId:
5661 if (IsValuePatternSupported(GetDelegate())) {
5662 return &PatternProvider<IValueProvider>;
5663 }
5664 break;
5665
5666 case UIA_WindowPatternId:
5667 if (HasBoolAttribute(ax::mojom::BoolAttribute::kModal)) {
5668 return &PatternProvider<IWindowProvider>;
5669 }
5670 break;
5671
5672 // Not currently implemented.
5673 case UIA_AnnotationPatternId:
5674 case UIA_CustomNavigationPatternId:
5675 case UIA_DockPatternId:
5676 case UIA_DragPatternId:
5677 case UIA_DropTargetPatternId:
5678 case UIA_ItemContainerPatternId:
5679 case UIA_MultipleViewPatternId:
5680 case UIA_ObjectModelPatternId:
5681 case UIA_SpreadsheetPatternId:
5682 case UIA_SpreadsheetItemPatternId:
5683 case UIA_StylesPatternId:
5684 case UIA_SynchronizedInputPatternId:
5685 case UIA_TextPattern2Id:
5686 case UIA_TransformPatternId:
5687 case UIA_TransformPattern2Id:
5688 case UIA_VirtualizedItemPatternId:
5689 break;
5690
5691 // Provided by UIA Core; we should not implement.
5692 case UIA_LegacyIAccessiblePatternId:
5693 break;
5694 }
5695 return nullptr;
5696}
5697
5698void AXPlatformNodeWin::FireLiveRegionChangeRecursive() {
5699 const auto live_status_attr = ax::mojom::StringAttribute::kLiveStatus;
5700 if (HasStringAttribute(live_status_attr) &&
5701 GetStringAttribute(live_status_attr) != "off") {
5702 BASE_DCHECK(GetDelegate()->IsWebContent());
5703 ::UiaRaiseAutomationEvent(this, UIA_LiveRegionChangedEventId);
5704 return;
5705 }
5706
5707 for (int index = 0; index < GetChildCount(); ++index) {
5708 auto* child = static_cast<AXPlatformNodeWin*>(
5709 FromNativeViewAccessible(ChildAtIndex(index)));
5710
5711 // We assume that only web-content will have live regions; also because
5712 // this will be called on each fragment-root, there is no need to walk
5713 // through non-content nodes.
5714 if (child->GetDelegate()->IsWebContent())
5715 child->FireLiveRegionChangeRecursive();
5716 }
5717}
5718
5719AXPlatformNodeWin* AXPlatformNodeWin::GetLowestAccessibleElement() {
5720 if (!IsInaccessibleDueToAncestor())
5721 return this;
5722
5723 AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
5725 while (parent) {
5726 if (parent->ShouldHideChildrenForUIA())
5727 return parent;
5728 parent = static_cast<AXPlatformNodeWin*>(
5729 AXPlatformNode::FromNativeViewAccessible(parent->GetParent()));
5730 }
5731
5733 return nullptr;
5734}
5735
5736AXPlatformNodeWin* AXPlatformNodeWin::GetFirstTextOnlyDescendant() {
5737 for (auto* child = static_cast<AXPlatformNodeWin*>(GetFirstChild()); child;
5738 child = static_cast<AXPlatformNodeWin*>(child->GetNextSibling())) {
5739 if (child->IsText())
5740 return child;
5741 if (AXPlatformNodeWin* descendant = child->GetFirstTextOnlyDescendant())
5742 return descendant;
5743 }
5744 return nullptr;
5745}
5746
5747bool AXPlatformNodeWin::IsDescendantOf(AXPlatformNode* ancestor) const {
5748 if (!ancestor) {
5749 return false;
5750 }
5751
5752 if (AXPlatformNodeBase::IsDescendantOf(ancestor)) {
5753 return true;
5754 }
5755
5756 // Test if the ancestor is an IRawElementProviderFragmentRoot and if it
5757 // matches this node's root fragment.
5758 IRawElementProviderFragmentRoot* root;
5759 if (SUCCEEDED(
5760 const_cast<AXPlatformNodeWin*>(this)->get_FragmentRoot(&root))) {
5761 AXPlatformNodeWin* root_win;
5762 if (SUCCEEDED(root->QueryInterface(__uuidof(AXPlatformNodeWin),
5763 reinterpret_cast<void**>(&root_win)))) {
5764 return ancestor == static_cast<AXPlatformNode*>(root_win);
5765 }
5766 }
5767
5768 return false;
5769}
5770
5771} // namespace ui
ax::mojom::Event event_type
#define COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET(var_id, target)
#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, arg, target)
#define COM_OBJECT_VALIDATE_1_ARG(arg)
#define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2)
#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET(var_id, arg1, arg2, arg3, arg4, target)
#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET(var_id, arg1, arg2, target)
#define UIA_VALIDATE_CALL_1_ARG(arg)
#define UIA_VALIDATE_CALL()
void Insert(typename internal::VariantUtil< ExpectedVartype >::Type value)
constexpr int height() const
Definition rect.h:79
constexpr int y() const
Definition rect.h:69
Vector2d OffsetFromOrigin() const
Definition rect.h:108
bool IsEmpty() const
Definition rect.h:140
constexpr int x() const
Definition rect.h:62
constexpr int width() const
Definition rect.h:76
static AXFragmentRootWin * GetForAcceleratedWidget(gfx::AcceleratedWidget widget)
static AXFragmentRootWin * GetFragmentRootParentOf(gfx::NativeViewAccessible accessible)
static constexpr uint32_t kScreenReader
Definition ax_mode.h:52
static constexpr uint32_t kHTML
Definition ax_mode.h:56
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override
virtual std::u16string GetValue() const
virtual bool IsPlatformCheckable() const
virtual void Init(AXPlatformNodeDelegate *delegate)
bool IsDescendantOf(AXPlatformNode *ancestor) const override
static AXPlatformNode * FromNativeViewAccessible(gfx::NativeViewAccessible accessible)
static AXPlatformNode * Create(AXPlatformNodeDelegate *delegate)
static void NotifyAddAXModeFlags(AXMode mode_flags)
static const UiaRegistrarWin & GetInstance()
static int input(yyscan_t yyscanner)
#define BASE_FALLTHROUGH
#define BASE_UNLIKELY(x)
int32_t value
int32_t x
MockDelegate delegate_
VkInstance instance
Definition main.cc:64
FlViewAccessible * accessible
const char FlTextDirection text_direction
uint32_t * target
const char * name
Definition fuchsia.cc:50
size_t length
double y
const int32_t kUnknownAriaColumnOrRowCount
DefaultActionVerb
Definition ax_enums.h:489
TextDecorationStyle
Definition ax_enums.h:996
float GetScaleFactorForHWND(HWND hwnd)
Definition display.cc:74
std::string UTF16ToUTF8(std::u16string src)
void ReplaceChars(std::string in, std::string from, std::string to, std::string *out)
std::u16string UTF8ToUTF16(std::string src)
std::u16string ASCIIToUTF16(std::string src)
std::string JoinString(std::vector< std::string > tokens, std::string delimiter)
Dst ClampRound(Src value)
bool Contains(const Container &container, const Value &value)
std::string NumberToString(int32_t number)
std::u16string NumberToString16(float number)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36
std::u16string WideStringToUtf16(const std::wstring_view str)
std::wstring Utf16ToWideString(const std::u16string_view str)
Point PointAtOffsetFromOrigin(const Vector2d &offset_from_origin)
Definition point.h:120
Rect ToEnclosingRect(const RectF &r)
UnimplementedNativeViewAccessible * NativeViewAccessible
void CreateATLModuleIfNeeded()
Definition atl_module.h:22
bool IsImage(const ax::mojom::Role role)
const uint32_t kScreenReaderAndHTMLAccessibilityModes
bool IsValuePatternSupported(AXPlatformNodeDelegate *delegate)
bool IsContainerWithSelectableChildren(const ax::mojom::Role role)
bool IsControl(const ax::mojom::Role role)
bool IsReadOnlySupported(const ax::mojom::Role role)
const char * ToString(ax::mojom::Event event)
bool IsCellOrTableHeader(const ax::mojom::Role role)
bool HasPresentationalChildren(const ax::mojom::Role role)
bool IsMenuItem(ax::mojom::Role role)
bool IsTableLike(const ax::mojom::Role role)
bool SupportsOrientation(const ax::mojom::Role role)
bool IsTableHeader(ax::mojom::Role role)
bool SupportsToggle(const ax::mojom::Role role)
bool ShouldHaveReadonlyStateByDefault(const ax::mojom::Role role)
bool IsList(const ax::mojom::Role role)
bool IsDialog(const ax::mojom::Role role)
std::optional< int32_t > GetActivePopupAxUniqueId()
bool IsText(ax::mojom::Role role)
int32_t height
int32_t width
const size_t end
#define BASE_DCHECK(condition)
Definition logging.h:63
#define BASE_UNREACHABLE()
Definition logging.h:69
int BOOL
#define SUCCEEDED(hr)
long LONG
#define REFGUID
#define WINAPI
#define FAILED(hr)

◆ COM_OBJECT_VALIDATE_1_ARG

#define COM_OBJECT_VALIDATE_1_ARG (   arg)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg) \
return E_INVALIDARG; \
*arg = {};

Definition at line 61 of file ax_platform_node_win.cc.

66 {};

◆ COM_OBJECT_VALIDATE_2_ARGS

#define COM_OBJECT_VALIDATE_2_ARGS (   arg1,
  arg2 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {};

Definition at line 67 of file ax_platform_node_win.cc.

72 {}; \
73 if (!arg2) \
74 return E_INVALIDARG; \
75 *arg2 = {};

◆ COM_OBJECT_VALIDATE_3_ARGS

#define COM_OBJECT_VALIDATE_3_ARGS (   arg1,
  arg2,
  arg3 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {};

Definition at line 76 of file ax_platform_node_win.cc.

81 {}; \
82 if (!arg2) \
83 return E_INVALIDARG; \
84 *arg2 = {}; \
85 if (!arg3) \
86 return E_INVALIDARG; \
87 *arg3 = {};

◆ COM_OBJECT_VALIDATE_4_ARGS

#define COM_OBJECT_VALIDATE_4_ARGS (   arg1,
  arg2,
  arg3,
  arg4 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
if (!arg4) \
return E_INVALIDARG; \
*arg4 = {};

Definition at line 88 of file ax_platform_node_win.cc.

93 {}; \
94 if (!arg2) \
95 return E_INVALIDARG; \
96 *arg2 = {}; \
97 if (!arg3) \
98 return E_INVALIDARG; \
99 *arg3 = {}; \
100 if (!arg4) \
101 return E_INVALIDARG; \
102 *arg4 = {};

◆ COM_OBJECT_VALIDATE_5_ARGS

#define COM_OBJECT_VALIDATE_5_ARGS (   arg1,
  arg2,
  arg3,
  arg4,
  arg5 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
if (!arg4) \
return E_INVALIDARG; \
*arg4 = {}; \
if (!arg5) \
return E_INVALIDARG; \
*arg5 = {};

Definition at line 103 of file ax_platform_node_win.cc.

108 {}; \
109 if (!arg2) \
110 return E_INVALIDARG; \
111 *arg2 = {}; \
112 if (!arg3) \
113 return E_INVALIDARG; \
114 *arg3 = {}; \
115 if (!arg4) \
116 return E_INVALIDARG; \
117 *arg4 = {}; \
118 if (!arg5) \
119 return E_INVALIDARG; \
120 *arg5 = {};

◆ COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET

#define COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET (   var_id,
  arg,
  target 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg) \
return E_INVALIDARG; \
*arg = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;

Definition at line 129 of file ax_platform_node_win.cc.

134 {}; \
135 target = GetTargetFromChildID(var_id); \
136 if (!target) \
137 return E_INVALIDARG; \
138 if (!target->GetDelegate()) \
139 return E_INVALIDARG;

◆ COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET

#define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS_AND_GET_TARGET (   var_id,
  arg1,
  arg2,
  target 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;

Definition at line 140 of file ax_platform_node_win.cc.

146 {}; \
147 if (!arg2) \
148 return E_INVALIDARG; \
149 *arg2 = {}; \
150 target = GetTargetFromChildID(var_id); \
151 if (!target) \
152 return E_INVALIDARG; \
153 if (!target->GetDelegate()) \
154 return E_INVALIDARG;

◆ COM_OBJECT_VALIDATE_VAR_ID_3_ARGS_AND_GET_TARGET

#define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS_AND_GET_TARGET (   var_id,
  arg1,
  arg2,
  arg3,
  target 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;

Definition at line 155 of file ax_platform_node_win.cc.

161 {}; \
162 if (!arg2) \
163 return E_INVALIDARG; \
164 *arg2 = {}; \
165 if (!arg3) \
166 return E_INVALIDARG; \
167 *arg3 = {}; \
168 target = GetTargetFromChildID(var_id); \
169 if (!target) \
170 return E_INVALIDARG; \
171 if (!target->GetDelegate()) \
172 return E_INVALIDARG;

◆ COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET

#define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS_AND_GET_TARGET (   var_id,
  arg1,
  arg2,
  arg3,
  arg4,
  target 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
if (!arg1) \
return E_INVALIDARG; \
*arg1 = {}; \
if (!arg2) \
return E_INVALIDARG; \
*arg2 = {}; \
if (!arg3) \
return E_INVALIDARG; \
*arg3 = {}; \
if (!arg4) \
return E_INVALIDARG; \
*arg4 = {}; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;

Definition at line 173 of file ax_platform_node_win.cc.

179 {}; \
180 if (!arg2) \
181 return E_INVALIDARG; \
182 *arg2 = {}; \
183 if (!arg3) \
184 return E_INVALIDARG; \
185 *arg3 = {}; \
186 if (!arg4) \
187 return E_INVALIDARG; \
188 *arg4 = {}; \
189 target = GetTargetFromChildID(var_id); \
190 if (!target) \
191 return E_INVALIDARG; \
192 if (!target->GetDelegate()) \
193 return E_INVALIDARG;

◆ COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET

#define COM_OBJECT_VALIDATE_VAR_ID_AND_GET_TARGET (   var_id,
  target 
)
Value:
if (!GetDelegate()) \
return E_FAIL; \
target = GetTargetFromChildID(var_id); \
if (!target) \
return E_INVALIDARG; \
if (!target->GetDelegate()) \
return E_INVALIDARG;

Definition at line 121 of file ax_platform_node_win.cc.