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.
9 #include "base/containers/hash_tables.h"
10 #include "base/lazy_instance.h"
11 #include "base/win/scoped_comptr.h"
12 #include "base/win/scoped_variant.h"
13 #include "third_party/iaccessible2/ia2_api_all.h"
14 #include "ui/accessibility/ax_node_data.h"
15 #include "ui/accessibility/ax_text_utils.h"
16 #include "ui/accessibility/platform/ax_platform_node_delegate.h"
17 #include "ui/accessibility/platform/ax_platform_node_win.h"
18 #include "ui/base/win/atl_module.h"
21 // Macros to use at the top of any AXPlatformNodeWin function that implements
22 // a COM interface. Because COM objects are reference counted and clients
23 // are completely untrusted, it's important to always first check that our
24 // object is still valid, and then check that all pointer arguments are
28 #define COM_OBJECT_VALIDATE() \
31 #define COM_OBJECT_VALIDATE_1_ARG(arg) \
32 if (!delegate_) return E_FAIL; \
33 if (!arg) return E_INVALIDARG
34 #define COM_OBJECT_VALIDATE_2_ARGS(arg1, arg2) \
35 if (!delegate_) return E_FAIL; \
36 if (!arg1) return E_INVALIDARG; \
37 if (!arg2) return E_INVALIDARG
38 #define COM_OBJECT_VALIDATE_3_ARGS(arg1, arg2, arg3) \
39 if (!delegate_) return E_FAIL; \
40 if (!arg1) return E_INVALIDARG; \
41 if (!arg2) return E_INVALIDARG; \
42 if (!arg3) return E_INVALIDARG
43 #define COM_OBJECT_VALIDATE_4_ARGS(arg1, arg2, arg3, arg4) \
44 if (!delegate_) return E_FAIL; \
45 if (!arg1) return E_INVALIDARG; \
46 if (!arg2) return E_INVALIDARG; \
47 if (!arg3) return E_INVALIDARG; \
48 if (!arg4) return E_INVALIDARG
49 #define COM_OBJECT_VALIDATE_VAR_ID(var_id) \
50 if (!delegate_) return E_FAIL; \
51 if (!IsValidId(var_id)) return E_INVALIDARG
52 #define COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, arg) \
53 if (!delegate_) return E_FAIL; \
54 if (!IsValidId(var_id)) return E_INVALIDARG; \
55 if (!arg) return E_INVALIDARG
56 #define COM_OBJECT_VALIDATE_VAR_ID_2_ARGS(var_id, arg1, arg2) \
57 if (!delegate_) return E_FAIL; \
58 if (!IsValidId(var_id)) return E_INVALIDARG; \
59 if (!arg1) return E_INVALIDARG; \
60 if (!arg2) return E_INVALIDARG
61 #define COM_OBJECT_VALIDATE_VAR_ID_3_ARGS(var_id, arg1, arg2, arg3) \
62 if (!delegate_) return E_FAIL; \
63 if (!IsValidId(var_id)) return E_INVALIDARG; \
64 if (!arg1) return E_INVALIDARG; \
65 if (!arg2) return E_INVALIDARG; \
66 if (!arg3) return E_INVALIDARG
67 #define COM_OBJECT_VALIDATE_VAR_ID_4_ARGS(var_id, arg1, arg2, arg3, arg4) \
68 if (!delegate_) return E_FAIL; \
69 if (!IsValidId(var_id)) return E_INVALIDARG; \
70 if (!arg1) return E_INVALIDARG; \
71 if (!arg2) return E_INVALIDARG; \
72 if (!arg3) return E_INVALIDARG; \
73 if (!arg4) return E_INVALIDARG
79 typedef base::hash_map
<LONG
, AXPlatformNodeWin
*> UniqueIdWinMap
;
80 // Map from each AXPlatformNodeWin's unique id to its instance.
81 base::LazyInstance
<UniqueIdWinMap
> g_unique_id_win_map
=
82 LAZY_INSTANCE_INITIALIZER
;
84 typedef base::hash_set
<AXPlatformNodeWin
*> AXPlatformNodeWinSet
;
85 // Set of all AXPlatformNodeWin objects that were the target of an
87 base::LazyInstance
<AXPlatformNodeWinSet
> g_alert_targets
=
88 LAZY_INSTANCE_INITIALIZER
;
90 LONG
GetNextNegativeUniqueIdForWinAccessibility(AXPlatformNodeWin
* obj
) {
91 static LONG next_unique_id
= -1;
92 LONG unique_id
= next_unique_id
;
93 if (next_unique_id
== LONG_MIN
)
98 g_unique_id_win_map
.Get().insert(std::make_pair(unique_id
, obj
));
103 void UnregisterNegativeUniqueId(LONG unique_id
) {
104 g_unique_id_win_map
.Get().erase(unique_id
);
110 AXPlatformNode
* AXPlatformNode::Create(AXPlatformNodeDelegate
* delegate
) {
111 // Make sure ATL is initialized in this module.
112 ui::win::CreateATLModuleIfNeeded();
114 CComObject
<AXPlatformNodeWin
>* instance
= nullptr;
115 HRESULT hr
= CComObject
<AXPlatformNodeWin
>::CreateInstance(&instance
);
116 DCHECK(SUCCEEDED(hr
));
117 instance
->Init(delegate
);
123 AXPlatformNode
* AXPlatformNode::FromNativeViewAccessible(
124 gfx::NativeViewAccessible accessible
) {
125 base::win::ScopedComPtr
<AXPlatformNodeWin
> ax_platform_node
;
126 accessible
->QueryInterface(ax_platform_node
.Receive());
127 return ax_platform_node
.get();
130 AXPlatformNodeWin::AXPlatformNodeWin()
131 : unique_id_win_(GetNextNegativeUniqueIdForWinAccessibility(this)) {
134 AXPlatformNodeWin::~AXPlatformNodeWin() {
139 // AXPlatformNode implementation.
142 void AXPlatformNodeWin::Destroy() {
144 UnregisterNegativeUniqueId(unique_id_win_
);
149 gfx::NativeViewAccessible
AXPlatformNodeWin::GetNativeViewAccessible() {
153 void AXPlatformNodeWin::NotifyAccessibilityEvent(ui::AXEvent event_type
) {
154 HWND hwnd
= delegate_
->GetTargetForNativeAccessibilityEvent();
158 int native_event
= MSAAEvent(event_type
);
159 if (native_event
< EVENT_MIN
)
162 ::NotifyWinEvent(native_event
, hwnd
, OBJID_CLIENT
, unique_id_win_
);
164 // Keep track of objects that are a target of an alert event.
165 if (event_type
== ui::AX_EVENT_ALERT
)
169 int AXPlatformNodeWin::GetIndexInParent() {
170 base::win::ScopedComPtr
<IDispatch
> parent_dispatch
;
171 base::win::ScopedComPtr
<IAccessible
> parent_accessible
;
172 if (S_OK
!= get_accParent(parent_dispatch
.Receive()))
174 if (S_OK
!= parent_dispatch
.QueryInterface(parent_accessible
.Receive()))
177 LONG child_count
= 0;
178 if (S_OK
!= parent_accessible
->get_accChildCount(&child_count
))
180 for (LONG index
= 1; index
<= child_count
; ++index
) {
181 base::win::ScopedVariant
childid_index(index
);
182 base::win::ScopedComPtr
<IDispatch
> child_dispatch
;
183 base::win::ScopedComPtr
<IAccessible
> child_accessible
;
184 if (S_OK
== parent_accessible
->get_accChild(childid_index
,
185 child_dispatch
.Receive()) &&
186 S_OK
== child_dispatch
.QueryInterface(child_accessible
.Receive())) {
187 if (child_accessible
.get() == this)
196 // IAccessible implementation.
199 STDMETHODIMP
AXPlatformNodeWin::accHitTest(
200 LONG x_left
, LONG y_top
, VARIANT
* child
) {
201 COM_OBJECT_VALIDATE_1_ARG(child
);
202 gfx::NativeViewAccessible hit_child
= delegate_
->HitTestSync(x_left
, y_top
);
204 child
->vt
= VT_EMPTY
;
208 if (hit_child
== this) {
209 // This object is the best match, so return CHILDID_SELF. It's tempting to
210 // simplify the logic and use VT_DISPATCH everywhere, but the Windows
211 // call AccessibleObjectFromPoint will keep calling accHitTest until some
212 // object returns CHILDID_SELF.
214 child
->lVal
= CHILDID_SELF
;
218 // Call accHitTest recursively on the result, which may be a recursive call
219 // to this function or it may be overridden, for example in the case of a
221 HRESULT result
= hit_child
->accHitTest(x_left
, y_top
, child
);
223 // If the recursive call returned CHILDID_SELF, we have to convert that
224 // into a VT_DISPATCH for the return value to this call.
225 if (S_OK
== result
&& child
->vt
== VT_I4
&& child
->lVal
== CHILDID_SELF
) {
226 child
->vt
= VT_DISPATCH
;
227 child
->pdispVal
= hit_child
;
228 // Always increment ref when returning a reference to a COM object.
229 child
->pdispVal
->AddRef();
234 HRESULT
AXPlatformNodeWin::accDoDefaultAction(VARIANT var_id
) {
235 COM_OBJECT_VALIDATE_VAR_ID(var_id
);
236 delegate_
->DoDefaultAction();
240 STDMETHODIMP
AXPlatformNodeWin::accLocation(
241 LONG
* x_left
, LONG
* y_top
, LONG
* width
, LONG
* height
, VARIANT var_id
) {
242 COM_OBJECT_VALIDATE_VAR_ID_4_ARGS(var_id
, x_left
, y_top
, width
, height
);
243 gfx::Rect bounds
= GetData().location
;
244 bounds
+= delegate_
->GetGlobalCoordinateOffset();
245 *x_left
= bounds
.x();
247 *width
= bounds
.width();
248 *height
= bounds
.height();
250 if (bounds
.IsEmpty())
256 STDMETHODIMP
AXPlatformNodeWin::accNavigate(
257 LONG nav_dir
, VARIANT start
, VARIANT
* end
) {
258 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(start
, end
);
259 IAccessible
* result
= nullptr;
266 // These directions are not implemented, matching Mozilla and IE.
269 case NAVDIR_FIRSTCHILD
:
270 if (delegate_
->GetChildCount() > 0)
271 result
= delegate_
->ChildAtIndex(0);
274 case NAVDIR_LASTCHILD
:
275 if (delegate_
->GetChildCount() > 0)
276 result
= delegate_
->ChildAtIndex(delegate_
->GetChildCount() - 1);
280 AXPlatformNodeBase
* next
= GetNextSibling();
282 result
= next
->GetNativeViewAccessible();
286 case NAVDIR_PREVIOUS
: {
287 AXPlatformNodeBase
* previous
= GetPreviousSibling();
289 result
= previous
->GetNativeViewAccessible();
302 end
->vt
= VT_DISPATCH
;
303 end
->pdispVal
= result
;
304 // Always increment ref when returning a reference to a COM object.
305 end
->pdispVal
->AddRef();
310 STDMETHODIMP
AXPlatformNodeWin::get_accChild(VARIANT var_child
,
311 IDispatch
** disp_child
) {
312 COM_OBJECT_VALIDATE_1_ARG(disp_child
);
313 LONG child_id
= V_I4(&var_child
);
314 if (child_id
== CHILDID_SELF
) {
316 (*disp_child
)->AddRef();
320 if (child_id
>= 1 && child_id
<= delegate_
->GetChildCount()) {
321 // Positive child ids are a 1-based child index, used by clients
322 // that want to enumerate all immediate children.
323 *disp_child
= delegate_
->ChildAtIndex(child_id
- 1);
326 (*disp_child
)->AddRef();
333 // Negative child ids can be used to map to any descendant.
334 UniqueIdWinMap
* unique_ids
= g_unique_id_win_map
.Pointer();
335 auto iter
= unique_ids
->find(child_id
);
336 if (iter
!= unique_ids
->end()) {
337 *disp_child
= iter
->second
;
338 (*disp_child
)->AddRef();
342 *disp_child
= nullptr;
346 STDMETHODIMP
AXPlatformNodeWin::get_accChildCount(LONG
* child_count
) {
347 COM_OBJECT_VALIDATE_1_ARG(child_count
);
348 *child_count
= delegate_
->GetChildCount();
352 STDMETHODIMP
AXPlatformNodeWin::get_accDefaultAction(
353 VARIANT var_id
, BSTR
* def_action
) {
354 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, def_action
);
355 return GetStringAttributeAsBstr(ui::AX_ATTR_ACTION
, def_action
);
358 STDMETHODIMP
AXPlatformNodeWin::get_accDescription(
359 VARIANT var_id
, BSTR
* desc
) {
360 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, desc
);
361 return GetStringAttributeAsBstr(ui::AX_ATTR_DESCRIPTION
, desc
);
364 STDMETHODIMP
AXPlatformNodeWin::get_accFocus(VARIANT
* focus_child
) {
365 COM_OBJECT_VALIDATE_1_ARG(focus_child
);
366 gfx::NativeViewAccessible focus_accessible
= delegate_
->GetFocus();
367 if (focus_accessible
== this) {
368 focus_child
->vt
= VT_I4
;
369 focus_child
->lVal
= CHILDID_SELF
;
370 } else if (focus_accessible
) {
371 focus_child
->vt
= VT_DISPATCH
;
372 focus_child
->pdispVal
= focus_accessible
;
373 focus_child
->pdispVal
->AddRef();
376 focus_child
->vt
= VT_EMPTY
;
382 STDMETHODIMP
AXPlatformNodeWin::get_accKeyboardShortcut(
383 VARIANT var_id
, BSTR
* acc_key
) {
384 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, acc_key
);
385 return GetStringAttributeAsBstr(ui::AX_ATTR_SHORTCUT
, acc_key
);
388 STDMETHODIMP
AXPlatformNodeWin::get_accName(
389 VARIANT var_id
, BSTR
* name
) {
390 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, name
);
391 return GetStringAttributeAsBstr(ui::AX_ATTR_NAME
, name
);
394 STDMETHODIMP
AXPlatformNodeWin::get_accParent(
395 IDispatch
** disp_parent
) {
396 COM_OBJECT_VALIDATE_1_ARG(disp_parent
);
397 *disp_parent
= GetParent();
399 (*disp_parent
)->AddRef();
406 STDMETHODIMP
AXPlatformNodeWin::get_accRole(
407 VARIANT var_id
, VARIANT
* role
) {
408 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, role
);
410 role
->lVal
= MSAARole();
414 STDMETHODIMP
AXPlatformNodeWin::get_accState(
415 VARIANT var_id
, VARIANT
* state
) {
416 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, state
);
418 state
->lVal
= MSAAState();
422 STDMETHODIMP
AXPlatformNodeWin::get_accHelp(
423 VARIANT var_id
, BSTR
* help
) {
424 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, help
);
425 return GetStringAttributeAsBstr(ui::AX_ATTR_HELP
, help
);
428 STDMETHODIMP
AXPlatformNodeWin::get_accValue(VARIANT var_id
, BSTR
* value
) {
429 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id
, value
);
430 return GetStringAttributeAsBstr(ui::AX_ATTR_VALUE
, value
);
433 STDMETHODIMP
AXPlatformNodeWin::put_accValue(VARIANT var_id
,
435 COM_OBJECT_VALIDATE_VAR_ID(var_id
);
436 if (delegate_
->SetStringValue(new_value
))
441 // IAccessible functions not supported.
443 STDMETHODIMP
AXPlatformNodeWin::get_accSelection(VARIANT
* selected
) {
444 COM_OBJECT_VALIDATE_1_ARG(selected
);
446 selected
->vt
= VT_EMPTY
;
450 STDMETHODIMP
AXPlatformNodeWin::accSelect(
451 LONG flagsSelect
, VARIANT var_id
) {
452 COM_OBJECT_VALIDATE_VAR_ID(var_id
);
456 STDMETHODIMP
AXPlatformNodeWin::get_accHelpTopic(
457 BSTR
* help_file
, VARIANT var_id
, LONG
* topic_id
) {
458 COM_OBJECT_VALIDATE_VAR_ID_2_ARGS(var_id
, help_file
, topic_id
);
460 *help_file
= nullptr;
463 *topic_id
= static_cast<LONG
>(-1);
468 STDMETHODIMP
AXPlatformNodeWin::put_accName(
469 VARIANT var_id
, BSTR put_name
) {
470 COM_OBJECT_VALIDATE_VAR_ID(var_id
);
476 // IAccessible2 implementation.
479 STDMETHODIMP
AXPlatformNodeWin::role(LONG
* role
) {
480 COM_OBJECT_VALIDATE_1_ARG(role
);
485 STDMETHODIMP
AXPlatformNodeWin::get_states(AccessibleStates
* states
) {
486 COM_OBJECT_VALIDATE_1_ARG(states
);
487 // There are only a couple of states we need to support
488 // in IAccessible2. If any more are added, we may want to
489 // add a helper function like MSAAState.
490 *states
= IA2_STATE_OPAQUE
;
491 if (GetData().state
& (1 << ui::AX_STATE_EDITABLE
))
492 *states
|= IA2_STATE_EDITABLE
;
497 STDMETHODIMP
AXPlatformNodeWin::get_uniqueID(LONG
* unique_id
) {
498 COM_OBJECT_VALIDATE_1_ARG(unique_id
);
499 *unique_id
= unique_id_win_
;
503 STDMETHODIMP
AXPlatformNodeWin::get_windowHandle(HWND
* window_handle
) {
504 COM_OBJECT_VALIDATE_1_ARG(window_handle
);
505 *window_handle
= delegate_
->GetTargetForNativeAccessibilityEvent();
506 return *window_handle
? S_OK
: S_FALSE
;
509 STDMETHODIMP
AXPlatformNodeWin::get_relationTargetsOfType(
514 COM_OBJECT_VALIDATE_2_ARGS(targets
, n_targets
);
519 // Only respond to requests for relations of type "alerts".
520 base::string16
type(type_bstr
);
521 if (type
!= L
"alerts")
524 // Collect all of the objects that have had an alert fired on them that
525 // are a descendant of this object.
526 std::vector
<AXPlatformNodeWin
*> alert_targets
;
527 for (auto iter
= g_alert_targets
.Get().begin();
528 iter
!= g_alert_targets
.Get().end();
530 AXPlatformNodeWin
* target
= *iter
;
531 if (IsDescendant(target
))
532 alert_targets
.push_back(target
);
535 long count
= static_cast<long>(alert_targets
.size());
539 // Don't return more targets than max_targets - but note that the caller
540 // is allowed to specify max_targets=0 to mean no limit.
541 if (max_targets
> 0 && count
> max_targets
)
544 // Return the number of targets.
547 // Allocate COM memory for the result array and populate it.
548 *targets
= static_cast<IUnknown
**>(
549 CoTaskMemAlloc(count
* sizeof(IUnknown
*)));
550 for (long i
= 0; i
< count
; ++i
) {
551 (*targets
)[i
] = static_cast<IAccessible
*>(alert_targets
[i
]);
552 (*targets
)[i
]->AddRef();
557 STDMETHODIMP
AXPlatformNodeWin::get_attributes(BSTR
* attributes
) {
558 COM_OBJECT_VALIDATE_1_ARG(attributes
);
559 base::string16 attributes_str
;
561 // Text fields need to report the attribute "text-model:a1" to instruct
562 // screen readers to use IAccessible2 APIs to handle text editing in this
563 // object (as opposed to treating it like a native Windows text box).
564 // The text-model:a1 attribute is documented here:
565 // http://www.linuxfoundation.org/collaborate/workgroups/accessibility/ia2/ia2_implementation_guide
566 if (GetData().role
== ui::AX_ROLE_TEXT_FIELD
) {
567 attributes_str
= L
"text-model:a1;";
570 *attributes
= SysAllocString(attributes_str
.c_str());
575 STDMETHODIMP
AXPlatformNodeWin::get_indexInParent(LONG
* index_in_parent
) {
576 COM_OBJECT_VALIDATE_1_ARG(index_in_parent
);
577 *index_in_parent
= GetIndexInParent();
578 if (*index_in_parent
< 0)
588 STDMETHODIMP
AXPlatformNodeWin::get_nCharacters(LONG
* n_characters
) {
589 COM_OBJECT_VALIDATE_1_ARG(n_characters
);
590 base::string16 text
= TextForIAccessibleText();
591 *n_characters
= static_cast<LONG
>(text
.size());
596 STDMETHODIMP
AXPlatformNodeWin::get_caretOffset(LONG
* offset
) {
597 COM_OBJECT_VALIDATE_1_ARG(offset
);
598 *offset
= static_cast<LONG
>(GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
));
602 STDMETHODIMP
AXPlatformNodeWin::get_nSelections(LONG
* n_selections
) {
603 COM_OBJECT_VALIDATE_1_ARG(n_selections
);
604 int sel_start
= GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
);
605 int sel_end
= GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
);
606 if (sel_start
!= sel_end
)
613 STDMETHODIMP
AXPlatformNodeWin::get_selection(LONG selection_index
,
616 COM_OBJECT_VALIDATE_2_ARGS(start_offset
, end_offset
);
617 if (selection_index
!= 0)
620 *start_offset
= static_cast<LONG
>(
621 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
));
622 *end_offset
= static_cast<LONG
>(
623 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END
));
627 STDMETHODIMP
AXPlatformNodeWin::get_text(LONG start_offset
,
630 COM_OBJECT_VALIDATE_1_ARG(text
);
631 int sel_end
= GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START
);
632 base::string16 text_str
= TextForIAccessibleText();
633 LONG len
= static_cast<LONG
>(text_str
.size());
635 if (start_offset
== IA2_TEXT_OFFSET_LENGTH
) {
637 } else if (start_offset
== IA2_TEXT_OFFSET_CARET
) {
638 start_offset
= static_cast<LONG
>(sel_end
);
640 if (end_offset
== IA2_TEXT_OFFSET_LENGTH
) {
641 end_offset
= static_cast<LONG
>(text_str
.size());
642 } else if (end_offset
== IA2_TEXT_OFFSET_CARET
) {
643 end_offset
= static_cast<LONG
>(sel_end
);
646 // The spec allows the arguments to be reversed.
647 if (start_offset
> end_offset
) {
648 LONG tmp
= start_offset
;
649 start_offset
= end_offset
;
653 // The spec does not allow the start or end offsets to be out or range;
654 // we must return an error if so.
655 if (start_offset
< 0)
657 if (end_offset
> len
)
660 base::string16 substr
=
661 text_str
.substr(start_offset
, end_offset
- start_offset
);
665 *text
= SysAllocString(substr
.c_str());
670 STDMETHODIMP
AXPlatformNodeWin::get_textAtOffset(
672 enum IA2TextBoundaryType boundary_type
,
673 LONG
* start_offset
, LONG
* end_offset
,
675 COM_OBJECT_VALIDATE_3_ARGS(start_offset
, end_offset
, text
);
676 // The IAccessible2 spec says we don't have to implement the "sentence"
677 // boundary type, we can just let the screen reader handle it.
678 if (boundary_type
== IA2_TEXT_BOUNDARY_SENTENCE
) {
685 const base::string16
& text_str
= TextForIAccessibleText();
687 *start_offset
= FindBoundary(
688 text_str
, boundary_type
, offset
, ui::BACKWARDS_DIRECTION
);
689 *end_offset
= FindBoundary(
690 text_str
, boundary_type
, offset
, ui::FORWARDS_DIRECTION
);
691 return get_text(*start_offset
, *end_offset
, text
);
694 STDMETHODIMP
AXPlatformNodeWin::get_textBeforeOffset(
696 enum IA2TextBoundaryType boundary_type
,
697 LONG
* start_offset
, LONG
* end_offset
,
699 if (!start_offset
|| !end_offset
|| !text
)
702 // The IAccessible2 spec says we don't have to implement the "sentence"
703 // boundary type, we can just let the screenreader handle it.
704 if (boundary_type
== IA2_TEXT_BOUNDARY_SENTENCE
) {
711 const base::string16
& text_str
= TextForIAccessibleText();
713 *start_offset
= FindBoundary(
714 text_str
, boundary_type
, offset
, ui::BACKWARDS_DIRECTION
);
715 *end_offset
= offset
;
716 return get_text(*start_offset
, *end_offset
, text
);
719 STDMETHODIMP
AXPlatformNodeWin::get_textAfterOffset(
721 enum IA2TextBoundaryType boundary_type
,
722 LONG
* start_offset
, LONG
* end_offset
,
724 if (!start_offset
|| !end_offset
|| !text
)
727 // The IAccessible2 spec says we don't have to implement the "sentence"
728 // boundary type, we can just let the screenreader handle it.
729 if (boundary_type
== IA2_TEXT_BOUNDARY_SENTENCE
) {
736 const base::string16
& text_str
= TextForIAccessibleText();
738 *start_offset
= offset
;
739 *end_offset
= FindBoundary(
740 text_str
, boundary_type
, offset
, ui::FORWARDS_DIRECTION
);
741 return get_text(*start_offset
, *end_offset
, text
);
744 STDMETHODIMP
AXPlatformNodeWin::get_offsetAtPoint(
745 LONG x
, LONG y
, enum IA2CoordinateType coord_type
, LONG
* offset
) {
746 COM_OBJECT_VALIDATE_1_ARG(offset
);
747 // We don't support this method, but we have to return something
748 // rather than E_NOTIMPL or screen readers will complain.
754 // IServiceProvider implementation.
757 STDMETHODIMP
AXPlatformNodeWin::QueryService(
758 REFGUID guidService
, REFIID riid
, void** object
) {
759 COM_OBJECT_VALIDATE_1_ARG(object
);
760 if (guidService
== IID_IAccessible
||
761 guidService
== IID_IAccessible2
||
762 guidService
== IID_IAccessible2_2
||
763 guidService
== IID_IAccessibleText
) {
764 return QueryInterface(riid
, object
);
772 // Private member functions.
775 bool AXPlatformNodeWin::IsValidId(const VARIANT
& child
) const {
776 // Since we have a separate IAccessible COM object for each node, we only
777 // support the CHILDID_SELF id.
778 return (VT_I4
== child
.vt
) && (CHILDID_SELF
== child
.lVal
);
781 int AXPlatformNodeWin::MSAARole() {
782 switch (GetData().role
) {
783 case ui::AX_ROLE_ALERT
:
784 return ROLE_SYSTEM_ALERT
;
785 case ui::AX_ROLE_APPLICATION
:
786 return ROLE_SYSTEM_APPLICATION
;
787 case ui::AX_ROLE_BUTTON_DROP_DOWN
:
788 return ROLE_SYSTEM_BUTTONDROPDOWN
;
789 case ui::AX_ROLE_POP_UP_BUTTON
:
790 return ROLE_SYSTEM_BUTTONMENU
;
791 case ui::AX_ROLE_CHECK_BOX
:
792 return ROLE_SYSTEM_CHECKBUTTON
;
793 case ui::AX_ROLE_COMBO_BOX
:
794 return ROLE_SYSTEM_COMBOBOX
;
795 case ui::AX_ROLE_DIALOG
:
796 return ROLE_SYSTEM_DIALOG
;
797 case ui::AX_ROLE_GROUP
:
798 return ROLE_SYSTEM_GROUPING
;
799 case ui::AX_ROLE_IMAGE
:
800 return ROLE_SYSTEM_GRAPHIC
;
801 case ui::AX_ROLE_LINK
:
802 return ROLE_SYSTEM_LINK
;
803 case ui::AX_ROLE_LOCATION_BAR
:
804 return ROLE_SYSTEM_GROUPING
;
805 case ui::AX_ROLE_MENU_BAR
:
806 return ROLE_SYSTEM_MENUBAR
;
807 case ui::AX_ROLE_MENU_ITEM
:
808 return ROLE_SYSTEM_MENUITEM
;
809 case ui::AX_ROLE_MENU_LIST_POPUP
:
810 return ROLE_SYSTEM_MENUPOPUP
;
811 case ui::AX_ROLE_TREE
:
812 return ROLE_SYSTEM_OUTLINE
;
813 case ui::AX_ROLE_TREE_ITEM
:
814 return ROLE_SYSTEM_OUTLINEITEM
;
815 case ui::AX_ROLE_TAB
:
816 return ROLE_SYSTEM_PAGETAB
;
817 case ui::AX_ROLE_TAB_LIST
:
818 return ROLE_SYSTEM_PAGETABLIST
;
819 case ui::AX_ROLE_PANE
:
820 return ROLE_SYSTEM_PANE
;
821 case ui::AX_ROLE_PROGRESS_INDICATOR
:
822 return ROLE_SYSTEM_PROGRESSBAR
;
823 case ui::AX_ROLE_BUTTON
:
824 return ROLE_SYSTEM_PUSHBUTTON
;
825 case ui::AX_ROLE_RADIO_BUTTON
:
826 return ROLE_SYSTEM_RADIOBUTTON
;
827 case ui::AX_ROLE_SCROLL_BAR
:
828 return ROLE_SYSTEM_SCROLLBAR
;
829 case ui::AX_ROLE_SPLITTER
:
830 return ROLE_SYSTEM_SEPARATOR
;
831 case ui::AX_ROLE_SLIDER
:
832 return ROLE_SYSTEM_SLIDER
;
833 case ui::AX_ROLE_STATIC_TEXT
:
834 return ROLE_SYSTEM_STATICTEXT
;
835 case ui::AX_ROLE_TEXT_FIELD
:
836 return ROLE_SYSTEM_TEXT
;
837 case ui::AX_ROLE_TITLE_BAR
:
838 return ROLE_SYSTEM_TITLEBAR
;
839 case ui::AX_ROLE_TOOLBAR
:
840 return ROLE_SYSTEM_TOOLBAR
;
841 case ui::AX_ROLE_WEB_VIEW
:
842 return ROLE_SYSTEM_GROUPING
;
843 case ui::AX_ROLE_WINDOW
:
844 return ROLE_SYSTEM_WINDOW
;
845 case ui::AX_ROLE_CLIENT
:
847 // This is the default role for MSAA.
848 return ROLE_SYSTEM_CLIENT
;
852 int AXPlatformNodeWin::MSAAState() {
853 uint32 state
= GetData().state
;
856 if (state
& (1 << ui::AX_STATE_CHECKED
))
857 msaa_state
|= STATE_SYSTEM_CHECKED
;
858 if (state
& (1 << ui::AX_STATE_COLLAPSED
))
859 msaa_state
|= STATE_SYSTEM_COLLAPSED
;
860 if (state
& (1 << ui::AX_STATE_DEFAULT
))
861 msaa_state
|= STATE_SYSTEM_DEFAULT
;
862 if (state
& (1 << ui::AX_STATE_EXPANDED
))
863 msaa_state
|= STATE_SYSTEM_EXPANDED
;
864 if (state
& (1 << ui::AX_STATE_FOCUSABLE
))
865 msaa_state
|= STATE_SYSTEM_FOCUSABLE
;
866 if (state
& (1 << ui::AX_STATE_FOCUSED
))
867 msaa_state
|= STATE_SYSTEM_FOCUSED
;
868 if (state
& (1 << ui::AX_STATE_HASPOPUP
))
869 msaa_state
|= STATE_SYSTEM_HASPOPUP
;
870 if (state
& (1 << ui::AX_STATE_HOVERED
))
871 msaa_state
|= STATE_SYSTEM_HOTTRACKED
;
872 if (state
& (1 << ui::AX_STATE_INVISIBLE
))
873 msaa_state
|= STATE_SYSTEM_INVISIBLE
;
874 if (state
& (1 << ui::AX_STATE_LINKED
))
875 msaa_state
|= STATE_SYSTEM_LINKED
;
876 if (state
& (1 << ui::AX_STATE_OFFSCREEN
))
877 msaa_state
|= STATE_SYSTEM_OFFSCREEN
;
878 if (state
& (1 << ui::AX_STATE_PRESSED
))
879 msaa_state
|= STATE_SYSTEM_PRESSED
;
880 if (state
& (1 << ui::AX_STATE_PROTECTED
))
881 msaa_state
|= STATE_SYSTEM_PROTECTED
;
882 if (state
& (1 << ui::AX_STATE_READ_ONLY
))
883 msaa_state
|= STATE_SYSTEM_READONLY
;
884 if (state
& (1 << ui::AX_STATE_SELECTABLE
))
885 msaa_state
|= STATE_SYSTEM_SELECTABLE
;
886 if (state
& (1 << ui::AX_STATE_SELECTED
))
887 msaa_state
|= STATE_SYSTEM_SELECTED
;
888 if (state
& (1 << ui::AX_STATE_DISABLED
))
889 msaa_state
|= STATE_SYSTEM_UNAVAILABLE
;
894 int AXPlatformNodeWin::MSAAEvent(ui::AXEvent event
) {
896 case ui::AX_EVENT_ALERT
:
897 return EVENT_SYSTEM_ALERT
;
898 case ui::AX_EVENT_FOCUS
:
899 return EVENT_OBJECT_FOCUS
;
900 case ui::AX_EVENT_MENU_START
:
901 return EVENT_SYSTEM_MENUSTART
;
902 case ui::AX_EVENT_MENU_END
:
903 return EVENT_SYSTEM_MENUEND
;
904 case ui::AX_EVENT_MENU_POPUP_START
:
905 return EVENT_SYSTEM_MENUPOPUPSTART
;
906 case ui::AX_EVENT_MENU_POPUP_END
:
907 return EVENT_SYSTEM_MENUPOPUPEND
;
908 case ui::AX_EVENT_SELECTION
:
909 return EVENT_OBJECT_SELECTION
;
910 case ui::AX_EVENT_SELECTION_ADD
:
911 return EVENT_OBJECT_SELECTIONADD
;
912 case ui::AX_EVENT_SELECTION_REMOVE
:
913 return EVENT_OBJECT_SELECTIONREMOVE
;
914 case ui::AX_EVENT_TEXT_CHANGED
:
915 return EVENT_OBJECT_NAMECHANGE
;
916 case ui::AX_EVENT_TEXT_SELECTION_CHANGED
:
917 return IA2_EVENT_TEXT_CARET_MOVED
;
918 case ui::AX_EVENT_VALUE_CHANGED
:
919 return EVENT_OBJECT_VALUECHANGE
;
925 HRESULT
AXPlatformNodeWin::GetStringAttributeAsBstr(
926 ui::AXStringAttribute attribute
,
927 BSTR
* value_bstr
) const {
930 if (!GetString16Attribute(attribute
, &str
))
936 *value_bstr
= SysAllocString(str
.c_str());
942 void AXPlatformNodeWin::AddAlertTarget() {
943 g_alert_targets
.Get().insert(this);
946 void AXPlatformNodeWin::RemoveAlertTarget() {
947 if (g_alert_targets
.Get().find(this) != g_alert_targets
.Get().end())
948 g_alert_targets
.Get().erase(this);
951 base::string16
AXPlatformNodeWin::TextForIAccessibleText() {
952 if (GetData().role
== ui::AX_ROLE_TEXT_FIELD
)
953 return GetString16Attribute(ui::AX_ATTR_VALUE
);
955 return GetString16Attribute(ui::AX_ATTR_NAME
);
958 void AXPlatformNodeWin::HandleSpecialTextOffset(
959 const base::string16
& text
, LONG
* offset
) {
960 if (*offset
== IA2_TEXT_OFFSET_LENGTH
) {
961 *offset
= static_cast<LONG
>(text
.size());
962 } else if (*offset
== IA2_TEXT_OFFSET_CARET
) {
963 get_caretOffset(offset
);
967 ui::TextBoundaryType
AXPlatformNodeWin::IA2TextBoundaryToTextBoundary(
968 IA2TextBoundaryType ia2_boundary
) {
969 switch(ia2_boundary
) {
970 case IA2_TEXT_BOUNDARY_CHAR
: return ui::CHAR_BOUNDARY
;
971 case IA2_TEXT_BOUNDARY_WORD
: return ui::WORD_BOUNDARY
;
972 case IA2_TEXT_BOUNDARY_LINE
: return ui::LINE_BOUNDARY
;
973 case IA2_TEXT_BOUNDARY_SENTENCE
: return ui::SENTENCE_BOUNDARY
;
974 case IA2_TEXT_BOUNDARY_PARAGRAPH
: return ui::PARAGRAPH_BOUNDARY
;
975 case IA2_TEXT_BOUNDARY_ALL
: return ui::ALL_BOUNDARY
;
978 return ui::CHAR_BOUNDARY
;
982 LONG
AXPlatformNodeWin::FindBoundary(
983 const base::string16
& text
,
984 IA2TextBoundaryType ia2_boundary
,
986 ui::TextBoundaryDirection direction
) {
987 HandleSpecialTextOffset(text
, &start_offset
);
988 ui::TextBoundaryType boundary
= IA2TextBoundaryToTextBoundary(ia2_boundary
);
989 std::vector
<int32
> line_breaks
;
990 return static_cast<LONG
>(ui::FindAccessibleTextBoundary(
991 text
, line_breaks
, boundary
, start_offset
, direction
));