Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
ax_platform_node_win.cc
Go to the documentation of this file.
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <wrl/client.h>
8#include <wrl/implements.h>
9
10#include <algorithm>
11#include <map>
12#include <set>
13#include <string>
14#include <unordered_set>
15#include <utility>
16
17#include "ax/ax_action_data.h"
18#include "ax/ax_active_popup.h"
19#include "ax/ax_enum_util.h"
20#include "ax/ax_mode_observer.h"
21#include "ax/ax_node_data.h"
22#include "ax/ax_node_position.h"
24#include "ax/ax_tree_data.h"
30
35#include "shellscalingapi.h"
36#include "uia_registrar_win.h"
37
38#include "base/logging.h"
39#include "base/win/atl_module.h"
40#include "base/win/display.h"
41#include "flutter/fml/platform/win/wstring_conversion.h"
43
44// From ax.constants.mojom
45namespace ax {
46namespace mojom {
47const int32_t kUnknownAriaColumnOrRowCount = -1;
48}
49} // namespace ax
50
51//
52// Macros to use at the top of any AXPlatformNodeWin function that implements
53// a non-UIA COM interface. Because COM objects are reference counted and
54// clients are completely untrusted, it's important to always first check that
55// our object is still valid, and then check that all pointer arguments are not
56// NULL.
57//
58#define COM_OBJECT_VALIDATE() \
59 if (!GetDelegate()) \
60 return E_FAIL;
61#define COM_OBJECT_VALIDATE_1_ARG(arg) \
62 if (!GetDelegate()) \
63 return E_FAIL; \
64 if (!arg) \
65 return E_INVALIDARG; \
66 *arg = {};
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
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
269 gfx::NativeViewAccessible accessible) {
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,
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.
564 if (event_type == ax::mojom::Event::kSelection) {
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) {
572 event_type = ax::mojom::Event::kFocus;
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()) {
579 event_type = ax::mojom::Event::kFocus;
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) {
586 event_type = ax::mojom::Event::kFocus;
587 }
588 }
589 }
590
591 if (event_type == ax::mojom::Event::kValueChanged) {
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.
624 if (event_type == ax::mojom::Event::kAlert)
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// IExpandCollapseProvider implementation.
1201//
1202
1203IFACEMETHODIMP AXPlatformNodeWin::Collapse() {
1205 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1206 return UIA_E_ELEMENTNOTAVAILABLE;
1207
1208 if (GetData().HasState(ax::mojom::State::kCollapsed))
1209 return UIA_E_INVALIDOPERATION;
1210
1211 AXActionData action_data;
1212 action_data.action = ax::mojom::Action::kDoDefault;
1213 if (GetDelegate()->AccessibilityPerformAction(action_data))
1214 return S_OK;
1215 return E_FAIL;
1216}
1217
1218IFACEMETHODIMP AXPlatformNodeWin::Expand() {
1220 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1221 return UIA_E_ELEMENTNOTAVAILABLE;
1222
1223 if (GetData().HasState(ax::mojom::State::kExpanded))
1224 return UIA_E_INVALIDOPERATION;
1225
1226 AXActionData action_data;
1227 action_data.action = ax::mojom::Action::kDoDefault;
1228 if (GetDelegate()->AccessibilityPerformAction(action_data))
1229 return S_OK;
1230 return E_FAIL;
1231}
1232
1233ExpandCollapseState AXPlatformNodeWin::ComputeExpandCollapseState() const {
1234 const AXNodeData& data = GetData();
1235
1236 // Since a menu button implies there is a popup and it is either expanded or
1237 // collapsed, and it should not support ExpandCollapseState_LeafNode.
1238 // According to the UIA spec, ExpandCollapseState_LeafNode indicates that the
1239 // element neither expands nor collapses.
1240 if (data.IsMenuButton()) {
1241 if (data.IsButtonPressed())
1242 return ExpandCollapseState_Expanded;
1243 return ExpandCollapseState_Collapsed;
1244 }
1245
1246 if (data.HasState(ax::mojom::State::kExpanded)) {
1247 return ExpandCollapseState_Expanded;
1248 } else if (data.HasState(ax::mojom::State::kCollapsed)) {
1249 return ExpandCollapseState_Collapsed;
1250 } else {
1251 return ExpandCollapseState_LeafNode;
1252 }
1253}
1254
1255IFACEMETHODIMP AXPlatformNodeWin::get_ExpandCollapseState(
1256 ExpandCollapseState* result) {
1258
1259 *result = ComputeExpandCollapseState();
1260
1261 return S_OK;
1262}
1263
1264//
1265// IGridItemProvider implementation.
1266//
1267
1268IFACEMETHODIMP AXPlatformNodeWin::get_Column(int* result) {
1270 std::optional<int> column = GetTableColumn();
1271 if (!column)
1272 return E_FAIL;
1273 *result = *column;
1274 return S_OK;
1275}
1276
1277IFACEMETHODIMP AXPlatformNodeWin::get_ColumnSpan(int* result) {
1279 std::optional<int> column_span = GetTableColumnSpan();
1280 if (!column_span)
1281 return E_FAIL;
1282 *result = *column_span;
1283 return S_OK;
1284}
1285
1286IFACEMETHODIMP AXPlatformNodeWin::get_ContainingGrid(
1287 IRawElementProviderSimple** result) {
1289
1290 AXPlatformNodeBase* table = GetTable();
1291 if (!table)
1292 return E_FAIL;
1293
1294 auto* node_win = static_cast<AXPlatformNodeWin*>(table);
1295 node_win->AddRef();
1296 *result = static_cast<IRawElementProviderSimple*>(node_win);
1297 return S_OK;
1298}
1299
1300IFACEMETHODIMP AXPlatformNodeWin::get_Row(int* result) {
1302 std::optional<int> row = GetTableRow();
1303 if (!row)
1304 return E_FAIL;
1305 *result = *row;
1306 return S_OK;
1307}
1308
1309IFACEMETHODIMP AXPlatformNodeWin::get_RowSpan(int* result) {
1311 std::optional<int> row_span = GetTableRowSpan();
1312 if (!row_span)
1313 return E_FAIL;
1314 *result = *row_span;
1315 return S_OK;
1316}
1317
1318//
1319// IGridProvider implementation.
1320//
1321
1322IFACEMETHODIMP AXPlatformNodeWin::GetItem(int row,
1323 int column,
1324 IRawElementProviderSimple** result) {
1326
1327 AXPlatformNodeBase* cell = GetTableCell(row, column);
1328 if (!cell)
1329 return E_INVALIDARG;
1330
1331 auto* node_win = static_cast<AXPlatformNodeWin*>(cell);
1332 node_win->AddRef();
1333 *result = static_cast<IRawElementProviderSimple*>(node_win);
1334 return S_OK;
1335}
1336
1337IFACEMETHODIMP AXPlatformNodeWin::get_RowCount(int* result) {
1339
1340 std::optional<int> row_count = GetTableAriaRowCount();
1341 if (!row_count)
1342 row_count = GetTableRowCount();
1343
1344 if (!row_count || *row_count == ax::mojom::kUnknownAriaColumnOrRowCount)
1345 return E_UNEXPECTED;
1346 *result = *row_count;
1347 return S_OK;
1348}
1349
1350IFACEMETHODIMP AXPlatformNodeWin::get_ColumnCount(int* result) {
1352
1353 std::optional<int> column_count = GetTableAriaColumnCount();
1354 if (!column_count)
1355 column_count = GetTableColumnCount();
1356
1357 if (!column_count ||
1358 *column_count == ax::mojom::kUnknownAriaColumnOrRowCount) {
1359 return E_UNEXPECTED;
1360 }
1361 *result = *column_count;
1362 return S_OK;
1363}
1364
1365//
1366// IInvokeProvider implementation.
1367//
1368
1369IFACEMETHODIMP AXPlatformNodeWin::Invoke() {
1371
1372 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1373 return UIA_E_ELEMENTNOTENABLED;
1374
1375 AXActionData action_data;
1376 action_data.action = ax::mojom::Action::kDoDefault;
1377 GetDelegate()->AccessibilityPerformAction(action_data);
1378
1379 return S_OK;
1380}
1381
1382//
1383// IScrollItemProvider implementation.
1384//
1385
1386IFACEMETHODIMP AXPlatformNodeWin::ScrollIntoView() {
1388 gfx::Rect r = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
1389 r -= r.OffsetFromOrigin();
1390
1391 AXActionData action_data;
1392 action_data.target_node_id = GetData().id;
1393 action_data.target_rect = r;
1394 action_data.horizontal_scroll_alignment =
1396 action_data.vertical_scroll_alignment =
1398 action_data.scroll_behavior =
1400 if (GetDelegate()->AccessibilityPerformAction(action_data))
1401 return S_OK;
1402 return E_FAIL;
1403}
1404
1405//
1406// IScrollProvider implementation.
1407//
1408
1409IFACEMETHODIMP AXPlatformNodeWin::Scroll(ScrollAmount horizontal_amount,
1410 ScrollAmount vertical_amount) {
1412 if (!IsScrollable())
1413 return E_FAIL;
1414
1415 AXActionData action_data;
1416 action_data.target_node_id = GetData().id;
1417 action_data.action = ax::mojom::Action::kSetScrollOffset;
1418 action_data.target_point = gfx::PointAtOffsetFromOrigin(
1419 CalculateUIAScrollPoint(horizontal_amount, vertical_amount));
1420 if (GetDelegate()->AccessibilityPerformAction(action_data))
1421 return S_OK;
1422 return E_FAIL;
1423}
1424
1425IFACEMETHODIMP AXPlatformNodeWin::SetScrollPercent(double horizontal_percent,
1426 double vertical_percent) {
1428 if (!IsScrollable())
1429 return E_FAIL;
1430
1431 const double x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
1432 const double x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
1433 const double y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
1434 const double y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
1435 const int x =
1436 base::ClampRound(horizontal_percent / 100.0 * (x_max - x_min) + x_min);
1437 const int y =
1438 base::ClampRound(vertical_percent / 100.0 * (y_max - y_min) + y_min);
1439 const gfx::Point scroll_to(x, y);
1440
1441 AXActionData action_data;
1442 action_data.target_node_id = GetData().id;
1443 action_data.action = ax::mojom::Action::kSetScrollOffset;
1444 action_data.target_point = scroll_to;
1445 if (GetDelegate()->AccessibilityPerformAction(action_data))
1446 return S_OK;
1447 return E_FAIL;
1448}
1449
1450IFACEMETHODIMP AXPlatformNodeWin::get_HorizontallyScrollable(BOOL* result) {
1452 *result = IsHorizontallyScrollable();
1453 return S_OK;
1454}
1455
1456IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalScrollPercent(double* result) {
1458 *result = GetHorizontalScrollPercent();
1459 return S_OK;
1460}
1461
1462// Horizontal size of the viewable region as a percentage of the total content
1463// area.
1464IFACEMETHODIMP AXPlatformNodeWin::get_HorizontalViewSize(double* result) {
1466 if (!IsHorizontallyScrollable()) {
1467 *result = 100.;
1468 return S_OK;
1469 }
1470
1471 gfx::RectF clipped_bounds(GetDelegate()->GetBoundsRect(
1473 float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
1474 float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
1475 float total_width = clipped_bounds.width() + x_max - x_min;
1476 BASE_DCHECK(clipped_bounds.width() <= total_width);
1477 *result = 100.0 * clipped_bounds.width() / total_width;
1478 return S_OK;
1479}
1480
1481IFACEMETHODIMP AXPlatformNodeWin::get_VerticallyScrollable(BOOL* result) {
1483 *result = IsVerticallyScrollable();
1484 return S_OK;
1485}
1486
1487IFACEMETHODIMP AXPlatformNodeWin::get_VerticalScrollPercent(double* result) {
1489 *result = GetVerticalScrollPercent();
1490 return S_OK;
1491}
1492
1493// Vertical size of the viewable region as a percentage of the total content
1494// area.
1495IFACEMETHODIMP AXPlatformNodeWin::get_VerticalViewSize(double* result) {
1497 if (!IsVerticallyScrollable()) {
1498 *result = 100.0;
1499 return S_OK;
1500 }
1501
1502 gfx::RectF clipped_bounds(GetDelegate()->GetBoundsRect(
1504 float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
1505 float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
1506 float total_height = clipped_bounds.height() + y_max - y_min;
1507 BASE_DCHECK(clipped_bounds.height() <= total_height);
1508 *result = 100.0 * clipped_bounds.height() / total_height;
1509 return S_OK;
1510}
1511
1512//
1513// ISelectionItemProvider implementation.
1514//
1515
1516HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(
1517 bool selected) const {
1519 if (GetData().GetRestriction() == ax::mojom::Restriction::kDisabled)
1520 return UIA_E_ELEMENTNOTENABLED;
1521
1522 // The platform implements selection follows focus for single-selection
1523 // container elements. Focus action can change a node's accessibility selected
1524 // state, but does not cause the actual control to be selected.
1525 // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
1526 // https://www.w3.org/TR/core-aam-1.2/#mapping_events_selection
1527 //
1528 // We don't want to perform |Action::kDoDefault| for an ax node that has
1529 // |kSelected=true| and |kSelectedFromFocus=false|, because perform
1530 // |Action::kDoDefault| may cause the control to be unselected. However, if an
1531 // ax node is selected due to focus, i.e. |kSelectedFromFocus=true|, we need
1532 // to perform |Action::kDoDefault| on the ax node, since focus action only
1533 // changes an ax node's accessibility selected state to |kSelected=true| and
1534 // no |Action::kDoDefault| was performed on that node yet. So we need to
1535 // perform |Action::kDoDefault| on the ax node to cause its associated control
1536 // to be selected.
1537 if (selected == ISelectionItemProviderIsSelected() &&
1539 return S_OK;
1540
1541 AXActionData data;
1543 if (GetDelegate()->AccessibilityPerformAction(data))
1544 return S_OK;
1545 return UIA_E_INVALIDOPERATION;
1546}
1547
1548bool AXPlatformNodeWin::ISelectionItemProviderIsSelected() const {
1549 // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
1550 // SelectionItem.IsSelected is set according to the True or False value of
1551 // aria-checked for 'radio' and 'menuitemradio' roles.
1552 if (GetData().role == ax::mojom::Role::kRadioButton ||
1553 GetData().role == ax::mojom::Role::kMenuItemRadio)
1554 return GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue;
1555
1556 // https://www.w3.org/TR/wai-aria-1.1/#aria-selected
1557 // SelectionItem.IsSelected is set according to the True or False value of
1558 // aria-selected.
1559 return GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
1560}
1561
1562IFACEMETHODIMP AXPlatformNodeWin::AddToSelection() {
1563 return ISelectionItemProviderSetSelected(true);
1564}
1565
1566IFACEMETHODIMP AXPlatformNodeWin::RemoveFromSelection() {
1567 return ISelectionItemProviderSetSelected(false);
1568}
1569
1570IFACEMETHODIMP AXPlatformNodeWin::Select() {
1571 return ISelectionItemProviderSetSelected(true);
1572}
1573
1574IFACEMETHODIMP AXPlatformNodeWin::get_IsSelected(BOOL* result) {
1576 *result = ISelectionItemProviderIsSelected();
1577 return S_OK;
1578}
1579
1580IFACEMETHODIMP AXPlatformNodeWin::get_SelectionContainer(
1581 IRawElementProviderSimple** result) {
1583
1584 auto* node_win = static_cast<AXPlatformNodeWin*>(GetSelectionContainer());
1585 if (!node_win)
1586 return E_FAIL;
1587
1588 node_win->AddRef();
1589 *result = static_cast<IRawElementProviderSimple*>(node_win);
1590 return S_OK;
1591}
1592
1593//
1594// ISelectionProvider implementation.
1595//
1596
1597IFACEMETHODIMP AXPlatformNodeWin::GetSelection(SAFEARRAY** result) {
1599
1600 std::vector<AXPlatformNodeBase*> selected_children;
1601 int max_items = GetMaxSelectableItems();
1602 if (max_items)
1603 GetSelectedItems(max_items, &selected_children);
1604
1605 LONG selected_children_count = selected_children.size();
1606 *result = SafeArrayCreateVector(VT_UNKNOWN, 0, selected_children_count);
1607 if (!*result)
1608 return E_OUTOFMEMORY;
1609
1610 for (LONG i = 0; i < selected_children_count; ++i) {
1611 AXPlatformNodeWin* children =
1612 static_cast<AXPlatformNodeWin*>(selected_children[i]);
1613 HRESULT hr = SafeArrayPutElement(
1614 *result, &i, static_cast<IRawElementProviderSimple*>(children));
1615 if (FAILED(hr)) {
1616 SafeArrayDestroy(*result);
1617 *result = nullptr;
1618 return hr;
1619 }
1620 }
1621 return S_OK;
1622}
1623
1624IFACEMETHODIMP AXPlatformNodeWin::get_CanSelectMultiple(BOOL* result) {
1626 *result = GetData().HasState(ax::mojom::State::kMultiselectable);
1627 return S_OK;
1628}
1629
1630IFACEMETHODIMP AXPlatformNodeWin::get_IsSelectionRequired(BOOL* result) {
1632 *result = GetData().HasState(ax::mojom::State::kRequired);
1633 return S_OK;
1634}
1635
1636//
1637// ITableItemProvider methods.
1638//
1639
1640IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaderItems(SAFEARRAY** result) {
1642
1643 std::optional<int> column = GetTableColumn();
1644 if (!column)
1645 return E_FAIL;
1646
1647 std::vector<int32_t> column_header_ids =
1648 GetDelegate()->GetColHeaderNodeIds(*column);
1649
1650 std::vector<AXPlatformNodeWin*> platform_node_list =
1651 CreatePlatformNodeVectorFromRelationIdVector(column_header_ids);
1652
1653 *result = CreateUIAElementsSafeArray(platform_node_list);
1654 return S_OK;
1655}
1656
1657IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaderItems(SAFEARRAY** result) {
1659
1660 std::optional<int> row = GetTableRow();
1661 if (!row)
1662 return E_FAIL;
1663
1664 std::vector<int32_t> row_header_ids =
1665 GetDelegate()->GetRowHeaderNodeIds(*row);
1666
1667 std::vector<AXPlatformNodeWin*> platform_node_list =
1668 CreatePlatformNodeVectorFromRelationIdVector(row_header_ids);
1669
1670 *result = CreateUIAElementsSafeArray(platform_node_list);
1671 return S_OK;
1672}
1673
1674//
1675// ITableProvider methods.
1676//
1677
1678IFACEMETHODIMP AXPlatformNodeWin::GetColumnHeaders(SAFEARRAY** result) {
1680
1681 std::vector<int32_t> column_header_ids = GetDelegate()->GetColHeaderNodeIds();
1682
1683 std::vector<AXPlatformNodeWin*> platform_node_list =
1684 CreatePlatformNodeVectorFromRelationIdVector(column_header_ids);
1685
1686 *result = CreateUIAElementsSafeArray(platform_node_list);
1687 return S_OK;
1688}
1689
1690IFACEMETHODIMP AXPlatformNodeWin::GetRowHeaders(SAFEARRAY** result) {
1692
1693 std::vector<int32_t> row_header_ids = GetDelegate()->GetRowHeaderNodeIds();
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
1702IFACEMETHODIMP AXPlatformNodeWin::get_RowOrColumnMajor(
1703 RowOrColumnMajor* result) {
1705
1706 // Tables and ARIA grids are always in row major order
1707 // see AXPlatformNodeBase::GetTableCell
1708 *result = RowOrColumnMajor_RowMajor;
1709 return S_OK;
1710}
1711
1712//
1713// IToggleProvider implementation.
1714//
1715
1716IFACEMETHODIMP AXPlatformNodeWin::Toggle() {
1718 AXActionData action_data;
1719 action_data.action = ax::mojom::Action::kDoDefault;
1720
1721 if (GetDelegate()->AccessibilityPerformAction(action_data))
1722 return S_OK;
1723 return E_FAIL;
1724}
1725
1726IFACEMETHODIMP AXPlatformNodeWin::get_ToggleState(ToggleState* result) {
1728 const auto checked_state = GetData().GetCheckedState();
1729 if (checked_state == ax::mojom::CheckedState::kTrue) {
1730 *result = ToggleState_On;
1731 } else if (checked_state == ax::mojom::CheckedState::kMixed) {
1732 *result = ToggleState_Indeterminate;
1733 } else {
1734 *result = ToggleState_Off;
1735 }
1736 return S_OK;
1737}
1738
1739//
1740// IValueProvider implementation.
1741//
1742
1743IFACEMETHODIMP AXPlatformNodeWin::SetValue(LPCWSTR value) {
1745 if (!value)
1746 return E_INVALIDARG;
1747
1748 if (GetData().IsReadOnlyOrDisabled())
1749 return UIA_E_ELEMENTNOTENABLED;
1750
1751 AXActionData data;
1754 if (GetDelegate()->AccessibilityPerformAction(data))
1755 return S_OK;
1756 return E_FAIL;
1757}
1758
1759IFACEMETHODIMP AXPlatformNodeWin::get_IsReadOnly(BOOL* result) {
1761 *result = GetData().IsReadOnlyOrDisabled();
1762 return S_OK;
1763}
1764
1765IFACEMETHODIMP AXPlatformNodeWin::get_Value(BSTR* result) {
1767 *result = GetValueAttributeAsBstr(this);
1768 return S_OK;
1769}
1770
1771//
1772// IWindowProvider implementation.
1773//
1774
1775IFACEMETHODIMP AXPlatformNodeWin::SetVisualState(
1776 WindowVisualState window_visual_state) {
1778 return UIA_E_NOTSUPPORTED;
1779}
1780
1781IFACEMETHODIMP AXPlatformNodeWin::Close() {
1783 return UIA_E_NOTSUPPORTED;
1784}
1785
1786IFACEMETHODIMP AXPlatformNodeWin::WaitForInputIdle(int milliseconds,
1787 BOOL* result) {
1789 return UIA_E_NOTSUPPORTED;
1790}
1791
1792IFACEMETHODIMP AXPlatformNodeWin::get_CanMaximize(BOOL* result) {
1794 return UIA_E_NOTSUPPORTED;
1795}
1796
1797IFACEMETHODIMP AXPlatformNodeWin::get_CanMinimize(BOOL* result) {
1799 return UIA_E_NOTSUPPORTED;
1800}
1801
1802IFACEMETHODIMP AXPlatformNodeWin::get_IsModal(BOOL* result) {
1804
1805 *result = GetBoolAttribute(ax::mojom::BoolAttribute::kModal);
1806
1807 return S_OK;
1808}
1809
1810IFACEMETHODIMP AXPlatformNodeWin::get_WindowVisualState(
1811 WindowVisualState* result) {
1813 return UIA_E_NOTSUPPORTED;
1814}
1815
1816IFACEMETHODIMP AXPlatformNodeWin::get_WindowInteractionState(
1817 WindowInteractionState* result) {
1819 return UIA_E_NOTSUPPORTED;
1820}
1821
1822IFACEMETHODIMP AXPlatformNodeWin::get_IsTopmost(BOOL* result) {
1824 return UIA_E_NOTSUPPORTED;
1825}
1826
1827//
1828// IRangeValueProvider implementation.
1829//
1830
1831IFACEMETHODIMP AXPlatformNodeWin::SetValue(double value) {
1833 AXActionData data;
1835 data.value = base::NumberToString(value);
1836 if (GetDelegate()->AccessibilityPerformAction(data))
1837 return S_OK;
1838 return E_FAIL;
1839}
1840
1841IFACEMETHODIMP AXPlatformNodeWin::get_LargeChange(double* result) {
1843 float attribute;
1845 &attribute)) {
1846 *result = attribute * kLargeChangeScaleFactor;
1847 return S_OK;
1848 }
1849 return E_FAIL;
1850}
1851
1852IFACEMETHODIMP AXPlatformNodeWin::get_Maximum(double* result) {
1854 float attribute;
1856 &attribute)) {
1857 *result = attribute;
1858 return S_OK;
1859 }
1860 return E_FAIL;
1861}
1862
1863IFACEMETHODIMP AXPlatformNodeWin::get_Minimum(double* result) {
1865 float attribute;
1867 &attribute)) {
1868 *result = attribute;
1869 return S_OK;
1870 }
1871 return E_FAIL;
1872}
1873
1874IFACEMETHODIMP AXPlatformNodeWin::get_SmallChange(double* result) {
1876 float attribute;
1878 &attribute)) {
1879 *result = attribute;
1880 return S_OK;
1881 }
1882 return E_FAIL;
1883}
1884
1885IFACEMETHODIMP AXPlatformNodeWin::get_Value(double* result) {
1887 float attribute;
1888 if (GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange,
1889 &attribute)) {
1890 *result = attribute;
1891 return S_OK;
1892 }
1893 return E_FAIL;
1894}
1895
1896//
1897// IRawElementProviderFragment implementation.
1898//
1899
1900IFACEMETHODIMP AXPlatformNodeWin::Navigate(
1901 NavigateDirection direction,
1902 IRawElementProviderFragment** element_provider) {
1903 UIA_VALIDATE_CALL_1_ARG(element_provider);
1904
1905 *element_provider = nullptr;
1906
1907 //
1908 // Navigation to a fragment root node:
1909 //
1910 // In order for the platform-neutral accessibility tree to support IA2 and UIA
1911 // simultaneously, we handle navigation to and from fragment roots in UIA
1912 // specific code. Consider the following platform-neutral tree:
1913 //
1914 // N1
1915 // _____/ \_____
1916 // / \
1917 // N2---N3---N4---N5
1918 // / \ / \
1919 // N6---N7 N8---N9
1920 //
1921 // N3 and N5 are nodes for which we need a fragment root. This will correspond
1922 // to the following tree in UIA:
1923 //
1924 // U1
1925 // _____/ \_____
1926 // / \
1927 // U2---R3---U4---R5
1928 // | |
1929 // U3 U5
1930 // / \ / \
1931 // U6---U7 U8---U9
1932 //
1933 // Ux is the platform node for Nx.
1934 // R3 and R5 are the fragment root nodes for U3 and U5 respectively.
1935 //
1936 // Navigation has the following behaviors:
1937 //
1938 // 1. Parent navigation: If source node Ux is the child of a fragment root,
1939 // return Rx. Otherwise, consult the platform-neutral tree.
1940 // 2. First/last child navigation: If target node Ux is the child of a
1941 // fragment root and the source node isn't Rx, return Rx. Otherwise, return
1942 // Ux.
1943 // 3. Next/previous sibling navigation:
1944 // a. If source node Ux is the child of a fragment root, return nullptr.
1945 // b. If target node Ux is the child of a fragment root, return Rx.
1946 // Otherwise, return Ux.
1947 //
1948 // Note that the condition in 3b is a special case of the condition in 2. In
1949 // 3b, the source node is never Rx. So in the code, we collapse them to a
1950 // common implementation.
1951 //
1952 // Navigation from an Rx node is set up by delegate APIs on AXFragmentRootWin.
1953 //
1954 gfx::NativeViewAccessible neighbor = nullptr;
1955 switch (direction) {
1956 case NavigateDirection_Parent: {
1957 // 1. If source node Ux is the child of a fragment root, return Rx.
1958 // Otherwise, consult the platform-neutral tree.
1959 AXFragmentRootWin* fragment_root =
1960 AXFragmentRootWin::GetFragmentRootParentOf(GetNativeViewAccessible());
1961 if (BASE_UNLIKELY(fragment_root)) {
1962 neighbor = fragment_root->GetNativeViewAccessible();
1963 } else {
1964 neighbor = GetParent();
1965 }
1966 } break;
1967
1968 case NavigateDirection_FirstChild:
1969 if (GetChildCount() > 0)
1970 neighbor = GetFirstChild()->GetNativeViewAccessible();
1971 break;
1972
1973 case NavigateDirection_LastChild:
1974 if (GetChildCount() > 0)
1975 neighbor = GetLastChild()->GetNativeViewAccessible();
1976 break;
1977
1978 case NavigateDirection_NextSibling:
1979 // 3a. If source node Ux is the child of a fragment root, return nullptr.
1981 GetNativeViewAccessible()) == nullptr) {
1982 AXPlatformNodeBase* neighbor_node = GetNextSibling();
1983 if (neighbor_node)
1984 neighbor = neighbor_node->GetNativeViewAccessible();
1985 }
1986 break;
1987
1988 case NavigateDirection_PreviousSibling:
1989 // 3a. If source node Ux is the child of a fragment root, return nullptr.
1991 GetNativeViewAccessible()) == nullptr) {
1992 AXPlatformNodeBase* neighbor_node = GetPreviousSibling();
1993 if (neighbor_node)
1994 neighbor = neighbor_node->GetNativeViewAccessible();
1995 }
1996 break;
1997
1998 default:
2000 break;
2001 }
2002
2003 if (neighbor) {
2004 if (direction != NavigateDirection_Parent) {
2005 // 2 / 3b. If target node Ux is the child of a fragment root and the
2006 // source node isn't Rx, return Rx.
2007 AXFragmentRootWin* fragment_root =
2009 if (BASE_UNLIKELY(fragment_root && fragment_root != GetDelegate()))
2010 neighbor = fragment_root->GetNativeViewAccessible();
2011 }
2012 neighbor->QueryInterface(IID_PPV_ARGS(element_provider));
2013 }
2014
2015 return S_OK;
2016}
2017
2018void AXPlatformNodeWin::GetRuntimeIdArray(
2019 AXPlatformNodeWin::RuntimeIdArray& runtime_id) {
2020 runtime_id[0] = UiaAppendRuntimeId;
2021 runtime_id[1] = GetUniqueId();
2022}
2023
2024IFACEMETHODIMP AXPlatformNodeWin::GetRuntimeId(SAFEARRAY** runtime_id) {
2025 UIA_VALIDATE_CALL_1_ARG(runtime_id);
2026
2027 RuntimeIdArray id_array;
2028 GetRuntimeIdArray(id_array);
2029 *runtime_id = ::SafeArrayCreateVector(VT_I4, 0, id_array.size());
2030
2031 int* array_data = nullptr;
2032 ::SafeArrayAccessData(*runtime_id, reinterpret_cast<void**>(&array_data));
2033
2034 size_t runtime_id_byte_count = id_array.size() * sizeof(int);
2035 memcpy_s(array_data, runtime_id_byte_count, id_array.data(),
2036 runtime_id_byte_count);
2037
2038 ::SafeArrayUnaccessData(*runtime_id);
2039
2040 return S_OK;
2041}
2042
2043IFACEMETHODIMP AXPlatformNodeWin::get_BoundingRectangle(
2044 UiaRect* screen_physical_pixel_bounds) {
2045 UIA_VALIDATE_CALL_1_ARG(screen_physical_pixel_bounds);
2046
2050 screen_physical_pixel_bounds->left = bounds.x();
2051 screen_physical_pixel_bounds->top = bounds.y();
2052 screen_physical_pixel_bounds->width = bounds.width();
2053 screen_physical_pixel_bounds->height = bounds.height();
2054 return S_OK;
2055}
2056
2057IFACEMETHODIMP AXPlatformNodeWin::GetEmbeddedFragmentRoots(
2058 SAFEARRAY** embedded_fragment_roots) {
2059 UIA_VALIDATE_CALL_1_ARG(embedded_fragment_roots);
2060
2061 *embedded_fragment_roots = nullptr;
2062 return S_OK;
2063}
2064
2065IFACEMETHODIMP AXPlatformNodeWin::SetFocus() {
2067
2068 AXActionData action_data;
2069 action_data.action = ax::mojom::Action::kFocus;
2070 delegate_->AccessibilityPerformAction(action_data);
2071 return S_OK;
2072}
2073
2074IFACEMETHODIMP AXPlatformNodeWin::get_FragmentRoot(
2075 IRawElementProviderFragmentRoot** fragment_root) {
2076 UIA_VALIDATE_CALL_1_ARG(fragment_root);
2077
2078 gfx::AcceleratedWidget widget =
2079 delegate_->GetTargetForNativeAccessibilityEvent();
2080 if (widget) {
2081 AXFragmentRootWin* root =
2083 if (root != nullptr) {
2084 root->GetNativeViewAccessible()->QueryInterface(
2085 IID_PPV_ARGS(fragment_root));
2086 BASE_DCHECK(*fragment_root);
2087 return S_OK;
2088 }
2089 }
2090
2091 *fragment_root = nullptr;
2092 return UIA_E_ELEMENTNOTAVAILABLE;
2093}
2094
2095//
2096// IRawElementProviderSimple implementation.
2097//
2098
2099IFACEMETHODIMP AXPlatformNodeWin::GetPatternProvider(PATTERNID pattern_id,
2100 IUnknown** result) {
2101 return GetPatternProviderImpl(pattern_id, result);
2102}
2103
2104HRESULT AXPlatformNodeWin::GetPatternProviderImpl(PATTERNID pattern_id,
2105 IUnknown** result) {
2107
2108 *result = nullptr;
2109
2110 PatternProviderFactoryMethod factory_method =
2111 GetPatternProviderFactoryMethod(pattern_id);
2112 if (factory_method)
2113 (*factory_method)(this, result);
2114
2115 return S_OK;
2116}
2117
2118IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
2119 VARIANT* result) {
2120 return GetPropertyValueImpl(property_id, result);
2121}
2122
2123HRESULT AXPlatformNodeWin::GetPropertyValueImpl(PROPERTYID property_id,
2124 VARIANT* result) {
2126
2127 result->vt = VT_EMPTY;
2128
2129 int int_attribute;
2130 const AXNodeData& data = GetData();
2131
2132 // Default UIA Property Ids.
2133 switch (property_id) {
2134 case UIA_AriaPropertiesPropertyId:
2135 result->vt = VT_BSTR;
2136 result->bstrVal = ::SysAllocString(
2137 fml::Utf16ToWideString(ComputeUIAProperties()).c_str());
2138 break;
2139
2140 case UIA_AriaRolePropertyId:
2141 result->vt = VT_BSTR;
2142 result->bstrVal =
2143 ::SysAllocString(fml::Utf16ToWideString(UIAAriaRole()).c_str());
2144 break;
2145
2146 case UIA_AutomationIdPropertyId:
2147 V_VT(result) = VT_BSTR;
2148 V_BSTR(result) = ::SysAllocString(
2149 fml::Utf16ToWideString(GetDelegate()->GetAuthorUniqueId()).c_str());
2150 break;
2151
2152 case UIA_ClassNamePropertyId:
2153 result->vt = VT_BSTR;
2154 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kClassName,
2155 &result->bstrVal);
2156 break;
2157
2158 case UIA_ClickablePointPropertyId:
2159 result->vt = VT_ARRAY | VT_R8;
2160 result->parray = CreateClickablePointArray();
2161 break;
2162
2163 case UIA_ControllerForPropertyId:
2164 result->vt = VT_ARRAY | VT_UNKNOWN;
2165 result->parray = CreateUIAControllerForArray();
2166 break;
2167
2168 case UIA_ControlTypePropertyId:
2169 result->vt = VT_I4;
2170 result->lVal = ComputeUIAControlType();
2171 break;
2172
2173 case UIA_CulturePropertyId: {
2174 std::optional<LCID> lcid = GetCultureAttributeAsLCID();
2175 if (!lcid)
2176 return E_FAIL;
2177 result->vt = VT_I4;
2178 result->lVal = lcid.value();
2179 break;
2180 }
2181
2182 case UIA_DescribedByPropertyId:
2183 result->vt = VT_ARRAY | VT_UNKNOWN;
2184 result->parray = CreateUIAElementsArrayForRelation(
2186 break;
2187
2188 case UIA_FlowsFromPropertyId:
2189 V_VT(result) = VT_ARRAY | VT_UNKNOWN;
2190 V_ARRAY(result) = CreateUIAElementsArrayForReverseRelation(
2192 break;
2193
2194 case UIA_FlowsToPropertyId:
2195 result->vt = VT_ARRAY | VT_UNKNOWN;
2196 result->parray = CreateUIAElementsArrayForRelation(
2198 break;
2199
2200 case UIA_FrameworkIdPropertyId:
2201 V_VT(result) = VT_BSTR;
2202 V_BSTR(result) = SysAllocString(FRAMEWORK_ID);
2203 break;
2204
2205 case UIA_HasKeyboardFocusPropertyId:
2206 result->vt = VT_BOOL;
2207 result->boolVal = (delegate_->GetFocus() == GetNativeViewAccessible())
2208 ? VARIANT_TRUE
2209 : VARIANT_FALSE;
2210 break;
2211
2212 case UIA_FullDescriptionPropertyId:
2213 result->vt = VT_BSTR;
2214 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kDescription,
2215 &result->bstrVal);
2216 break;
2217
2218 case UIA_HelpTextPropertyId:
2219 if (HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder)) {
2220 V_VT(result) = VT_BSTR;
2221 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kPlaceholder,
2222 &V_BSTR(result));
2223 } else if (data.GetNameFrom() == ax::mojom::NameFrom::kPlaceholder ||
2224 data.GetNameFrom() == ax::mojom::NameFrom::kTitle) {
2225 V_VT(result) = VT_BSTR;
2226 GetNameAsBstr(&V_BSTR(result));
2227 } else if (HasStringAttribute(ax::mojom::StringAttribute::kTooltip)) {
2228 V_VT(result) = VT_BSTR;
2229 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kTooltip,
2230 &V_BSTR(result));
2231 }
2232 break;
2233
2234 case UIA_IsContentElementPropertyId:
2235 case UIA_IsControlElementPropertyId:
2236 result->vt = VT_BOOL;
2237 result->boolVal = IsUIAControl() ? VARIANT_TRUE : VARIANT_FALSE;
2238 break;
2239
2240 case UIA_IsDataValidForFormPropertyId:
2241 if (data.GetIntAttribute(ax::mojom::IntAttribute::kInvalidState,
2242 &int_attribute)) {
2243 result->vt = VT_BOOL;
2244 result->boolVal =
2245 (static_cast<int>(ax::mojom::InvalidState::kFalse) == int_attribute)
2246 ? VARIANT_TRUE
2247 : VARIANT_FALSE;
2248 }
2249 break;
2250
2251 case UIA_IsDialogPropertyId:
2252 result->vt = VT_BOOL;
2253 result->boolVal = IsDialog(data.role) ? VARIANT_TRUE : VARIANT_FALSE;
2254 break;
2255
2256 case UIA_IsKeyboardFocusablePropertyId:
2257 result->vt = VT_BOOL;
2258 result->boolVal =
2259 ShouldNodeHaveFocusableState(data) ? VARIANT_TRUE : VARIANT_FALSE;
2260 break;
2261
2262 case UIA_IsOffscreenPropertyId:
2263 result->vt = VT_BOOL;
2264 result->boolVal =
2265 GetDelegate()->IsOffscreen() ? VARIANT_TRUE : VARIANT_FALSE;
2266 break;
2267
2268 case UIA_IsRequiredForFormPropertyId:
2269 result->vt = VT_BOOL;
2270 if (data.HasState(ax::mojom::State::kRequired)) {
2271 result->boolVal = VARIANT_TRUE;
2272 } else {
2273 result->boolVal = VARIANT_FALSE;
2274 }
2275 break;
2276
2277 case UIA_ItemStatusPropertyId: {
2278 // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
2279 // aria-sort='ascending|descending|other' is mapped for the
2280 // HeaderItem Control Type.
2281 int32_t sort_direction;
2282 if (IsTableHeader(data.role) &&
2284 &sort_direction)) {
2285 switch (static_cast<ax::mojom::SortDirection>(sort_direction)) {
2288 break;
2290 V_VT(result) = VT_BSTR;
2291 V_BSTR(result) = SysAllocString(L"ascending");
2292 break;
2294 V_VT(result) = VT_BSTR;
2295 V_BSTR(result) = SysAllocString(L"descending");
2296 break;
2298 V_VT(result) = VT_BSTR;
2299 V_BSTR(result) = SysAllocString(L"other");
2300 break;
2301 }
2302 }
2303 break;
2304 }
2305
2306 case UIA_LabeledByPropertyId:
2307 if (AXPlatformNodeWin* node = ComputeUIALabeledBy()) {
2308 result->vt = VT_UNKNOWN;
2309 result->punkVal = node->GetNativeViewAccessible();
2310 result->punkVal->AddRef();
2311 }
2312 break;
2313
2314 case UIA_LocalizedControlTypePropertyId: {
2315 std::u16string localized_control_type = GetRoleDescription();
2316 if (!localized_control_type.empty()) {
2317 result->vt = VT_BSTR;
2318 result->bstrVal = ::SysAllocString(
2319 fml::Utf16ToWideString(localized_control_type).c_str());
2320 }
2321 // If a role description has not been provided, leave as VT_EMPTY.
2322 // UIA core handles Localized Control type for some built-in types and
2323 // also has a mapping for ARIA roles. To get these defaults, we need to
2324 // have returned VT_EMPTY.
2325 } break;
2326
2327 case UIA_NamePropertyId:
2328 if (IsNameExposed()) {
2329 result->vt = VT_BSTR;
2330 GetNameAsBstr(&result->bstrVal);
2331 }
2332 break;
2333
2334 case UIA_OrientationPropertyId:
2335 if (SupportsOrientation(data.role)) {
2336 if (data.HasState(ax::mojom::State::kHorizontal) &&
2338 BASE_UNREACHABLE(); // << "An accessibility object cannot have a
2339 // horizontal "
2340 //"and a vertical orientation at the same time.";
2341 }
2342 if (data.HasState(ax::mojom::State::kHorizontal)) {
2343 result->vt = VT_I4;
2344 result->intVal = OrientationType_Horizontal;
2345 }
2346 if (data.HasState(ax::mojom::State::kVertical)) {
2347 result->vt = VT_I4;
2348 result->intVal = OrientationType_Vertical;
2349 }
2350 } else {
2351 result->vt = VT_I4;
2352 result->intVal = OrientationType_None;
2353 }
2354 break;
2355
2356 case UIA_IsEnabledPropertyId:
2357 V_VT(result) = VT_BOOL;
2358 switch (data.GetRestriction()) {
2360 V_BOOL(result) = VARIANT_FALSE;
2361 break;
2362
2365 V_BOOL(result) = VARIANT_TRUE;
2366 break;
2367 }
2368 break;
2369
2370 case UIA_IsPasswordPropertyId:
2371 result->vt = VT_BOOL;
2372 result->boolVal = data.HasState(ax::mojom::State::kProtected)
2373 ? VARIANT_TRUE
2374 : VARIANT_FALSE;
2375 break;
2376
2377 case UIA_AcceleratorKeyPropertyId:
2378 if (data.HasStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts)) {
2379 result->vt = VT_BSTR;
2380 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kKeyShortcuts,
2381 &result->bstrVal);
2382 }
2383 break;
2384
2385 case UIA_AccessKeyPropertyId:
2386 if (data.HasStringAttribute(ax::mojom::StringAttribute::kAccessKey)) {
2387 result->vt = VT_BSTR;
2388 GetStringAttributeAsBstr(ax::mojom::StringAttribute::kAccessKey,
2389 &result->bstrVal);
2390 }
2391 break;
2392
2393 case UIA_IsPeripheralPropertyId:
2394 result->vt = VT_BOOL;
2395 result->boolVal = VARIANT_FALSE;
2396 break;
2397
2398 case UIA_LevelPropertyId:
2400 &int_attribute)) {
2401 result->vt = VT_I4;
2402 result->intVal = int_attribute;
2403 }
2404 break;
2405
2406 case UIA_LiveSettingPropertyId: {
2407 result->vt = VT_I4;
2408 result->intVal = LiveSetting::Off;
2409
2410 std::string string_attribute;
2411 if (data.GetStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
2412 &string_attribute)) {
2413 if (string_attribute == "polite")
2414 result->intVal = LiveSetting::Polite;
2415 else if (string_attribute == "assertive")
2416 result->intVal = LiveSetting::Assertive;
2417 }
2418 break;
2419 }
2420
2421 case UIA_OptimizeForVisualContentPropertyId:
2422 result->vt = VT_BOOL;
2423 result->boolVal = VARIANT_FALSE;
2424 break;
2425
2426 case UIA_PositionInSetPropertyId: {
2427 std::optional<int> pos_in_set = GetPosInSet();
2428 if (pos_in_set) {
2429 result->vt = VT_I4;
2430 result->intVal = *pos_in_set;
2431 }
2432 } break;
2433
2434 case UIA_ScrollHorizontalScrollPercentPropertyId: {
2435 V_VT(result) = VT_R8;
2436 V_R8(result) = GetHorizontalScrollPercent();
2437 break;
2438 }
2439
2440 case UIA_ScrollVerticalScrollPercentPropertyId: {
2441 V_VT(result) = VT_R8;
2442 V_R8(result) = GetVerticalScrollPercent();
2443 break;
2444 }
2445
2446 case UIA_SizeOfSetPropertyId: {
2447 std::optional<int> set_size = GetSetSize();
2448 if (set_size) {
2449 result->vt = VT_I4;
2450 result->intVal = *set_size;
2451 }
2452 break;
2453 }
2454
2455 case UIA_LandmarkTypePropertyId: {
2456 std::optional<LONG> landmark_type = ComputeUIALandmarkType();
2457 if (landmark_type) {
2458 result->vt = VT_I4;
2459 result->intVal = landmark_type.value();
2460 }
2461 break;
2462 }
2463
2464 case UIA_LocalizedLandmarkTypePropertyId: {
2465 std::u16string localized_landmark_type =
2466 GetDelegate()->GetLocalizedStringForLandmarkType();
2467 if (!localized_landmark_type.empty()) {
2468 result->vt = VT_BSTR;
2469 result->bstrVal = ::SysAllocString(
2470 fml::Utf16ToWideString(localized_landmark_type).c_str());
2471 }
2472 break;
2473 }
2474
2475 case UIA_ExpandCollapseExpandCollapseStatePropertyId:
2476 result->vt = VT_I4;
2477 result->intVal = static_cast<int>(ComputeExpandCollapseState());
2478 break;
2479
2480 case UIA_ToggleToggleStatePropertyId: {
2481 ToggleState state;
2482 get_ToggleState(&state);
2483 result->vt = VT_I4;
2484 result->lVal = state;
2485 break;
2486 }
2487
2488 case UIA_ValueValuePropertyId:
2489 result->vt = VT_BSTR;
2490 result->bstrVal = GetValueAttributeAsBstr(this);
2491 break;
2492
2493 // Not currently implemented.
2494 case UIA_AnnotationObjectsPropertyId:
2495 case UIA_AnnotationTypesPropertyId:
2496 case UIA_CenterPointPropertyId:
2497 case UIA_FillColorPropertyId:
2498 case UIA_FillTypePropertyId:
2499 case UIA_HeadingLevelPropertyId:
2500 case UIA_ItemTypePropertyId:
2501 case UIA_OutlineColorPropertyId:
2502 case UIA_OutlineThicknessPropertyId:
2503 case UIA_RotationPropertyId:
2504 case UIA_SizePropertyId:
2505 case UIA_VisualEffectsPropertyId:
2506 break;
2507
2508 // Provided by UIA Core; we should not implement.
2509 case UIA_BoundingRectanglePropertyId:
2510 case UIA_NativeWindowHandlePropertyId:
2511 case UIA_ProcessIdPropertyId:
2512 case UIA_ProviderDescriptionPropertyId:
2513 case UIA_RuntimeIdPropertyId:
2514 break;
2515 } // End of default UIA property ids.
2516
2517 // Custom UIA Property Ids.
2518 if (property_id ==
2519 UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId()) {
2520 // We want to negate the unique id for it to be consistent across different
2521 // Windows accessiblity APIs. The negative unique id convention originated
2522 // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
2523 // self, and a positive child id means child #n. In order to fire an event
2524 // for an arbitrary descendant of the window, Firefox started the practice
2525 // of using a negative unique id. We follow the same negative unique id
2526 // convention here and when we fire events via ::NotifyWinEvent().
2527 result->vt = VT_BSTR;
2528 result->bstrVal = ::SysAllocString(
2529 fml::Utf16ToWideString(base::NumberToString16(-GetUniqueId())).c_str());
2530 }
2531
2532 return S_OK;
2533}
2534
2535IFACEMETHODIMP AXPlatformNodeWin::get_ProviderOptions(ProviderOptions* ret) {
2537
2538 *ret = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading |
2539 ProviderOptions_RefuseNonClientSupport |
2540 ProviderOptions_HasNativeIAccessible;
2541 return S_OK;
2542}
2543
2544IFACEMETHODIMP AXPlatformNodeWin::get_HostRawElementProvider(
2545 IRawElementProviderSimple** provider) {
2546 UIA_VALIDATE_CALL_1_ARG(provider);
2547
2548 *provider = nullptr;
2549 return S_OK;
2550}
2551
2552//
2553// IRawElementProviderSimple2 implementation.
2554//
2555
2556IFACEMETHODIMP AXPlatformNodeWin::ShowContextMenu() {
2558
2559 AXActionData action_data;
2560 action_data.action = ax::mojom::Action::kShowContextMenu;
2561 delegate_->AccessibilityPerformAction(action_data);
2562 return S_OK;
2563}
2564
2565//
2566// IServiceProvider implementation.
2567//
2568
2569IFACEMETHODIMP AXPlatformNodeWin::QueryService(REFGUID guidService,
2570 REFIID riid,
2571 void** object) {
2573
2574 if (guidService == IID_IAccessible) {
2575 return QueryInterface(riid, object);
2576 }
2577
2578 // TODO(suproteem): Include IAccessibleEx in the list, potentially checking
2579 // for version.
2580
2581 *object = nullptr;
2582 return E_FAIL;
2583}
2584
2585//
2586// Methods used by the ATL COM map.
2587//
2588
2589// static
2590STDMETHODIMP AXPlatformNodeWin::InternalQueryInterface(
2591 void* this_ptr,
2592 const _ATL_INTMAP_ENTRY* entries,
2593 REFIID riid,
2594 void** object) {
2595 if (!object)
2596 return E_INVALIDARG;
2597 *object = nullptr;
2598 AXPlatformNodeWin* accessible =
2599 reinterpret_cast<AXPlatformNodeWin*>(this_ptr);
2600 BASE_DCHECK(accessible);
2601
2602 return CComObjectRootBase::InternalQueryInterface(this_ptr, entries, riid,
2603 object);
2604}
2605
2606HRESULT AXPlatformNodeWin::GetTextAttributeValue(
2607 TEXTATTRIBUTEID attribute_id,
2608 const std::optional<int>& start_offset,
2609 const std::optional<int>& end_offset,
2611 BASE_DCHECK(!start_offset || start_offset.value() >= 0);
2612 BASE_DCHECK(!end_offset || end_offset.value() >= 0);
2613
2614 switch (attribute_id) {
2615 case UIA_AnnotationTypesAttributeId:
2616 return GetAnnotationTypesAttribute(start_offset, end_offset, result);
2617 case UIA_BackgroundColorAttributeId:
2618 result->Insert<VT_I4>(
2619 GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute::kBackgroundColor));
2620 break;
2621 case UIA_BulletStyleAttributeId:
2622 result->Insert<VT_I4>(ComputeUIABulletStyle());
2623 break;
2624 case UIA_CultureAttributeId: {
2625 std::optional<LCID> lcid = GetCultureAttributeAsLCID();
2626 if (!lcid)
2627 return E_FAIL;
2628 result->Insert<VT_I4>(lcid.value());
2629 break;
2630 }
2631 case UIA_FontNameAttributeId:
2632 result->Insert<VT_BSTR>(GetFontNameAttributeAsBSTR());
2633 break;
2634 case UIA_FontSizeAttributeId: {
2635 std::optional<float> font_size_in_points = GetFontSizeInPoints();
2636 if (font_size_in_points) {
2637 result->Insert<VT_R8>(*font_size_in_points);
2638 }
2639 break;
2640 }
2641 case UIA_FontWeightAttributeId:
2642 result->Insert<VT_I4>(
2643 GetFloatAttribute(ax::mojom::FloatAttribute::kFontWeight));
2644 break;
2645 case UIA_ForegroundColorAttributeId:
2646 result->Insert<VT_I4>(
2647 GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute::kColor));
2648 break;
2649 case UIA_IsHiddenAttributeId:
2650 result->Insert<VT_BOOL>(IsInvisibleOrIgnored());
2651 break;
2652 case UIA_IsItalicAttributeId:
2653 result->Insert<VT_BOOL>(
2654 GetData().HasTextStyle(ax::mojom::TextStyle::kItalic));
2655 break;
2656 case UIA_IsReadOnlyAttributeId:
2657 // Placeholder text should return the enclosing element's read-only value.
2658 if (IsPlaceholderText()) {
2659 AXPlatformNodeWin* parent_platform_node =
2660 static_cast<AXPlatformNodeWin*>(
2661 FromNativeViewAccessible(GetParent()));
2662 return parent_platform_node->GetTextAttributeValue(
2663 attribute_id, start_offset, end_offset, result);
2664 }
2665 result->Insert<VT_BOOL>(GetData().IsReadOnlyOrDisabled());
2666 break;
2667 case UIA_IsSubscriptAttributeId:
2668 result->Insert<VT_BOOL>(GetData().GetTextPosition() ==
2670 break;
2671 case UIA_IsSuperscriptAttributeId:
2672 result->Insert<VT_BOOL>(GetData().GetTextPosition() ==
2674 break;
2675 case UIA_OverlineStyleAttributeId:
2676 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2678 break;
2679 case UIA_StrikethroughStyleAttributeId:
2680 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2682 break;
2683 case UIA_StyleNameAttributeId:
2684 result->Insert<VT_BSTR>(GetStyleNameAttributeAsBSTR());
2685 break;
2686 case UIA_StyleIdAttributeId:
2687 result->Insert<VT_I4>(ComputeUIAStyleId());
2688 break;
2689 case UIA_HorizontalTextAlignmentAttributeId: {
2690 std::optional<HorizontalTextAlignment> horizontal_text_alignment =
2691 AXTextAlignToUIAHorizontalTextAlignment(GetData().GetTextAlign());
2692 if (horizontal_text_alignment)
2693 result->Insert<VT_I4>(*horizontal_text_alignment);
2694 break;
2695 }
2696 case UIA_UnderlineStyleAttributeId:
2697 result->Insert<VT_I4>(GetUIATextDecorationStyle(
2699 break;
2700 case UIA_TextFlowDirectionsAttributeId:
2701 result->Insert<VT_I4>(
2702 TextDirectionToFlowDirections(GetData().GetTextDirection()));
2703 break;
2704 default: {
2705 Microsoft::WRL::ComPtr<IUnknown> not_supported_value;
2706 HRESULT hr = ::UiaGetReservedNotSupportedValue(&not_supported_value);
2707 if (SUCCEEDED(hr))
2708 result->Insert<VT_UNKNOWN>(not_supported_value.Get());
2709 return hr;
2710 } break;
2711 }
2712
2713 return S_OK;
2714}
2715
2716HRESULT AXPlatformNodeWin::GetAnnotationTypesAttribute(
2717 const std::optional<int>& start_offset,
2718 const std::optional<int>& end_offset,
2720 base::win::VariantVector variant_vector;
2721
2722 MarkerTypeRangeResult grammar_result = MarkerTypeRangeResult::kNone;
2723 MarkerTypeRangeResult spelling_result = MarkerTypeRangeResult::kNone;
2724
2725 if (IsText() || IsPlainTextField()) {
2726 grammar_result = GetMarkerTypeFromRange(start_offset, end_offset,
2728 spelling_result = GetMarkerTypeFromRange(start_offset, end_offset,
2730 }
2731
2732 if (grammar_result == MarkerTypeRangeResult::kMixed ||
2733 spelling_result == MarkerTypeRangeResult::kMixed) {
2734 Microsoft::WRL::ComPtr<IUnknown> mixed_attribute_value;
2735 HRESULT hr = ::UiaGetReservedMixedAttributeValue(&mixed_attribute_value);
2736 if (SUCCEEDED(hr))
2737 result->Insert<VT_UNKNOWN>(mixed_attribute_value.Get());
2738 return hr;
2739 }
2740
2741 if (spelling_result == MarkerTypeRangeResult::kMatch)
2742 result->Insert<VT_I4>(AnnotationType_SpellingError);
2743 if (grammar_result == MarkerTypeRangeResult::kMatch)
2744 result->Insert<VT_I4>(AnnotationType_GrammarError);
2745
2746 return S_OK;
2747}
2748
2749std::optional<LCID> AXPlatformNodeWin::GetCultureAttributeAsLCID() const {
2750 const std::u16string language =
2751 GetInheritedString16Attribute(ax::mojom::StringAttribute::kLanguage);
2752 const LCID lcid =
2753 LocaleNameToLCID((wchar_t*)language.c_str(), LOCALE_ALLOW_NEUTRAL_NAMES);
2754 if (!lcid)
2755 return std::nullopt;
2756
2757 return lcid;
2758}
2759
2760COLORREF AXPlatformNodeWin::GetIntAttributeAsCOLORREF(
2761 ax::mojom::IntAttribute attribute) const {
2762 uint32_t color = GetIntAttribute(attribute);
2763 // From skia_utils_win.cc
2764 return (_byteswap_ulong(color) >> 8);
2765}
2766
2767BulletStyle AXPlatformNodeWin::ComputeUIABulletStyle() const {
2768 // UIA expects the list style of a non-list-item to be none however the
2769 // default list style cascaded is disc not none. Therefore we must ensure that
2770 // this node is contained within a list-item to distinguish non-list-items and
2771 // disc styled list items.
2772 const AXPlatformNodeBase* current_node = this;
2773 while (current_node &&
2774 current_node->GetData().role != ax::mojom::Role::kListItem) {
2775 current_node = FromNativeViewAccessible(current_node->GetParent());
2776 }
2777
2778 const ax::mojom::ListStyle list_style =
2779 current_node ? current_node->GetData().GetListStyle()
2781
2782 switch (list_style) {
2784 return BulletStyle::BulletStyle_None;
2786 return BulletStyle::BulletStyle_HollowRoundBullet;
2788 return BulletStyle::BulletStyle_FilledRoundBullet;
2790 return BulletStyle::BulletStyle_Other;
2793 return BulletStyle::BulletStyle_None;
2795 return BulletStyle::BulletStyle_FilledSquareBullet;
2796 }
2797}
2798
2799LONG AXPlatformNodeWin::ComputeUIAStyleId() const {
2800 const AXPlatformNodeBase* current_node = this;
2801 do {
2802 switch (current_node->GetData().role) {
2804 return AXHierarchicalLevelToUIAStyleId(current_node->GetIntAttribute(
2807 return AXListStyleToUIAStyleId(current_node->GetData().GetListStyle());
2809 return StyleId_Custom;
2811 return StyleId_Quote;
2812 default:
2813 break;
2814 }
2815 current_node = FromNativeViewAccessible(current_node->GetParent());
2816 } while (current_node);
2817
2818 return StyleId_Normal;
2819}
2820
2821// static
2822std::optional<HorizontalTextAlignment>
2823AXPlatformNodeWin::AXTextAlignToUIAHorizontalTextAlignment(
2824 ax::mojom::TextAlign text_align) {
2825 switch (text_align) {
2827 return std::nullopt;
2829 return HorizontalTextAlignment_Left;
2831 return HorizontalTextAlignment_Right;
2833 return HorizontalTextAlignment_Centered;
2835 return HorizontalTextAlignment_Justified;
2836 }
2837}
2838
2839// static
2840LONG AXPlatformNodeWin::AXHierarchicalLevelToUIAStyleId(
2841 int32_t hierarchical_level) {
2842 switch (hierarchical_level) {
2843 case 0:
2844 return StyleId_Normal;
2845 case 1:
2846 return StyleId_Heading1;
2847 case 2:
2848 return StyleId_Heading2;
2849 case 3:
2850 return StyleId_Heading3;
2851 case 4:
2852 return StyleId_Heading4;
2853 case 5:
2854 return StyleId_Heading5;
2855 case 6:
2856 return StyleId_Heading6;
2857 case 7:
2858 return StyleId_Heading7;
2859 case 8:
2860 return StyleId_Heading8;
2861 case 9:
2862 return StyleId_Heading9;
2863 default:
2864 return StyleId_Custom;
2865 }
2866}
2867
2868// static
2869LONG AXPlatformNodeWin::AXListStyleToUIAStyleId(
2870 ax::mojom::ListStyle list_style) {
2871 switch (list_style) {
2873 return StyleId_Normal;
2878 return StyleId_BulletedList;
2881 return StyleId_NumberedList;
2882 }
2883}
2884
2885// static
2886FlowDirections AXPlatformNodeWin::TextDirectionToFlowDirections(
2887 ax::mojom::WritingDirection text_direction) {
2888 switch (text_direction) {
2890 return FlowDirections::FlowDirections_Default;
2892 return FlowDirections::FlowDirections_Default;
2894 return FlowDirections::FlowDirections_RightToLeft;
2896 return FlowDirections::FlowDirections_Vertical;
2898 return FlowDirections::FlowDirections_BottomToTop;
2899 }
2900}
2901
2902// static
2903void AXPlatformNodeWin::AggregateRangesForMarkerType(
2904 AXPlatformNodeBase* node,
2905 ax::mojom::MarkerType marker_type,
2906 int offset_ranges_amount,
2907 std::vector<std::pair<int, int>>* ranges) {
2908 BASE_DCHECK(node->IsText());
2909 const std::vector<int32_t>& marker_types =
2910 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
2911 const std::vector<int>& marker_starts =
2912 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
2913 const std::vector<int>& marker_ends =
2914 node->GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
2915
2916 for (size_t i = 0; i < marker_types.size(); ++i) {
2917 if (static_cast<ax::mojom::MarkerType>(marker_types[i]) != marker_type)
2918 continue;
2919
2920 const int marker_start = marker_starts[i] + offset_ranges_amount;
2921 const int marker_end = marker_ends[i] + offset_ranges_amount;
2922 ranges->emplace_back(std::make_pair(marker_start, marker_end));
2923 }
2924}
2925
2926AXPlatformNodeWin::MarkerTypeRangeResult
2927AXPlatformNodeWin::GetMarkerTypeFromRange(
2928 const std::optional<int>& start_offset,
2929 const std::optional<int>& end_offset,
2930 ax::mojom::MarkerType marker_type) {
2931 BASE_DCHECK(IsText() || IsPlainTextField());
2932 std::vector<std::pair<int, int>> relevant_ranges;
2933
2934 if (IsText()) {
2935 AggregateRangesForMarkerType(this, marker_type, /*offset_ranges_amount=*/0,
2936 &relevant_ranges);
2937 } else if (IsPlainTextField()) {
2938 int offset_ranges_amount = 0;
2939 for (AXPlatformNodeBase* static_text = GetFirstTextOnlyDescendant();
2940 static_text; static_text = static_text->GetNextSibling()) {
2941 const int child_offset_ranges_amount = offset_ranges_amount;
2942 if (start_offset || end_offset) {
2943 // Break if the current node is after the desired |end_offset|.
2944 if (end_offset && child_offset_ranges_amount > end_offset.value())
2945 break;
2946
2947 // Skip over nodes preceding the desired |start_offset|.
2948 offset_ranges_amount += static_text->GetHypertext().length();
2949 if (start_offset && offset_ranges_amount < start_offset.value())
2950 continue;
2951 }
2952
2953 AggregateRangesForMarkerType(static_text, marker_type,
2954 child_offset_ranges_amount,
2955 &relevant_ranges);
2956 }
2957 }
2958
2959 // Sort the ranges by their start offset.
2960 const auto sort_ranges_by_start_offset = [](const std::pair<int, int>& a,
2961 const std::pair<int, int>& b) {
2962 return a.first < b.first;
2963 };
2964 std::sort(relevant_ranges.begin(), relevant_ranges.end(),
2965 sort_ranges_by_start_offset);
2966
2967 // Validate that the desired range has a contiguous MarkerType.
2968 std::optional<std::pair<int, int>> contiguous_range;
2969 for (const std::pair<int, int>& range : relevant_ranges) {
2970 if (end_offset && range.first > end_offset.value())
2971 break;
2972 if (start_offset && range.second < start_offset.value())
2973 continue;
2974
2975 if (!contiguous_range) {
2976 contiguous_range = range;
2977 continue;
2978 }
2979
2980 // If there is a gap, then the range must be mixed.
2981 if ((range.first - contiguous_range->second) > 1)
2982 return MarkerTypeRangeResult::kMixed;
2983
2984 // Expand the range if possible.
2985 contiguous_range->second = std::max(contiguous_range->second, range.second);
2986 }
2987
2988 // The desired range does not overlap with |marker_type|.
2989 if (!contiguous_range)
2990 return MarkerTypeRangeResult::kNone;
2991
2992 // If there is a partial overlap, then the desired range must be mixed.
2993 // 1. The |start_offset| is not specified, treat it as offset 0.
2994 if (!start_offset && contiguous_range->first > 0)
2995 return MarkerTypeRangeResult::kMixed;
2996 // 2. The |end_offset| is not specified, treat it as max text offset.
2997 if (!end_offset && contiguous_range->second < GetHypertext().length())
2998 return MarkerTypeRangeResult::kMixed;
2999 // 3. The |start_offset| is specified, but is before the first matching range.
3000 if (start_offset && start_offset.value() < contiguous_range->first)
3001 return MarkerTypeRangeResult::kMixed;
3002 // 4. The |end_offset| is specified, but is after the last matching range.
3003 if (end_offset && end_offset.value() > contiguous_range->second)
3004 return MarkerTypeRangeResult::kMixed;
3005
3006 // The desired range is a complete match for |marker_type|.
3007 return MarkerTypeRangeResult::kMatch;
3008}
3009
3010// IRawElementProviderSimple support methods.
3011
3012bool AXPlatformNodeWin::IsPatternProviderSupported(PATTERNID pattern_id) {
3013 return GetPatternProviderFactoryMethod(pattern_id);
3014}
3015
3016//
3017// Private member functions.
3018//
3019int AXPlatformNodeWin::MSAARole() {
3020 // If this is a web area for a presentational iframe, give it a role of
3021 // something other than DOCUMENT so that the fact that it's a separate doc
3022 // is not exposed to AT.
3023 if (IsWebAreaForPresentationalIframe())
3024 return ROLE_SYSTEM_GROUPING;
3025
3026 switch (GetData().role) {
3028 return ROLE_SYSTEM_ALERT;
3029
3031 return ROLE_SYSTEM_DIALOG;
3032
3034 return ROLE_SYSTEM_LINK;
3035
3038 return ROLE_SYSTEM_GROUPING;
3039
3041 return ROLE_SYSTEM_APPLICATION;
3042
3044 return ROLE_SYSTEM_DOCUMENT;
3045
3047 return ROLE_SYSTEM_GROUPING;
3048
3051 return ROLE_SYSTEM_GROUPING;
3052
3054 return ROLE_SYSTEM_GROUPING;
3055
3057 return ROLE_SYSTEM_PUSHBUTTON;
3058
3060 return ROLE_SYSTEM_GRAPHIC;
3061
3063 return ROLE_SYSTEM_TEXT;
3064
3066 return ROLE_SYSTEM_CARET;
3067
3069 return ROLE_SYSTEM_CELL;
3070
3072 return ROLE_SYSTEM_CHECKBUTTON;
3073
3075 return ROLE_SYSTEM_PANE;
3076
3078 return ROLE_SYSTEM_TEXT;
3079
3081 return ROLE_SYSTEM_COLUMN;
3082
3084 return ROLE_SYSTEM_COLUMNHEADER;
3085
3088 return ROLE_SYSTEM_COMBOBOX;
3089
3091 return ROLE_SYSTEM_GROUPING;
3092
3095 return ROLE_SYSTEM_GROUPING;
3096
3099 return ROLE_SYSTEM_GROUPING;
3100
3103 return ROLE_SYSTEM_DROPLIST;
3104
3106 return ROLE_SYSTEM_GROUPING;
3107
3109 return ROLE_SYSTEM_TEXT;
3110
3112 return ROLE_SYSTEM_LIST;
3113
3115 return ROLE_SYSTEM_LISTITEM;
3116
3118 return ROLE_SYSTEM_PANE;
3119
3121 return ROLE_SYSTEM_GROUPING;
3122
3124 return ROLE_SYSTEM_DIALOG;
3125
3127 return ROLE_SYSTEM_PUSHBUTTON;
3128
3130 return ROLE_SYSTEM_LIST;
3131
3133 return ROLE_SYSTEM_GRAPHIC;
3134
3139 return ROLE_SYSTEM_LINK;
3140
3144 return ROLE_SYSTEM_LISTITEM;
3145
3147 return ROLE_SYSTEM_SEPARATOR;
3148
3179 return ROLE_SYSTEM_GROUPING;
3180
3184 return ROLE_SYSTEM_DOCUMENT;
3185
3187 // Even though the HTML-AAM has ROLE_SYSTEM_CLIENT for <embed>, we are
3188 // forced to use ROLE_SYSTEM_GROUPING when the <embed> has children in the
3189 // accessibility tree.
3190 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
3191 //
3192 // Screen readers Jaws and NVDA do not "see" any of the <embed>'s contents
3193 // if they are represented as its children in the accessibility tree. For
3194 // example, one of the places that would be negatively impacted is the
3195 // reading of PDFs.
3196 if (GetDelegate()->GetChildCount()) {
3197 return ROLE_SYSTEM_GROUPING;
3198 } else {
3199 return ROLE_SYSTEM_CLIENT;
3200 }
3201
3203 return ROLE_SYSTEM_GROUPING;
3204
3206 return ROLE_SYSTEM_GROUPING;
3207
3209 return ROLE_SYSTEM_GROUPING;
3210
3213 return ROLE_SYSTEM_GROUPING;
3214
3216 return ROLE_SYSTEM_GROUPING;
3217
3219 return ROLE_SYSTEM_GROUPING;
3220
3222 return ROLE_SYSTEM_DOCUMENT;
3223
3225 return ROLE_SYSTEM_PANE;
3226
3228 return ROLE_SYSTEM_GRAPHIC;
3229
3231 return ROLE_SYSTEM_TABLE;
3232
3234 return ROLE_SYSTEM_GROUPING;
3235
3237 return ROLE_SYSTEM_GROUPING;
3238
3240 return ROLE_SYSTEM_DOCUMENT;
3241
3243 return ROLE_SYSTEM_GROUPING;
3244
3247 return ROLE_SYSTEM_GRAPHIC;
3248
3250 return ROLE_SYSTEM_GROUPING;
3251
3253 return ROLE_SYSTEM_STATICTEXT;
3254
3257 return ROLE_SYSTEM_TEXT;
3258
3260 return ROLE_SYSTEM_TABLE;
3261
3263 return ROLE_SYSTEM_CELL;
3264
3266 return ROLE_SYSTEM_ROW;
3267
3269 return ROLE_SYSTEM_LINK;
3270
3272 return ROLE_SYSTEM_LIST;
3273
3275 return ROLE_SYSTEM_LIST;
3276
3278 return ROLE_SYSTEM_LISTITEM;
3279
3281 return ROLE_SYSTEM_LIST;
3282
3284 return ROLE_SYSTEM_LISTITEM;
3285
3287 if (!GetDelegate()->GetChildCount()) {
3288 // There's only a name attribute when using Legacy layout. With Legacy
3289 // layout, list markers have no child and are considered as StaticText.
3290 // We consider a list marker as a group in LayoutNG since it has
3291 // a text child node.
3292 return ROLE_SYSTEM_STATICTEXT;
3293 }
3294 return ROLE_SYSTEM_GROUPING;
3295
3297 return ROLE_SYSTEM_GROUPING;
3298
3300 return ROLE_SYSTEM_GROUPING;
3301
3303 return ROLE_SYSTEM_GROUPING;
3304
3306 return ROLE_SYSTEM_ANIMATION;
3307
3309 return ROLE_SYSTEM_EQUATION;
3310
3312 return ROLE_SYSTEM_MENUPOPUP;
3313
3315 return ROLE_SYSTEM_MENUBAR;
3316
3320 return ROLE_SYSTEM_MENUITEM;
3321
3323 return ROLE_SYSTEM_LIST;
3324
3326 return ROLE_SYSTEM_LISTITEM;
3327
3329 return ROLE_SYSTEM_PROGRESSBAR;
3330
3332 return ROLE_SYSTEM_GROUPING;
3333
3335 return ROLE_SYSTEM_GROUPING;
3336
3338 return ROLE_SYSTEM_GROUPING;
3339
3341 return ROLE_SYSTEM_PUSHBUTTON;
3342
3344 // See also case ax::mojom::Role::kEmbeddedObject.
3345 if (GetDelegate()->GetChildCount()) {
3346 return ROLE_SYSTEM_GROUPING;
3347 } else {
3348 return ROLE_SYSTEM_CLIENT;
3349 }
3350
3352 std::string html_tag =
3353 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
3354 if (html_tag == "select")
3355 return ROLE_SYSTEM_COMBOBOX;
3356 return ROLE_SYSTEM_BUTTONMENU;
3357 }
3358
3360 return ROLE_SYSTEM_PUSHBUTTON;
3361
3363 return ROLE_SYSTEM_TEXT;
3364
3366 return ROLE_SYSTEM_PROGRESSBAR;
3367
3369 return ROLE_SYSTEM_RADIOBUTTON;
3370
3372 return ROLE_SYSTEM_GROUPING;
3373
3375 return ROLE_SYSTEM_PANE;
3376
3377 case ax::mojom::Role::kRow: {
3378 // Role changes depending on whether row is inside a treegrid
3379 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
3380 return IsInTreeGrid() ? ROLE_SYSTEM_OUTLINEITEM : ROLE_SYSTEM_ROW;
3381 }
3382
3384 return ROLE_SYSTEM_GROUPING;
3385
3387 return ROLE_SYSTEM_ROWHEADER;
3388
3390 return ROLE_SYSTEM_TEXT;
3391
3393 if (GetNameAsString16().empty()) {
3394 // Do not use ARIA mapping for nameless <section>.
3395 return ROLE_SYSTEM_GROUPING;
3396 }
3397 // Use ARIA mapping.
3398 return ROLE_SYSTEM_PANE;
3399 }
3400
3402 return ROLE_SYSTEM_SCROLLBAR;
3403
3405 return ROLE_SYSTEM_PANE;
3406
3408 return ROLE_SYSTEM_GROUPING;
3409
3411 return ROLE_SYSTEM_SLIDER;
3412
3414 return ROLE_SYSTEM_SLIDER;
3415
3417 return ROLE_SYSTEM_SPINBUTTON;
3418
3420 return ROLE_SYSTEM_CHECKBUTTON;
3421
3424 return ROLE_SYSTEM_STATICTEXT;
3425
3427 return ROLE_SYSTEM_STATUSBAR;
3428
3430 return ROLE_SYSTEM_SEPARATOR;
3431
3433 return ROLE_SYSTEM_GRAPHIC;
3434
3436 return ROLE_SYSTEM_PAGETAB;
3437
3439 return ROLE_SYSTEM_TABLE;
3440
3442 return ROLE_SYSTEM_GROUPING;
3443
3445 return ROLE_SYSTEM_PAGETABLIST;
3446
3448 return ROLE_SYSTEM_PROPERTYPAGE;
3449
3451 return ROLE_SYSTEM_LISTITEM;
3452
3454 return ROLE_SYSTEM_TITLEBAR;
3455
3457 return ROLE_SYSTEM_CHECKBUTTON;
3458
3461 return ROLE_SYSTEM_TEXT;
3462
3464 return ROLE_SYSTEM_COMBOBOX;
3465
3471 return ROLE_SYSTEM_TEXT;
3472
3474 return ROLE_SYSTEM_CLOCK;
3475
3477 return ROLE_SYSTEM_TOOLBAR;
3478
3480 return ROLE_SYSTEM_TOOLTIP;
3481
3483 return ROLE_SYSTEM_OUTLINE;
3484
3486 return ROLE_SYSTEM_OUTLINE;
3487
3489 return ROLE_SYSTEM_OUTLINEITEM;
3490
3492 return ROLE_SYSTEM_WHITESPACE;
3493
3495 return ROLE_SYSTEM_GROUPING;
3496
3498 return ROLE_SYSTEM_CLIENT;
3499
3502 // Do not return ROLE_SYSTEM_WINDOW as that is a special MSAA system
3503 // role used to indicate a real native window object. It is
3504 // automatically created by oleacc.dll as a parent of the root of our
3505 // hierarchy, matching the HWND.
3506 return ROLE_SYSTEM_PANE;
3507
3514 return ROLE_SYSTEM_PANE;
3515 }
3516
3518 return ROLE_SYSTEM_GROUPING;
3519}
3520
3521bool AXPlatformNodeWin::IsWebAreaForPresentationalIframe() {
3522 if (GetData().role != ax::mojom::Role::kWebArea &&
3523 GetData().role != ax::mojom::Role::kRootWebArea) {
3524 return false;
3525 }
3526
3527 AXPlatformNodeBase* parent = FromNativeViewAccessible(GetParent());
3528 if (!parent)
3529 return false;
3530
3531 return parent->GetData().role == ax::mojom::Role::kIframePresentational;
3532}
3533
3534std::u16string AXPlatformNodeWin::UIAAriaRole() {
3535 // If this is a web area for a presentational iframe, give it a role of
3536 // something other than document so that the fact that it's a separate doc
3537 // is not exposed to AT.
3538 if (IsWebAreaForPresentationalIframe())
3539 return u"group";
3540
3541 switch (GetData().role) {
3543 return u"alert";
3544
3546 // Our MSAA implementation suggests the use of
3547 // "alert", not "alertdialog" because some
3548 // Windows screen readers are not compatible with
3549 // |ax::mojom::Role::kAlertDialog| yet.
3550 return u"alert";
3551
3553 return u"link";
3554
3557 return u"group";
3558
3560 return u"application";
3561
3563 return u"article";
3564
3566 return u"group";
3567
3570 return u"banner";
3571
3573 return u"group";
3574
3576 return u"button";
3577
3579 return u"img";
3580
3582 return u"description";
3583
3585 return u"region";
3586
3588 return u"gridcell";
3589
3591 return u"code";
3592
3594 return u"checkbox";
3595
3597 return u"region";
3598
3600 return u"textbox";
3601
3603 return u"region";
3604
3606 return u"columnheader";
3607
3610 return u"combobox";
3611
3613 return u"complementary";
3614
3617 return u"group";
3618
3621 return u"contentinfo";
3622
3625 return u"textbox";
3626
3628 return u"definition";
3629
3631 return u"description";
3632
3634 return u"list";
3635
3637 return u"listitem";
3638
3640 return u"document";
3641
3643 return u"group";
3644
3646 return u"dialog";
3647
3649 return u"button";
3650
3652 return u"directory";
3653
3655 return u"img";
3656
3661 return u"link";
3662
3666 return u"listitem";
3667
3669 return u"separator";
3670
3701 return u"group";
3702
3706 return u"document";
3707
3709 if (GetDelegate()->GetChildCount()) {
3710 return u"group";
3711 } else {
3712 return u"document";
3713 }
3714
3716 return u"emphasis";
3717
3719 return u"group";
3720
3722 return u"description";
3723
3725 return u"group";
3726
3729 return u"group";
3730
3732 return u"form";
3733
3735 return u"group";
3736
3738 return u"document";
3739
3741 return u"region";
3742
3744 return u"img";
3745
3747 return u"grid";
3748
3750 return u"group";
3751
3753 return u"heading";
3754
3756 return u"document";
3757
3759 return u"group";
3760
3762 return u"img";
3763
3765 return u"document";
3766
3768 // Internal role, not used on Windows.
3769 return u"group";
3770
3772 return u"group";
3773
3775 return u"textbox";
3776
3778 return u"group";
3779
3782 return u"description";
3783
3785 return u"grid";
3786
3788 return u"gridcell";
3789
3791 return u"row";
3792
3794 return u"link";
3795
3797 return u"list";
3798
3800 return u"listbox";
3801
3803 return u"option";
3804
3806 return u"listview";
3807
3809 return u"listitem";
3810
3812 if (!GetDelegate()->GetChildCount()) {
3813 // There's only a name attribute when using Legacy layout. With Legacy
3814 // layout, list markers have no child and are considered as StaticText.
3815 // We consider a list marker as a group in LayoutNG since it has
3816 // a text child node.
3817 return u"description";
3818 }
3819 return u"group";
3820
3822 return u"log";
3823
3825 return u"main";
3826
3828 return u"description";
3829
3831 return u"marquee";
3832
3834 return u"group";
3835
3837 return u"menu";
3838
3840 return u"menubar";
3841
3843 return u"menuitem";
3844
3846 return u"menuitemcheckbox";
3847
3849 return u"menuitemradio";
3850
3852 return u"list";
3853
3855 return u"listitem";
3856
3858 return u"progressbar";
3859
3861 return u"navigation";
3862
3864 return u"note";
3865
3867 return u"group";
3868
3870 return u"button";
3871
3873 if (GetDelegate()->GetChildCount()) {
3874 return u"group";
3875 } else {
3876 return u"document";
3877 }
3878
3880 std::string html_tag =
3881 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
3882 if (html_tag == "select")
3883 return u"combobox";
3884 return u"button";
3885 }
3886
3888 return u"button";
3889
3891 return u"region";
3892
3894 return u"progressbar";
3895
3897 return u"radio";
3898
3900 return u"radiogroup";
3901
3903 return u"region";
3904
3905 case ax::mojom::Role::kRow: {
3906 // Role changes depending on whether row is inside a treegrid
3907 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
3908 return IsInTreeGrid() ? u"treeitem" : u"row";
3909 }
3910
3912 return u"rowgroup";
3913
3915 return u"rowheader";
3916
3918 return u"region";
3919
3921 if (GetNameAsString16().empty()) {
3922 // Do not use ARIA mapping for nameless <section>.
3923 return u"group";
3924 }
3925 // Use ARIA mapping.
3926 return u"region";
3927 }
3928
3930 return u"scrollbar";
3931
3933 return u"region";
3934
3936 return u"search";
3937
3939 return u"slider";
3940
3942 return u"slider";
3943
3945 return u"spinbutton";
3946
3948 return u"strong";
3949
3951 return u"switch";
3952
3955 return u"description";
3956
3958 return u"status";
3959
3961 return u"separator";
3962
3964 return u"img";
3965
3967 return u"tab";
3968
3970 return u"grid";
3971
3973 return u"group";
3974
3976 return u"tablist";
3977
3979 return u"tabpanel";
3980
3982 return u"listitem";
3983
3985 return u"document";
3986
3988 return u"button";
3989
3991 return u"textbox";
3992
3994 return u"searchbox";
3995
3997 return u"combobox";
3998
4000 return u"description";
4001
4003 return u"time";
4004
4006 return u"timer";
4007
4009 return u"toolbar";
4010
4012 return u"tooltip";
4013
4015 return u"tree";
4016
4018 return u"treegrid";
4019
4021 return u"treeitem";
4022
4024 return u"separator";
4025
4027 return u"group";
4028
4030 return u"document";
4031
4038 return u"region";
4039 }
4040
4042 return u"document";
4043}
4044
4045std::u16string AXPlatformNodeWin::ComputeUIAProperties() {
4046 std::vector<std::u16string> properties;
4047 const AXNodeData& data = GetData();
4048
4049 BoolAttributeToUIAAriaProperty(
4050 properties, ax::mojom::BoolAttribute::kLiveAtomic, "atomic");
4051 BoolAttributeToUIAAriaProperty(properties, ax::mojom::BoolAttribute::kBusy,
4052 "busy");
4053
4054 switch (data.GetCheckedState()) {
4056 break;
4059 properties.emplace_back(u"pressed=false");
4060 } else if (data.role == ax::mojom::Role::kSwitch) {
4061 // ARIA switches are exposed to Windows accessibility as toggle
4062 // buttons. For maximum compatibility with ATs, we expose both the
4063 // pressed and checked states.
4064 properties.emplace_back(u"pressed=false");
4065 properties.emplace_back(u"checked=false");
4066 } else {
4067 properties.emplace_back(u"checked=false");
4068 }
4069 break;
4072 properties.emplace_back(u"pressed=true");
4073 } else if (data.role == ax::mojom::Role::kSwitch) {
4074 // ARIA switches are exposed to Windows accessibility as toggle
4075 // buttons. For maximum compatibility with ATs, we expose both the
4076 // pressed and checked states.
4077 properties.emplace_back(u"pressed=true");
4078 properties.emplace_back(u"checked=true");
4079 } else {
4080 properties.emplace_back(u"checked=true");
4081 }
4082 break;
4085 properties.emplace_back(u"pressed=mixed");
4086 } else if (data.role == ax::mojom::Role::kSwitch) {
4087 // This is disallowed both by the ARIA standard and by Blink.
4089 } else {
4090 properties.emplace_back(u"checked=mixed");
4091 }
4092 break;
4093 }
4094
4095 const auto restriction = static_cast<ax::mojom::Restriction>(
4096 GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
4097 if (restriction == ax::mojom::Restriction::kDisabled) {
4098 properties.push_back(u"disabled=true");
4099 } else {
4100 // The readonly property is complex on Windows. We set "readonly=true"
4101 // on *some* document structure roles such as paragraph, heading or list
4102 // even if the node data isn't marked as read only, as long as the
4103 // node is not editable.
4104 if (GetData().IsReadOnlyOrDisabled())
4105 properties.push_back(u"readonly=true");
4106 }
4107
4108 // aria-dropeffect is deprecated in WAI-ARIA 1.1.
4109 if (data.HasIntAttribute(ax::mojom::IntAttribute::kDropeffect)) {
4110 properties.push_back(u"dropeffect=" +
4111 base::UTF8ToUTF16(data.DropeffectBitfieldToString()));
4112 }
4113 StateToUIAAriaProperty(properties, ax::mojom::State::kExpanded, "expanded");
4114 BoolAttributeToUIAAriaProperty(properties, ax::mojom::BoolAttribute::kGrabbed,
4115 "grabbed");
4116
4117 switch (static_cast<ax::mojom::HasPopup>(
4118 data.GetIntAttribute(ax::mojom::IntAttribute::kHasPopup))) {
4120 break;
4122 properties.push_back(u"haspopup=true");
4123 break;
4125 properties.push_back(u"haspopup=menu");
4126 break;
4128 properties.push_back(u"haspopup=listbox");
4129 break;
4131 properties.push_back(u"haspopup=tree");
4132 break;
4134 properties.push_back(u"haspopup=grid");
4135 break;
4137 properties.push_back(u"haspopup=dialog");
4138 break;
4139 }
4140
4141 if (IsInvisibleOrIgnored())
4142 properties.push_back(u"hidden=true");
4143
4144 if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState) &&
4145 GetIntAttribute(ax::mojom::IntAttribute::kInvalidState) !=
4146 static_cast<int32_t>(ax::mojom::InvalidState::kFalse)) {
4147 properties.push_back(u"invalid=true");
4148 }
4149
4150 IntAttributeToUIAAriaProperty(
4151 properties, ax::mojom::IntAttribute::kHierarchicalLevel, "level");
4152 StringAttributeToUIAAriaProperty(
4153 properties, ax::mojom::StringAttribute::kLiveStatus, "live");
4154 StateToUIAAriaProperty(properties, ax::mojom::State::kMultiline, "multiline");
4155 StateToUIAAriaProperty(properties, ax::mojom::State::kMultiselectable,
4156 "multiselectable");
4157 IntAttributeToUIAAriaProperty(properties, ax::mojom::IntAttribute::kPosInSet,
4158 "posinset");
4159 StringAttributeToUIAAriaProperty(
4160 properties, ax::mojom::StringAttribute::kLiveRelevant, "relevant");
4161 StateToUIAAriaProperty(properties, ax::mojom::State::kRequired, "required");
4162 BoolAttributeToUIAAriaProperty(
4163 properties, ax::mojom::BoolAttribute::kSelected, "selected");
4164 IntAttributeToUIAAriaProperty(properties, ax::mojom::IntAttribute::kSetSize,
4165 "setsize");
4166
4167 int32_t sort_direction;
4168 if (IsTableHeader(data.role) &&
4170 &sort_direction)) {
4171 switch (static_cast<ax::mojom::SortDirection>(sort_direction)) {
4173 break;
4175 properties.push_back(u"sort=none");
4176 break;
4178 properties.push_back(u"sort=ascending");
4179 break;
4181 properties.push_back(u"sort=descending");
4182 break;
4184 properties.push_back(u"sort=other");
4185 break;
4186 }
4187 }
4188
4189 if (data.IsRangeValueSupported()) {
4190 FloatAttributeToUIAAriaProperty(
4191 properties, ax::mojom::FloatAttribute::kMaxValueForRange, "valuemax");
4192 FloatAttributeToUIAAriaProperty(
4193 properties, ax::mojom::FloatAttribute::kMinValueForRange, "valuemin");
4194 StringAttributeToUIAAriaProperty(
4195 properties, ax::mojom::StringAttribute::kValue, "valuetext");
4196
4197 std::u16string value_now = GetRangeValueText();
4198 SanitizeStringAttributeForUIAAriaProperty(value_now, &value_now);
4199 if (!value_now.empty())
4200 properties.push_back(u"valuenow=" + value_now);
4201 }
4202
4203 std::u16string result = base::JoinString(properties, u";");
4204 return result;
4205}
4206
4207LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
4208 // If this is a web area for a presentational iframe, give it a role of
4209 // something other than document so that the fact that it's a separate doc
4210 // is not exposed to AT.
4211 if (IsWebAreaForPresentationalIframe())
4212 return UIA_GroupControlTypeId;
4213
4214 switch (GetData().role) {
4216 return UIA_TextControlTypeId;
4217
4219 // Our MSAA implementation suggests the use of
4220 // |UIA_TextControlTypeId|, not |UIA_PaneControlTypeId| because some
4221 // Windows screen readers are not compatible with
4222 // |ax::mojom::Role::kAlertDialog| yet.
4223 return UIA_TextControlTypeId;
4224
4226 return UIA_HyperlinkControlTypeId;
4227
4230 return ROLE_SYSTEM_GROUPING;
4231
4233 return UIA_PaneControlTypeId;
4234
4236 return UIA_GroupControlTypeId;
4237
4239 return UIA_GroupControlTypeId;
4240
4243 return UIA_GroupControlTypeId;
4244
4246 return UIA_GroupControlTypeId;
4247
4249 return UIA_ButtonControlTypeId;
4250
4252 return UIA_ImageControlTypeId;
4253
4255 return UIA_TextControlTypeId;
4256
4258 return UIA_PaneControlTypeId;
4259
4261 return UIA_DataItemControlTypeId;
4262
4264 return UIA_CheckBoxControlTypeId;
4265
4267 return UIA_PaneControlTypeId;
4268
4270 return UIA_TextControlTypeId;
4271
4273 return UIA_ButtonControlTypeId;
4274
4276 return UIA_PaneControlTypeId;
4277
4279 return UIA_DataItemControlTypeId;
4280
4283 return UIA_ComboBoxControlTypeId;
4284
4286 return UIA_GroupControlTypeId;
4287
4290 return UIA_GroupControlTypeId;
4291
4294 return UIA_GroupControlTypeId;
4295
4298 return UIA_EditControlTypeId;
4299
4301 return UIA_GroupControlTypeId;
4302
4304 return UIA_TextControlTypeId;
4305
4307 return UIA_ListControlTypeId;
4308
4310 return UIA_ListItemControlTypeId;
4311
4313 return UIA_DocumentControlTypeId;
4314
4316 return UIA_GroupControlTypeId;
4317
4319 return UIA_PaneControlTypeId;
4320
4322 return UIA_ButtonControlTypeId;
4323
4325 return UIA_ListControlTypeId;
4326
4328 return UIA_ImageControlTypeId;
4329
4334 return UIA_HyperlinkControlTypeId;
4335
4339 return UIA_ListItemControlTypeId;
4340
4342 return UIA_SeparatorControlTypeId;
4343
4374 return UIA_GroupControlTypeId;
4375
4379 return UIA_DocumentControlTypeId;
4380
4382 return UIA_PaneControlTypeId;
4383
4385 return UIA_TextControlTypeId;
4386
4388 return UIA_GroupControlTypeId;
4389
4391 return UIA_TextControlTypeId;
4392
4394 return UIA_GroupControlTypeId;
4395
4398 return UIA_GroupControlTypeId;
4399
4401 return UIA_GroupControlTypeId;
4402
4404 return UIA_GroupControlTypeId;
4405
4407 return UIA_DocumentControlTypeId;
4408
4410 return UIA_PaneControlTypeId;
4411
4413 return UIA_ImageControlTypeId;
4414
4416 return UIA_DataGridControlTypeId;
4417
4419 return UIA_GroupControlTypeId;
4420
4422 return UIA_TextControlTypeId;
4423
4425 return UIA_DocumentControlTypeId;
4426
4428 return UIA_GroupControlTypeId;
4429
4431 return UIA_ImageControlTypeId;
4432
4434 return UIA_DocumentControlTypeId;
4435
4437 return UIA_GroupControlTypeId;
4438
4440 return UIA_DocumentControlTypeId;
4441
4444 return UIA_TextControlTypeId;
4445
4447 return UIA_TableControlTypeId;
4448
4450 return UIA_DataItemControlTypeId;
4451
4453 return UIA_DataItemControlTypeId;
4454
4456 return UIA_HyperlinkControlTypeId;
4457
4459 return UIA_ListControlTypeId;
4460
4462 return UIA_ListControlTypeId;
4463
4465 return UIA_ListItemControlTypeId;
4466
4468 return UIA_DataGridControlTypeId;
4469
4471 return UIA_ListItemControlTypeId;
4472
4474 if (!GetDelegate()->GetChildCount()) {
4475 // There's only a name attribute when using Legacy layout. With Legacy
4476 // layout, list markers have no child and are considered as StaticText.
4477 // We consider a list marker as a group in LayoutNG since it has
4478 // a text child node.
4479 return UIA_TextControlTypeId;
4480 }
4481 return UIA_GroupControlTypeId;
4482
4484 return UIA_GroupControlTypeId;
4485
4487 return UIA_GroupControlTypeId;
4488
4490 return UIA_TextControlTypeId;
4491
4493 return UIA_TextControlTypeId;
4494
4496 return UIA_GroupControlTypeId;
4497
4499 return UIA_MenuControlTypeId;
4500
4502 return UIA_MenuBarControlTypeId;
4503
4505 return UIA_MenuItemControlTypeId;
4506
4508 return UIA_CheckBoxControlTypeId;
4509
4511 return UIA_RadioButtonControlTypeId;
4512
4514 return UIA_ListControlTypeId;
4515
4517 return UIA_ListItemControlTypeId;
4518
4520 return UIA_ProgressBarControlTypeId;
4521
4523 return UIA_GroupControlTypeId;
4524
4526 return UIA_GroupControlTypeId;
4527
4529 return UIA_GroupControlTypeId;
4530
4532 return UIA_CustomControlTypeId;
4533
4535 if (GetDelegate()->GetChildCount()) {
4536 return UIA_GroupControlTypeId;
4537 } else {
4538 return UIA_DocumentControlTypeId;
4539 }
4540
4542 std::string html_tag =
4543 GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
4544 if (html_tag == "select")
4545 return UIA_ComboBoxControlTypeId;
4546 return UIA_ButtonControlTypeId;
4547 }
4548
4550 return UIA_ButtonControlTypeId;
4551
4553 return UIA_PaneControlTypeId;
4554
4556 return UIA_ProgressBarControlTypeId;
4557
4559 return UIA_RadioButtonControlTypeId;
4560
4562 return UIA_GroupControlTypeId;
4563
4565 return UIA_GroupControlTypeId;
4566
4567 case ax::mojom::Role::kRow: {
4568 // Role changes depending on whether row is inside a treegrid
4569 // https://www.w3.org/TR/core-aam-1.1/#role-map-row
4570 return IsInTreeGrid() ? UIA_TreeItemControlTypeId
4571 : UIA_DataItemControlTypeId;
4572 }
4573
4575 return UIA_GroupControlTypeId;
4576
4578 return UIA_DataItemControlTypeId;
4579
4581 return UIA_PaneControlTypeId;
4582
4584 return UIA_GroupControlTypeId;
4585
4587 return UIA_ScrollBarControlTypeId;
4588
4590 return UIA_PaneControlTypeId;
4591
4593 return UIA_GroupControlTypeId;
4594
4596 return UIA_SliderControlTypeId;
4597
4599 return UIA_SliderControlTypeId;
4600
4602 return UIA_SpinnerControlTypeId;
4603
4605 return UIA_ButtonControlTypeId;
4606
4609 return UIA_TextControlTypeId;
4610
4612 return UIA_StatusBarControlTypeId;
4613
4615 return UIA_TextControlTypeId;
4616
4618 return UIA_SeparatorControlTypeId;
4619
4621 return UIA_ImageControlTypeId;
4622
4624 return UIA_TabItemControlTypeId;
4625
4627 return UIA_TableControlTypeId;
4628
4630 return UIA_GroupControlTypeId;
4631
4633 return UIA_TabControlTypeId;
4634
4636 return UIA_PaneControlTypeId;
4637
4639 return UIA_ListItemControlTypeId;
4640
4642 return UIA_DocumentControlTypeId;
4643
4645 return UIA_ButtonControlTypeId;
4646
4649 return UIA_EditControlTypeId;
4650
4652 return UIA_ComboBoxControlTypeId;
4653
4656 return UIA_TextControlTypeId;
4657
4659 return UIA_PaneControlTypeId;
4660
4662 return UIA_ToolBarControlTypeId;
4663
4665 return UIA_ToolTipControlTypeId;
4666
4668 return UIA_TreeControlTypeId;
4669
4671 return UIA_DataGridControlTypeId;
4672
4674 return UIA_TreeItemControlTypeId;
4675
4677 return UIA_SeparatorControlTypeId;
4678
4680 return UIA_GroupControlTypeId;
4681
4683 return UIA_DocumentControlTypeId;
4684
4693 return UIA_PaneControlTypeId;
4694 }
4695
4697 return UIA_DocumentControlTypeId;
4698}
4699
4700AXPlatformNodeWin* AXPlatformNodeWin::ComputeUIALabeledBy() {
4701 // Not all control types expect a value for this property.
4702 if (!CanHaveUIALabeledBy())
4703 return nullptr;
4704
4705 // This property only accepts static text elements to be returned. Find the
4706 // first static text used to label this node.
4707 for (int32_t id : GetData().GetIntListAttribute(
4709 auto* node_win =
4710 static_cast<AXPlatformNodeWin*>(GetDelegate()->GetFromNodeID(id));
4711 if (!node_win)
4712 continue;
4713
4714 // If this node is a static text, then simply return the node itself.
4715 if (IsValidUiaRelationTarget(node_win) &&
4716 node_win->GetData().role == ax::mojom::Role::kStaticText) {
4717 return node_win;
4718 }
4719
4720 // Otherwise, find the first static text node in its descendants.
4721 for (auto iter = node_win->GetDelegate()->ChildrenBegin();
4722 *iter != *node_win->GetDelegate()->ChildrenEnd(); ++(*iter)) {
4723 AXPlatformNodeWin* child = static_cast<AXPlatformNodeWin*>(
4725 iter->GetNativeViewAccessible()));
4726 if (IsValidUiaRelationTarget(child) &&
4727 child->GetData().role == ax::mojom::Role::kStaticText) {
4728 return child;
4729 }
4730 }
4731 }
4732
4733 return nullptr;
4734}
4735
4736bool AXPlatformNodeWin::CanHaveUIALabeledBy() {
4737 // Not all control types expect a value for this property. See
4738 // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-supportinguiautocontroltypes
4739 // for a complete list of control types. Each one of them has specific
4740 // expectations regarding the UIA_LabeledByPropertyId.
4741 switch (ComputeUIAControlType()) {
4742 case UIA_ButtonControlTypeId:
4743 case UIA_CheckBoxControlTypeId:
4744 case UIA_DataItemControlTypeId:
4745 case UIA_MenuControlTypeId:
4746 case UIA_MenuBarControlTypeId:
4747 case UIA_RadioButtonControlTypeId:
4748 case UIA_ScrollBarControlTypeId:
4749 case UIA_SeparatorControlTypeId:
4750 case UIA_StatusBarControlTypeId:
4751 case UIA_TabItemControlTypeId:
4752 case UIA_TextControlTypeId:
4753 case UIA_ToolBarControlTypeId:
4754 case UIA_ToolTipControlTypeId:
4755 case UIA_TreeItemControlTypeId:
4756 return false;
4757 default:
4758 return true;
4759 }
4760}
4761
4762bool AXPlatformNodeWin::IsNameExposed() const {
4763 const AXNodeData& data = GetData();
4764 switch (data.role) {
4766 return !GetDelegate()->GetChildCount();
4767 default:
4768 return true;
4769 }
4770}
4771
4772bool AXPlatformNodeWin::IsUIAControl() const {
4773 // UIA provides multiple "views": raw, content and control. We only want to
4774 // populate the content and control views with items that make sense to
4775 // traverse over.
4776
4777 if (GetDelegate()->IsWebContent()) {
4778 // Invisible or ignored elements should not show up in control view at all.
4779 if (IsInvisibleOrIgnored())
4780 return false;
4781
4782 if (IsText()) {
4783 // A text leaf can be a UIAControl, but text inside of a heading, link,
4784 // button, etc. where the role allows the name to be generated from the
4785 // content is not. We want to avoid reading out a button, moving to the
4786 // next item, and then reading out the button's text child, causing the
4787 // text to be effectively repeated.
4788 auto* parent = FromNativeViewAccessible(GetDelegate()->GetParent());
4789 while (parent) {
4790 const AXNodeData& data = parent->GetData();
4791 if (IsCellOrTableHeader(data.role))
4792 return false;
4793 switch (data.role) {
4815 return false;
4816 default:
4817 break;
4818 }
4819 parent = FromNativeViewAccessible(parent->GetParent());
4820 }
4821 } // end of text only case.
4822
4823 const AXNodeData& data = GetData();
4824 // https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-treeoverview#control-view
4825 // The control view also includes noninteractive UI items that contribute
4826 // to the logical structure of the UI.
4827 if (IsControl(data.role) || ComputeUIALandmarkType() ||
4828 IsTableLike(data.role) || IsList(data.role)) {
4829 return true;
4830 }
4831 if (IsImage(data.role)) {
4832 // If the author provides an explicitly empty alt text attribute then
4833 // the image is decorational and should not be considered as a control.
4834 if (data.role == ax::mojom::Role::kImage &&
4835 data.GetNameFrom() ==
4837 return false;
4838 }
4839 return true;
4840 }
4841 switch (data.role) {
4858 return true;
4859 default:
4860 break;
4861 }
4862 // Classify generic containers that are not clickable or focusable and have
4863 // no name, description, landmark type, and is not the root of editable
4864 // content as not controls.
4865 // Doing so helps Narrator find all the content of live regions.
4866 if (!data.GetBoolAttribute(ax::mojom::BoolAttribute::kHasAriaAttribute) &&
4867 !data.GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
4868 GetNameAsString16().empty() &&
4870 .empty() &&
4871 !data.HasState(ax::mojom::State::kFocusable) && !data.IsClickable()) {
4872 return false;
4873 }
4874
4875 return true;
4876 } // end of web-content only case.
4877
4878 const AXNodeData& data = GetData();
4879 return !((IsReadOnlySupported(data.role) && data.IsReadOnlyOrDisabled()) ||
4881 (data.IsIgnored() && !data.HasState(ax::mojom::State::kFocusable)));
4882}
4883
4884std::optional<LONG> AXPlatformNodeWin::ComputeUIALandmarkType() const {
4885 const AXNodeData& data = GetData();
4886 switch (data.role) {
4892 return UIA_CustomLandmarkTypeId;
4893
4895 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
4896 // https://w3c.github.io/core-aam/#mapping_role_table
4897 // While the HTML-AAM spec states that <form> without an accessible name
4898 // should have no corresponding role, removing the role breaks both
4899 // aria-setsize and aria-posinset.
4900 // The only other difference for UIA is that it should not be a landmark.
4901 // If the author provided an accessible name, or the role was explicit,
4902 // then allow the form landmark.
4903 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName) ||
4904 data.HasStringAttribute(ax::mojom::StringAttribute::kRole)) {
4905 return UIA_FormLandmarkTypeId;
4906 }
4907 return {};
4908
4910 return UIA_MainLandmarkTypeId;
4911
4913 return UIA_NavigationLandmarkTypeId;
4914
4916 return UIA_SearchLandmarkTypeId;
4917
4920 if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
4921 return UIA_CustomLandmarkTypeId;
4923
4924 default:
4925 return {};
4926 }
4927}
4928
4929bool AXPlatformNodeWin::IsInaccessibleDueToAncestor() const {
4930 AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
4932 while (parent) {
4933 if (parent->ShouldHideChildrenForUIA())
4934 return true;
4935 parent = static_cast<AXPlatformNodeWin*>(
4936 FromNativeViewAccessible(parent->GetParent()));
4937 }
4938 return false;
4939}
4940
4941bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
4942 if (IsPlainTextField())
4943 return true;
4944
4945 auto role = GetData().role;
4946 if (HasPresentationalChildren(role))
4947 return true;
4948
4949 switch (role) {
4950 // Other elements that are expected by UIA to hide their children without
4951 // having "Children Presentational: True".
4952 //
4953 // TODO(bebeaudr): We might be able to remove ax::mojom::Role::kLink once
4954 // http://crbug.com/1054514 is fixed. Links should not have to hide their
4955 // children.
4956 // TODO(virens): |kPdfActionableHighlight| needs to follow a fix similar to
4957 // links. At present Pdf highlghts have text nodes as children. But, we may
4958 // enable pdf highlights to have complex children like links based on user
4959 // feedback.
4961 // Links with a single text-only child should hide their subtree.
4962 if (GetChildCount() == 1) {
4963 AXPlatformNodeBase* only_child = GetFirstChild();
4964 return only_child && only_child->IsText();
4965 }
4966 return false;
4968 return true;
4969 default:
4970 return false;
4971 }
4972}
4973
4974std::u16string AXPlatformNodeWin::GetValue() const {
4975 std::u16string value = AXPlatformNodeBase::GetValue();
4976
4977 // If this doesn't have a value and is linked then set its value to the URL
4978 // attribute. This allows screen readers to read an empty link's
4979 // destination.
4980 // TODO(dougt): Look into ensuring that on click handlers correctly provide
4981 // a value here.
4982 if (value.empty() && (MSAAState() & STATE_SYSTEM_LINKED))
4983 value = GetString16Attribute(ax::mojom::StringAttribute::kUrl);
4984
4985 return value;
4986}
4987
4988bool AXPlatformNodeWin::IsPlatformCheckable() const {
4989 if (GetData().role == ax::mojom::Role::kToggleButton)
4990 return false;
4991
4993}
4994
4995bool AXPlatformNodeWin::ShouldNodeHaveFocusableState(
4996 const AXNodeData& data) const {
4997 switch (data.role) {
5001 return true;
5002
5004 AXPlatformNodeBase* parent = FromNativeViewAccessible(GetParent());
5005 return !parent || parent->GetData().role != ax::mojom::Role::kPortal;
5006 }
5007
5009 return false;
5010
5013 if (data.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected))
5014 return true;
5015 break;
5016
5017 default:
5018 break;
5019 }
5020
5021 return data.HasState(ax::mojom::State::kFocusable);
5022}
5023
5024int AXPlatformNodeWin::MSAAState() const {
5025 const AXNodeData& data = GetData();
5026 int msaa_state = 0;
5027
5028 // Map the ax::mojom::State to MSAA state. Note that some of the states are
5029 // not currently handled.
5030
5031 if (data.GetBoolAttribute(ax::mojom::BoolAttribute::kBusy))
5032 msaa_state |= STATE_SYSTEM_BUSY;
5033
5034 if (data.HasState(ax::mojom::State::kCollapsed))
5035 msaa_state |= STATE_SYSTEM_COLLAPSED;
5036
5037 if (data.HasState(ax::mojom::State::kDefault))
5038 msaa_state |= STATE_SYSTEM_DEFAULT;
5039
5040 // TODO(dougt) unhandled ux::ax::mojom::State::kEditable
5041
5042 if (data.HasState(ax::mojom::State::kExpanded))
5043 msaa_state |= STATE_SYSTEM_EXPANDED;
5044
5045 if (ShouldNodeHaveFocusableState(data))
5046 msaa_state |= STATE_SYSTEM_FOCUSABLE;
5047
5048 // Built-in autofill and autocomplete wil also set has popup.
5049 if (data.HasIntAttribute(ax::mojom::IntAttribute::kHasPopup))
5050 msaa_state |= STATE_SYSTEM_HASPOPUP;
5051
5052 // TODO(dougt) unhandled ux::ax::mojom::State::kHorizontal
5053
5054 if (data.HasState(ax::mojom::State::kHovered)) {
5055 // Expose whether or not the mouse is over an element, but suppress
5056 // this for tests because it can make the test results flaky depending
5057 // on the position of the mouse.
5058 if (GetDelegate()->ShouldIgnoreHoveredStateForTesting())
5059 msaa_state |= STATE_SYSTEM_HOTTRACKED;
5060 }
5061
5062 // If the role is IGNORED, we want these elements to be invisible so that
5063 // these nodes are hidden from the screen reader.
5064 if (IsInvisibleOrIgnored())
5065 msaa_state |= STATE_SYSTEM_INVISIBLE;
5066
5067 if (data.HasState(ax::mojom::State::kLinked))
5068 msaa_state |= STATE_SYSTEM_LINKED;
5069
5070 // TODO(dougt) unhandled ux::ax::mojom::State::kMultiline
5071
5073 msaa_state |= STATE_SYSTEM_EXTSELECTABLE;
5074 msaa_state |= STATE_SYSTEM_MULTISELECTABLE;
5075 }
5076
5077 if (GetDelegate()->IsOffscreen())
5078 msaa_state |= STATE_SYSTEM_OFFSCREEN;
5079
5080 if (data.HasState(ax::mojom::State::kProtected))
5081 msaa_state |= STATE_SYSTEM_PROTECTED;
5082
5083 // TODO(dougt) unhandled ux::ax::mojom::State::kRequired
5084 // TODO(dougt) unhandled ux::ax::mojom::State::kRichlyEditable
5085
5086 if (data.IsSelectable())
5087 msaa_state |= STATE_SYSTEM_SELECTABLE;
5088
5089 if (data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected))
5090 msaa_state |= STATE_SYSTEM_SELECTED;
5091
5092 // TODO(dougt) unhandled VERTICAL
5093
5094 if (data.HasState(ax::mojom::State::kVisited))
5095 msaa_state |= STATE_SYSTEM_TRAVERSED;
5096
5097 //
5098 // Checked state
5099 //
5100
5101 switch (data.GetCheckedState()) {
5104 break;
5107 msaa_state |= STATE_SYSTEM_PRESSED;
5108 } else if (data.role == ax::mojom::Role::kSwitch) {
5109 // ARIA switches are exposed to Windows accessibility as toggle
5110 // buttons. For maximum compatibility with ATs, we expose both the
5111 // pressed and checked states.
5112 msaa_state |= STATE_SYSTEM_PRESSED | STATE_SYSTEM_CHECKED;
5113 } else {
5114 msaa_state |= STATE_SYSTEM_CHECKED;
5115 }
5116 break;
5118 msaa_state |= STATE_SYSTEM_MIXED;
5119 break;
5120 }
5121
5122 const auto restriction = static_cast<ax::mojom::Restriction>(
5123 GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
5124 switch (restriction) {
5126 msaa_state |= STATE_SYSTEM_UNAVAILABLE;
5127 break;
5129 msaa_state |= STATE_SYSTEM_READONLY;
5130 break;
5131 default:
5132 // READONLY state is complex on Windows. We set STATE_SYSTEM_READONLY
5133 // on *some* document structure roles such as paragraph, heading or list
5134 // even if the node data isn't marked as read only, as long as the
5135 // node is not editable.
5136 if (!data.HasState(ax::mojom::State::kRichlyEditable) &&
5138 msaa_state |= STATE_SYSTEM_READONLY;
5139 }
5140 break;
5141 }
5142
5143 // Windowless plugins should have STATE_SYSTEM_UNAVAILABLE.
5144 //
5145 // (All of our plugins are windowless.)
5148 msaa_state |= STATE_SYSTEM_UNAVAILABLE;
5149 }
5150
5151 //
5152 // Handle STATE_SYSTEM_FOCUSED
5153 //
5154 gfx::NativeViewAccessible focus = GetDelegate()->GetFocus();
5155 if (focus == const_cast<AXPlatformNodeWin*>(this)->GetNativeViewAccessible())
5156 msaa_state |= STATE_SYSTEM_FOCUSED;
5157
5158 // In focused single selection UI menus and listboxes, mirror item selection
5159 // to focus. This helps NVDA read the selected option as it changes.
5160 if ((data.role == ax::mojom::Role::kListBoxOption || IsMenuItem(data.role)) &&
5161 data.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
5162 AXPlatformNodeBase* container = FromNativeViewAccessible(GetParent());
5163 if (container && container->GetParent() == focus) {
5164 AXNodeData container_data = container->GetData();
5165 if ((container_data.role == ax::mojom::Role::kListBox ||
5166 container_data.role == ax::mojom::Role::kMenu) &&
5167 !container_data.HasState(ax::mojom::State::kMultiselectable)) {
5168 msaa_state |= STATE_SYSTEM_FOCUSED;
5169 }
5170 }
5171 }
5172
5173 // On Windows, the "focus" bit should be set on certain containers, like
5174 // menu bars, when visible.
5175 //
5176 // TODO(dmazzoni): this should probably check if focus is actually inside
5177 // the menu bar, but we don't currently track focus inside menu pop-ups,
5178 // and Chrome only has one menu visible at a time so this works for now.
5179 if (data.role == ax::mojom::Role::kMenuBar &&
5180 !(data.HasState(ax::mojom::State::kInvisible))) {
5181 msaa_state |= STATE_SYSTEM_FOCUSED;
5182 }
5183
5184 // Handle STATE_SYSTEM_LINKED
5185 if (GetData().role == ax::mojom::Role::kLink)
5186 msaa_state |= STATE_SYSTEM_LINKED;
5187
5188 // Special case for indeterminate progressbar.
5189 if (GetData().role == ax::mojom::Role::kProgressIndicator &&
5191 msaa_state |= STATE_SYSTEM_MIXED;
5192
5193 return msaa_state;
5194}
5195
5196// static
5197std::optional<DWORD> AXPlatformNodeWin::MojoEventToMSAAEvent(
5199 switch (event) {
5200 case ax::mojom::Event::kAlert:
5201 return EVENT_SYSTEM_ALERT;
5202 case ax::mojom::Event::kCheckedStateChanged:
5203 case ax::mojom::Event::kExpandedChanged:
5204 case ax::mojom::Event::kStateChanged:
5205 return EVENT_OBJECT_STATECHANGE;
5206 case ax::mojom::Event::kFocus:
5207 case ax::mojom::Event::kFocusContext:
5208 return EVENT_OBJECT_FOCUS;
5209 case ax::mojom::Event::kLiveRegionChanged:
5210 return EVENT_OBJECT_LIVEREGIONCHANGED;
5211 case ax::mojom::Event::kMenuStart:
5212 return EVENT_SYSTEM_MENUSTART;
5213 case ax::mojom::Event::kMenuEnd:
5214 return EVENT_SYSTEM_MENUEND;
5215 case ax::mojom::Event::kMenuPopupStart:
5216 return EVENT_SYSTEM_MENUPOPUPSTART;
5217 case ax::mojom::Event::kMenuPopupEnd:
5218 return EVENT_SYSTEM_MENUPOPUPEND;
5219 case ax::mojom::Event::kSelection:
5220 return EVENT_OBJECT_SELECTION;
5221 case ax::mojom::Event::kSelectionAdd:
5222 return EVENT_OBJECT_SELECTIONADD;
5223 case ax::mojom::Event::kSelectionRemove:
5224 return EVENT_OBJECT_SELECTIONREMOVE;
5225 case ax::mojom::Event::kTextChanged:
5226 return EVENT_OBJECT_NAMECHANGE;
5227 case ax::mojom::Event::kTooltipClosed:
5228 return EVENT_OBJECT_HIDE;
5229 case ax::mojom::Event::kTooltipOpened:
5230 return EVENT_OBJECT_SHOW;
5231 case ax::mojom::Event::kValueChanged:
5232 return EVENT_OBJECT_VALUECHANGE;
5233 case ax::mojom::Event::kDocumentSelectionChanged:
5234 return EVENT_OBJECT_TEXTSELECTIONCHANGED;
5235 default:
5236 return std::nullopt;
5237 }
5238}
5239
5240// static
5241std::optional<EVENTID> AXPlatformNodeWin::MojoEventToUIAEvent(
5243 switch (event) {
5244 case ax::mojom::Event::kAlert:
5245 return UIA_SystemAlertEventId;
5246 case ax::mojom::Event::kDocumentSelectionChanged:
5247 return UIA_Text_TextChangedEventId;
5248 case ax::mojom::Event::kFocus:
5249 case ax::mojom::Event::kFocusContext:
5250 case ax::mojom::Event::kFocusAfterMenuClose:
5251 return UIA_AutomationFocusChangedEventId;
5252 case ax::mojom::Event::kLiveRegionChanged:
5253 return UIA_LiveRegionChangedEventId;
5254 case ax::mojom::Event::kSelection:
5255 return UIA_SelectionItem_ElementSelectedEventId;
5256 case ax::mojom::Event::kSelectionAdd:
5257 return UIA_SelectionItem_ElementAddedToSelectionEventId;
5258 case ax::mojom::Event::kSelectionRemove:
5259 return UIA_SelectionItem_ElementRemovedFromSelectionEventId;
5260 case ax::mojom::Event::kTooltipClosed:
5261 return UIA_ToolTipClosedEventId;
5262 case ax::mojom::Event::kTooltipOpened:
5263 return UIA_ToolTipOpenedEventId;
5264 default:
5265 return std::nullopt;
5266 }
5267}
5268
5269std::optional<PROPERTYID> AXPlatformNodeWin::MojoEventToUIAProperty(
5271 switch (event) {
5272 case ax::mojom::Event::kControlsChanged:
5273 return UIA_ControllerForPropertyId;
5274 case ax::mojom::Event::kCheckedStateChanged:
5275 return UIA_ToggleToggleStatePropertyId;
5276 case ax::mojom::Event::kRowCollapsed:
5277 case ax::mojom::Event::kRowExpanded:
5278 return UIA_ExpandCollapseExpandCollapseStatePropertyId;
5279 case ax::mojom::Event::kSelection:
5280 case ax::mojom::Event::kSelectionAdd:
5281 case ax::mojom::Event::kSelectionRemove:
5282 return UIA_SelectionItemIsSelectedPropertyId;
5283 case ax::mojom::Event::kValueChanged:
5284 if (SupportsToggle(GetData().role)) {
5285 return UIA_ToggleToggleStatePropertyId;
5286 }
5287 return std::nullopt;
5288 default:
5289 return std::nullopt;
5290 }
5291}
5292
5293// static
5294BSTR AXPlatformNodeWin::GetValueAttributeAsBstr(AXPlatformNodeWin* target) {
5295 // GetValueAttributeAsBstr() has two sets of special cases depending on the
5296 // node's role.
5297 // The first set apply without regard for the nodes |value| attribute. That is
5298 // the nodes value attribute isn't consider for the first set of special
5299 // cases. For example, if the node role is ax::mojom::Role::kColorWell, we do
5300 // not care at all about the node's ax::mojom::StringAttribute::kValue
5301 // attribute. The second set of special cases only apply if the value
5302 // attribute for the node is empty. That is, if
5303 // ax::mojom::StringAttribute::kValue is empty, we do something special.
5304 std::u16string result;
5305
5306 //
5307 // Color Well special case (Use ax::mojom::IntAttribute::kColorValue)
5308 //
5309 if (target->GetData().role == ax::mojom::Role::kColorWell) {
5310 // static cast because SkColor is a 4-byte unsigned int
5311 unsigned int color = static_cast<unsigned int>(
5313
5314 // This is just ARGB
5315 unsigned int red = (((color) >> 16) & 0xFF);
5316 unsigned int green = (((color) >> 8) & 0xFF);
5317 unsigned int blue = (((color) >> 0) & 0xFF);
5318 std::u16string value_text;
5319 value_text = base::NumberToString16(red * 100 / 255) + u"% red " +
5320 base::NumberToString16(green * 100 / 255) + u"% green " +
5321 base::NumberToString16(blue * 100 / 255) + u"% blue";
5322 BSTR value = ::SysAllocString(fml::Utf16ToWideString(value_text).c_str());
5323 BASE_DCHECK(value);
5324 return value;
5325 }
5326
5327 //
5328 // Document special case (Use the document's URL)
5329 //
5330 if (target->GetData().role == ax::mojom::Role::kRootWebArea ||
5331 target->GetData().role == ax::mojom::Role::kWebArea) {
5332 result = base::UTF8ToUTF16(target->GetDelegate()->GetTreeData().url);
5333 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5334 BASE_DCHECK(value);
5335 return value;
5336 }
5337
5338 //
5339 // Links (Use ax::mojom::StringAttribute::kUrl)
5340 //
5341 if (target->GetData().role == ax::mojom::Role::kLink) {
5342 result = target->GetString16Attribute(ax::mojom::StringAttribute::kUrl);
5343 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5344 BASE_DCHECK(value);
5345 return value;
5346 }
5347
5348 // For range controls, e.g. sliders and spin buttons, |ax_attr_value| holds
5349 // the aria-valuetext if present but not the inner text. The actual value,
5350 // provided either via aria-valuenow or the actual control's value is held in
5351 // |ax::mojom::FloatAttribute::kValueForRange|.
5352 result = target->GetString16Attribute(ax::mojom::StringAttribute::kValue);
5353 if (result.empty() && target->GetData().IsRangeValueSupported()) {
5354 float fval;
5356 &fval)) {
5358 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5359 BASE_DCHECK(value);
5360 return value;
5361 }
5362 }
5363
5364 if (result.empty() && target->IsRichTextField())
5365 result = target->GetInnerText();
5366
5367 BSTR value = ::SysAllocString(fml::Utf16ToWideString(result).c_str());
5368 BASE_DCHECK(value);
5369 return value;
5370}
5371
5372HRESULT AXPlatformNodeWin::GetStringAttributeAsBstr(
5374 BSTR* value_bstr) const {
5375 std::u16string str;
5376
5377 if (!GetString16Attribute(attribute, &str))
5378 return S_FALSE;
5379
5380 *value_bstr = ::SysAllocString(fml::Utf16ToWideString(str).c_str());
5381 BASE_DCHECK(*value_bstr);
5382
5383 return S_OK;
5384}
5385
5386HRESULT AXPlatformNodeWin::GetNameAsBstr(BSTR* value_bstr) const {
5387 std::u16string str = GetNameAsString16();
5388 *value_bstr = ::SysAllocString(fml::Utf16ToWideString(str).c_str());
5389 BASE_DCHECK(*value_bstr);
5390 return S_OK;
5391}
5392
5393// TODO(gw280): https://github.com/flutter/flutter/issues/78800
5394// Alert targets
5395void AXPlatformNodeWin::AddAlertTarget() {}
5396
5397void AXPlatformNodeWin::RemoveAlertTarget() {}
5398
5399AXPlatformNodeWin* AXPlatformNodeWin::GetTargetFromChildID(
5400 const VARIANT& var_id) {
5401 if (V_VT(&var_id) != VT_I4)
5402 return nullptr;
5403
5404 LONG child_id = V_I4(&var_id);
5405 if (child_id == CHILDID_SELF)
5406 return this;
5407
5408 if (child_id >= 1 && child_id <= GetDelegate()->GetChildCount()) {
5409 // Positive child ids are a 1-based child index, used by clients
5410 // that want to enumerate all immediate children.
5411 AXPlatformNodeBase* base =
5412 FromNativeViewAccessible(GetDelegate()->ChildAtIndex(child_id - 1));
5413 return static_cast<AXPlatformNodeWin*>(base);
5414 }
5415
5416 if (child_id >= 0)
5417 return nullptr;
5418
5419 // Negative child ids can be used to map to any descendant.
5420 AXPlatformNode* node = GetFromUniqueId(-child_id);
5421 if (!node)
5422 return nullptr;
5423
5424 AXPlatformNodeBase* base =
5425 FromNativeViewAccessible(node->GetNativeViewAccessible());
5426 if (base && !base->IsDescendantOf(this))
5427 base = nullptr;
5428
5429 return static_cast<AXPlatformNodeWin*>(base);
5430}
5431
5432bool AXPlatformNodeWin::IsInTreeGrid() {
5433 AXPlatformNodeBase* container = FromNativeViewAccessible(GetParent());
5434
5435 // If parent was a rowgroup, we need to look at the grandparent
5436 if (container && container->GetData().role == ax::mojom::Role::kRowGroup)
5437 container = FromNativeViewAccessible(container->GetParent());
5438
5439 if (!container)
5440 return false;
5441
5442 return container->GetData().role == ax::mojom::Role::kTreeGrid;
5443}
5444
5445HRESULT AXPlatformNodeWin::AllocateComArrayFromVector(
5446 std::vector<LONG>& results,
5447 LONG max,
5448 LONG** selected,
5449 LONG* n_selected) {
5450 BASE_DCHECK(max > 0);
5451 BASE_DCHECK(selected);
5452 BASE_DCHECK(n_selected);
5453
5454 auto count = std::min((LONG)results.size(), max);
5455 *n_selected = count;
5456 *selected = static_cast<LONG*>(CoTaskMemAlloc(sizeof(LONG) * count));
5457
5458 for (LONG i = 0; i < count; i++)
5459 (*selected)[i] = results[i];
5460 return S_OK;
5461}
5462
5463bool AXPlatformNodeWin::IsPlaceholderText() const {
5464 if (GetData().role != ax::mojom::Role::kStaticText)
5465 return false;
5466 AXPlatformNodeWin* parent =
5467 static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(GetParent()));
5468 // Static text nodes are always expected to have a parent.
5469 BASE_DCHECK(parent);
5470 return parent->IsTextField() &&
5471 parent->HasStringAttribute(ax::mojom::StringAttribute::kPlaceholder);
5472}
5473
5474double AXPlatformNodeWin::GetHorizontalScrollPercent() {
5475 if (!IsHorizontallyScrollable())
5476 return UIA_ScrollPatternNoScroll;
5477
5478 float x_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin);
5479 float x_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax);
5480 float x = GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
5481 return 100.0 * (x - x_min) / (x_max - x_min);
5482}
5483
5484double AXPlatformNodeWin::GetVerticalScrollPercent() {
5485 if (!IsVerticallyScrollable())
5486 return UIA_ScrollPatternNoScroll;
5487
5488 float y_min = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin);
5489 float y_max = GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
5490 float y = GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
5491 return 100.0 * (y - y_min) / (y_max - y_min);
5492}
5493
5494BSTR AXPlatformNodeWin::GetFontNameAttributeAsBSTR() const {
5495 const std::u16string string =
5496 GetInheritedString16Attribute(ax::mojom::StringAttribute::kFontFamily);
5497
5498 return ::SysAllocString(fml::Utf16ToWideString(string).c_str());
5499}
5500
5501BSTR AXPlatformNodeWin::GetStyleNameAttributeAsBSTR() const {
5502 std::u16string style_name =
5503 GetDelegate()->GetStyleNameAttributeAsLocalizedString();
5504
5505 return ::SysAllocString(fml::Utf16ToWideString(style_name).c_str());
5506}
5507
5508TextDecorationLineStyle AXPlatformNodeWin::GetUIATextDecorationStyle(
5509 const ax::mojom::IntAttribute int_attribute) const {
5510 const ax::mojom::TextDecorationStyle text_decoration_style =
5511 static_cast<ax::mojom::TextDecorationStyle>(
5512 GetIntAttribute(int_attribute));
5513
5514 switch (text_decoration_style) {
5516 return TextDecorationLineStyle::TextDecorationLineStyle_None;
5518 return TextDecorationLineStyle::TextDecorationLineStyle_Dot;
5520 return TextDecorationLineStyle::TextDecorationLineStyle_Dash;
5522 return TextDecorationLineStyle::TextDecorationLineStyle_Single;
5524 return TextDecorationLineStyle::TextDecorationLineStyle_Double;
5526 return TextDecorationLineStyle::TextDecorationLineStyle_Wavy;
5527 }
5528}
5529
5530// IRawElementProviderSimple support methods.
5531
5532AXPlatformNodeWin::PatternProviderFactoryMethod
5533AXPlatformNodeWin::GetPatternProviderFactoryMethod(PATTERNID pattern_id) {
5534 const AXNodeData& data = GetData();
5535
5536 switch (pattern_id) {
5537 case UIA_ExpandCollapsePatternId:
5538 if (data.SupportsExpandCollapse()) {
5539 return &PatternProvider<IExpandCollapseProvider>;
5540 }
5541 break;
5542
5543 case UIA_GridPatternId:
5544 if (IsTableLike(data.role)) {
5545 return &PatternProvider<IGridProvider>;
5546 }
5547 break;
5548
5549 case UIA_GridItemPatternId:
5550 if (IsCellOrTableHeader(data.role)) {
5551 return &PatternProvider<IGridItemProvider>;
5552 }
5553 break;
5554
5555 case UIA_InvokePatternId:
5556 if (data.IsInvocable()) {
5557 return &PatternProvider<IInvokeProvider>;
5558 }
5559 break;
5560
5561 case UIA_RangeValuePatternId:
5562 if (data.IsRangeValueSupported()) {
5563 return &PatternProvider<IRangeValueProvider>;
5564 }
5565 break;
5566
5567 case UIA_ScrollPatternId:
5568 if (IsScrollable()) {
5569 return &PatternProvider<IScrollProvider>;
5570 }
5571 break;
5572
5573 case UIA_ScrollItemPatternId:
5574 return &PatternProvider<IScrollItemProvider>;
5575 break;
5576
5577 case UIA_SelectionItemPatternId:
5578 if (IsSelectionItemSupported()) {
5579 return &PatternProvider<ISelectionItemProvider>;
5580 }
5581 break;
5582
5583 case UIA_SelectionPatternId:
5585 return &PatternProvider<ISelectionProvider>;
5586 }
5587 break;
5588
5589 case UIA_TablePatternId:
5590 // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itableprovider
5591 // This control pattern is analogous to IGridProvider with the distinction
5592 // that any control implementing ITableProvider must also expose a column
5593 // and/or row header relationship for each child element.
5594 if (IsTableLike(data.role)) {
5595 std::optional<bool> table_has_headers =
5596 GetDelegate()->GetTableHasColumnOrRowHeaderNode();
5597 if (table_has_headers.has_value() && table_has_headers.value()) {
5598 return &PatternProvider<ITableProvider>;
5599 }
5600 }
5601 break;
5602
5603 case UIA_TableItemPatternId:
5604 // https://docs.microsoft.com/en-us/windows/win32/api/uiautomationcore/nn-uiautomationcore-itableitemprovider
5605 // This control pattern is analogous to IGridItemProvider with the
5606 // distinction that any control implementing ITableItemProvider must
5607 // expose the relationship between the individual cell and its row and
5608 // column information.
5609 if (IsCellOrTableHeader(data.role)) {
5610 std::optional<bool> table_has_headers =
5611 GetDelegate()->GetTableHasColumnOrRowHeaderNode();
5612 if (table_has_headers.has_value() && table_has_headers.value()) {
5613 return &PatternProvider<ITableItemProvider>;
5614 }
5615 }
5616 break;
5617
5618 case UIA_TextEditPatternId:
5619 case UIA_TextPatternId:
5620 if (IsText() || IsTextField() ||
5622 return &AXPlatformNodeTextProviderWin::CreateIUnknown;
5623 }
5624 break;
5625
5626 case UIA_TogglePatternId:
5627 if (SupportsToggle(data.role)) {
5628 return &PatternProvider<IToggleProvider>;
5629 }
5630 break;
5631
5632 case UIA_ValuePatternId:
5633 if (IsValuePatternSupported(GetDelegate())) {
5634 return &PatternProvider<IValueProvider>;
5635 }
5636 break;
5637
5638 case UIA_WindowPatternId:
5639 if (HasBoolAttribute(ax::mojom::BoolAttribute::kModal)) {
5640 return &PatternProvider<IWindowProvider>;
5641 }
5642 break;
5643
5644 // Not currently implemented.
5645 case UIA_AnnotationPatternId:
5646 case UIA_CustomNavigationPatternId:
5647 case UIA_DockPatternId:
5648 case UIA_DragPatternId:
5649 case UIA_DropTargetPatternId:
5650 case UIA_ItemContainerPatternId:
5651 case UIA_MultipleViewPatternId:
5652 case UIA_ObjectModelPatternId:
5653 case UIA_SpreadsheetPatternId:
5654 case UIA_SpreadsheetItemPatternId:
5655 case UIA_StylesPatternId:
5656 case UIA_SynchronizedInputPatternId:
5657 case UIA_TextPattern2Id:
5658 case UIA_TransformPatternId:
5659 case UIA_TransformPattern2Id:
5660 case UIA_VirtualizedItemPatternId:
5661 break;
5662
5663 // Provided by UIA Core; we should not implement.
5664 case UIA_LegacyIAccessiblePatternId:
5665 break;
5666 }
5667 return nullptr;
5668}
5669
5670void AXPlatformNodeWin::FireLiveRegionChangeRecursive() {
5671 const auto live_status_attr = ax::mojom::StringAttribute::kLiveStatus;
5672 if (HasStringAttribute(live_status_attr) &&
5673 GetStringAttribute(live_status_attr) != "off") {
5674 BASE_DCHECK(GetDelegate()->IsWebContent());
5675 ::UiaRaiseAutomationEvent(this, UIA_LiveRegionChangedEventId);
5676 return;
5677 }
5678
5679 for (int index = 0; index < GetChildCount(); ++index) {
5680 auto* child = static_cast<AXPlatformNodeWin*>(
5681 FromNativeViewAccessible(ChildAtIndex(index)));
5682
5683 // We assume that only web-content will have live regions; also because
5684 // this will be called on each fragment-root, there is no need to walk
5685 // through non-content nodes.
5686 if (child->GetDelegate()->IsWebContent())
5687 child->FireLiveRegionChangeRecursive();
5688 }
5689}
5690
5691AXPlatformNodeWin* AXPlatformNodeWin::GetLowestAccessibleElement() {
5692 if (!IsInaccessibleDueToAncestor())
5693 return this;
5694
5695 AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
5697 while (parent) {
5698 if (parent->ShouldHideChildrenForUIA())
5699 return parent;
5700 parent = static_cast<AXPlatformNodeWin*>(
5701 AXPlatformNode::FromNativeViewAccessible(parent->GetParent()));
5702 }
5703
5705 return nullptr;
5706}
5707
5708AXPlatformNodeWin* AXPlatformNodeWin::GetFirstTextOnlyDescendant() {
5709 for (auto* child = static_cast<AXPlatformNodeWin*>(GetFirstChild()); child;
5710 child = static_cast<AXPlatformNodeWin*>(child->GetNextSibling())) {
5711 if (child->IsText())
5712 return child;
5713 if (AXPlatformNodeWin* descendant = child->GetFirstTextOnlyDescendant())
5714 return descendant;
5715 }
5716 return nullptr;
5717}
5718
5719bool AXPlatformNodeWin::IsDescendantOf(AXPlatformNode* ancestor) const {
5720 if (!ancestor) {
5721 return false;
5722 }
5723
5724 if (AXPlatformNodeBase::IsDescendantOf(ancestor)) {
5725 return true;
5726 }
5727
5728 // Test if the ancestor is an IRawElementProviderFragmentRoot and if it
5729 // matches this node's root fragment.
5730 IRawElementProviderFragmentRoot* root;
5731 if (SUCCEEDED(
5732 const_cast<AXPlatformNodeWin*>(this)->get_FragmentRoot(&root))) {
5733 AXPlatformNodeWin* root_win;
5734 if (SUCCEEDED(root->QueryInterface(__uuidof(AXPlatformNodeWin),
5735 reinterpret_cast<void**>(&root_win)))) {
5736 return ancestor == static_cast<AXPlatformNode*>(root_win);
5737 }
5738 }
5739
5740 return false;
5741}
5742
5743} // namespace ui
int count
SkColor4f color
static float next(float f)
static SkScalar center(float pos0, float pos1)
SI F table(const skcms_Curve *curve, F v)
ax::mojom::Event event_type
Type::kYUV Type::kRGBA() int(0.7 *637)
#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_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()
Vector2d OffsetFromOrigin() const
Definition rect.h:108
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()
MockDelegate delegate_
VkInstance instance
Definition main.cc:48
static bool b
struct MyStruct a[10]
EMSCRIPTEN_KEEPALIVE void empty()
AtkStateType state
glong glong end
FlKeyEvent * event
uint8_t value
GAsyncResult * result
uint32_t * target
const char * name
Definition fuchsia.cc:50
static float max(float r, float g, float b)
Definition hsl.cpp:49
size_t length
double y
double x
Optional< SkRect > bounds
Definition SkRecords.h:189
const int32_t kUnknownAriaColumnOrRowCount
DefaultActionVerb
Definition ax_enums.h:489
TextDecorationStyle
Definition ax_enums.h:995
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 switches.h:41
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)
#define T
int32_t height
int32_t width
#define BASE_FALLTHROUGH
#define BASE_UNLIKELY(x)
#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)