OLD | NEW |
| (Empty) |
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 | |
5 #include <atlbase.h> | |
6 #include <atlcom.h> | |
7 #include <oleacc.h> | |
8 | |
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" | |
19 | |
20 // | |
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 | |
25 // not NULL. | |
26 // | |
27 | |
28 #define COM_OBJECT_VALIDATE() \ | |
29 if (!delegate_) \ | |
30 return E_FAIL; | |
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 | |
74 | |
75 namespace ui { | |
76 | |
77 namespace { | |
78 | |
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; | |
83 | |
84 typedef base::hash_set<AXPlatformNodeWin*> AXPlatformNodeWinSet; | |
85 // Set of all AXPlatformNodeWin objects that were the target of an | |
86 // alert event. | |
87 base::LazyInstance<AXPlatformNodeWinSet> g_alert_targets = | |
88 LAZY_INSTANCE_INITIALIZER; | |
89 | |
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) | |
94 next_unique_id = -1; | |
95 else | |
96 next_unique_id--; | |
97 | |
98 g_unique_id_win_map.Get().insert(std::make_pair(unique_id, obj)); | |
99 | |
100 return unique_id; | |
101 } | |
102 | |
103 void UnregisterNegativeUniqueId(LONG unique_id) { | |
104 g_unique_id_win_map.Get().erase(unique_id); | |
105 } | |
106 | |
107 } // namespace | |
108 | |
109 // static | |
110 AXPlatformNode* AXPlatformNode::Create(AXPlatformNodeDelegate* delegate) { | |
111 // Make sure ATL is initialized in this module. | |
112 ui::win::CreateATLModuleIfNeeded(); | |
113 | |
114 CComObject<AXPlatformNodeWin>* instance = nullptr; | |
115 HRESULT hr = CComObject<AXPlatformNodeWin>::CreateInstance(&instance); | |
116 DCHECK(SUCCEEDED(hr)); | |
117 instance->Init(delegate); | |
118 instance->AddRef(); | |
119 return instance; | |
120 } | |
121 | |
122 // static | |
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(); | |
128 } | |
129 | |
130 AXPlatformNodeWin::AXPlatformNodeWin() | |
131 : unique_id_win_(GetNextNegativeUniqueIdForWinAccessibility(this)) { | |
132 } | |
133 | |
134 AXPlatformNodeWin::~AXPlatformNodeWin() { | |
135 CHECK(!delegate_); | |
136 } | |
137 | |
138 // | |
139 // AXPlatformNode implementation. | |
140 // | |
141 | |
142 void AXPlatformNodeWin::Destroy() { | |
143 delegate_ = nullptr; | |
144 UnregisterNegativeUniqueId(unique_id_win_); | |
145 RemoveAlertTarget(); | |
146 Release(); | |
147 } | |
148 | |
149 gfx::NativeViewAccessible AXPlatformNodeWin::GetNativeViewAccessible() { | |
150 return this; | |
151 } | |
152 | |
153 void AXPlatformNodeWin::NotifyAccessibilityEvent(ui::AXEvent event_type) { | |
154 HWND hwnd = delegate_->GetTargetForNativeAccessibilityEvent(); | |
155 if (!hwnd) | |
156 return; | |
157 | |
158 int native_event = MSAAEvent(event_type); | |
159 if (native_event < EVENT_MIN) | |
160 return; | |
161 | |
162 ::NotifyWinEvent(native_event, hwnd, OBJID_CLIENT, unique_id_win_); | |
163 | |
164 // Keep track of objects that are a target of an alert event. | |
165 if (event_type == ui::AX_EVENT_ALERT) | |
166 AddAlertTarget(); | |
167 } | |
168 | |
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())) | |
173 return -1; | |
174 if (S_OK != parent_dispatch.QueryInterface(parent_accessible.Receive())) | |
175 return -1; | |
176 | |
177 LONG child_count = 0; | |
178 if (S_OK != parent_accessible->get_accChildCount(&child_count)) | |
179 return -1; | |
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) | |
188 return index - 1; | |
189 } | |
190 } | |
191 | |
192 return -1; | |
193 } | |
194 | |
195 // | |
196 // IAccessible implementation. | |
197 // | |
198 | |
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); | |
203 if (!hit_child) { | |
204 child->vt = VT_EMPTY; | |
205 return S_FALSE; | |
206 } | |
207 | |
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. | |
213 child->vt = VT_I4; | |
214 child->lVal = CHILDID_SELF; | |
215 return S_OK; | |
216 } | |
217 | |
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 | |
220 // WebView. | |
221 HRESULT result = hit_child->accHitTest(x_left, y_top, child); | |
222 | |
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(); | |
230 } | |
231 return result; | |
232 } | |
233 | |
234 HRESULT AXPlatformNodeWin::accDoDefaultAction(VARIANT var_id) { | |
235 COM_OBJECT_VALIDATE_VAR_ID(var_id); | |
236 delegate_->DoDefaultAction(); | |
237 return S_OK; | |
238 } | |
239 | |
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(); | |
246 *y_top = bounds.y(); | |
247 *width = bounds.width(); | |
248 *height = bounds.height(); | |
249 | |
250 if (bounds.IsEmpty()) | |
251 return S_FALSE; | |
252 | |
253 return S_OK; | |
254 } | |
255 | |
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; | |
260 | |
261 switch (nav_dir) { | |
262 case NAVDIR_DOWN: | |
263 case NAVDIR_UP: | |
264 case NAVDIR_LEFT: | |
265 case NAVDIR_RIGHT: | |
266 // These directions are not implemented, matching Mozilla and IE. | |
267 return E_NOTIMPL; | |
268 | |
269 case NAVDIR_FIRSTCHILD: | |
270 if (delegate_->GetChildCount() > 0) | |
271 result = delegate_->ChildAtIndex(0); | |
272 break; | |
273 | |
274 case NAVDIR_LASTCHILD: | |
275 if (delegate_->GetChildCount() > 0) | |
276 result = delegate_->ChildAtIndex(delegate_->GetChildCount() - 1); | |
277 break; | |
278 | |
279 case NAVDIR_NEXT: { | |
280 AXPlatformNodeBase* next = GetNextSibling(); | |
281 if (next) | |
282 result = next->GetNativeViewAccessible(); | |
283 break; | |
284 } | |
285 | |
286 case NAVDIR_PREVIOUS: { | |
287 AXPlatformNodeBase* previous = GetPreviousSibling(); | |
288 if (previous) | |
289 result = previous->GetNativeViewAccessible(); | |
290 break; | |
291 } | |
292 | |
293 default: | |
294 return E_INVALIDARG; | |
295 } | |
296 | |
297 if (!result) { | |
298 end->vt = VT_EMPTY; | |
299 return S_FALSE; | |
300 } | |
301 | |
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(); | |
306 | |
307 return S_OK; | |
308 } | |
309 | |
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) { | |
315 *disp_child = this; | |
316 (*disp_child)->AddRef(); | |
317 return S_OK; | |
318 } | |
319 | |
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); | |
324 (*disp_child)->AddRef(); | |
325 return S_OK; | |
326 } | |
327 | |
328 if (child_id >= 0) | |
329 return E_FAIL; | |
330 | |
331 // Negative child ids can be used to map to any descendant. | |
332 UniqueIdWinMap* unique_ids = g_unique_id_win_map.Pointer(); | |
333 auto iter = unique_ids->find(child_id); | |
334 if (iter != unique_ids->end()) { | |
335 *disp_child = iter->second; | |
336 (*disp_child)->AddRef(); | |
337 return S_OK; | |
338 } | |
339 | |
340 *disp_child = nullptr; | |
341 return E_FAIL; | |
342 } | |
343 | |
344 STDMETHODIMP AXPlatformNodeWin::get_accChildCount(LONG* child_count) { | |
345 COM_OBJECT_VALIDATE_1_ARG(child_count); | |
346 *child_count = delegate_->GetChildCount(); | |
347 return S_OK; | |
348 } | |
349 | |
350 STDMETHODIMP AXPlatformNodeWin::get_accDefaultAction( | |
351 VARIANT var_id, BSTR* def_action) { | |
352 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, def_action); | |
353 return GetStringAttributeAsBstr(ui::AX_ATTR_ACTION, def_action); | |
354 } | |
355 | |
356 STDMETHODIMP AXPlatformNodeWin::get_accDescription( | |
357 VARIANT var_id, BSTR* desc) { | |
358 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, desc); | |
359 return GetStringAttributeAsBstr(ui::AX_ATTR_DESCRIPTION, desc); | |
360 } | |
361 | |
362 STDMETHODIMP AXPlatformNodeWin::get_accFocus(VARIANT* focus_child) { | |
363 COM_OBJECT_VALIDATE_1_ARG(focus_child); | |
364 gfx::NativeViewAccessible focus_accessible = delegate_->GetFocus(); | |
365 if (focus_accessible == this) { | |
366 focus_child->vt = VT_I4; | |
367 focus_child->lVal = CHILDID_SELF; | |
368 } else if (focus_accessible) { | |
369 focus_child->vt = VT_DISPATCH; | |
370 focus_child->pdispVal = focus_accessible; | |
371 focus_child->pdispVal->AddRef(); | |
372 return S_OK; | |
373 } else { | |
374 focus_child->vt = VT_EMPTY; | |
375 } | |
376 | |
377 return S_OK; | |
378 } | |
379 | |
380 STDMETHODIMP AXPlatformNodeWin::get_accKeyboardShortcut( | |
381 VARIANT var_id, BSTR* acc_key) { | |
382 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, acc_key); | |
383 return GetStringAttributeAsBstr(ui::AX_ATTR_SHORTCUT, acc_key); | |
384 } | |
385 | |
386 STDMETHODIMP AXPlatformNodeWin::get_accName( | |
387 VARIANT var_id, BSTR* name) { | |
388 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, name); | |
389 return GetStringAttributeAsBstr(ui::AX_ATTR_NAME, name); | |
390 } | |
391 | |
392 STDMETHODIMP AXPlatformNodeWin::get_accParent( | |
393 IDispatch** disp_parent) { | |
394 COM_OBJECT_VALIDATE_1_ARG(disp_parent); | |
395 *disp_parent = GetParent(); | |
396 if (*disp_parent) { | |
397 (*disp_parent)->AddRef(); | |
398 return S_OK; | |
399 } | |
400 | |
401 return S_FALSE; | |
402 } | |
403 | |
404 STDMETHODIMP AXPlatformNodeWin::get_accRole( | |
405 VARIANT var_id, VARIANT* role) { | |
406 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, role); | |
407 role->vt = VT_I4; | |
408 role->lVal = MSAARole(); | |
409 return S_OK; | |
410 } | |
411 | |
412 STDMETHODIMP AXPlatformNodeWin::get_accState( | |
413 VARIANT var_id, VARIANT* state) { | |
414 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, state); | |
415 state->vt = VT_I4; | |
416 state->lVal = MSAAState(); | |
417 return S_OK; | |
418 } | |
419 | |
420 STDMETHODIMP AXPlatformNodeWin::get_accHelp( | |
421 VARIANT var_id, BSTR* help) { | |
422 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, help); | |
423 return GetStringAttributeAsBstr(ui::AX_ATTR_HELP, help); | |
424 } | |
425 | |
426 STDMETHODIMP AXPlatformNodeWin::get_accValue(VARIANT var_id, BSTR* value) { | |
427 COM_OBJECT_VALIDATE_VAR_ID_1_ARG(var_id, value); | |
428 return GetStringAttributeAsBstr(ui::AX_ATTR_VALUE, value); | |
429 } | |
430 | |
431 STDMETHODIMP AXPlatformNodeWin::put_accValue(VARIANT var_id, | |
432 BSTR new_value) { | |
433 COM_OBJECT_VALIDATE_VAR_ID(var_id); | |
434 if (delegate_->SetStringValue(new_value)) | |
435 return S_OK; | |
436 return E_FAIL; | |
437 } | |
438 | |
439 // IAccessible functions not supported. | |
440 | |
441 STDMETHODIMP AXPlatformNodeWin::get_accSelection(VARIANT* selected) { | |
442 COM_OBJECT_VALIDATE_1_ARG(selected); | |
443 if (selected) | |
444 selected->vt = VT_EMPTY; | |
445 return E_NOTIMPL; | |
446 } | |
447 | |
448 STDMETHODIMP AXPlatformNodeWin::accSelect( | |
449 LONG flagsSelect, VARIANT var_id) { | |
450 COM_OBJECT_VALIDATE_VAR_ID(var_id); | |
451 return E_NOTIMPL; | |
452 } | |
453 | |
454 STDMETHODIMP AXPlatformNodeWin::get_accHelpTopic( | |
455 BSTR* help_file, VARIANT var_id, LONG* topic_id) { | |
456 COM_OBJECT_VALIDATE_VAR_ID_2_ARGS(var_id, help_file, topic_id); | |
457 if (help_file) { | |
458 *help_file = nullptr; | |
459 } | |
460 if (topic_id) { | |
461 *topic_id = static_cast<LONG>(-1); | |
462 } | |
463 return E_NOTIMPL; | |
464 } | |
465 | |
466 STDMETHODIMP AXPlatformNodeWin::put_accName( | |
467 VARIANT var_id, BSTR put_name) { | |
468 COM_OBJECT_VALIDATE_VAR_ID(var_id); | |
469 // Deprecated. | |
470 return E_NOTIMPL; | |
471 } | |
472 | |
473 // | |
474 // IAccessible2 implementation. | |
475 // | |
476 | |
477 STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { | |
478 COM_OBJECT_VALIDATE_1_ARG(role); | |
479 *role = MSAARole(); | |
480 return S_OK; | |
481 } | |
482 | |
483 STDMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) { | |
484 COM_OBJECT_VALIDATE_1_ARG(states); | |
485 // There are only a couple of states we need to support | |
486 // in IAccessible2. If any more are added, we may want to | |
487 // add a helper function like MSAAState. | |
488 *states = IA2_STATE_OPAQUE; | |
489 if (GetData().state & (1 << ui::AX_STATE_EDITABLE)) | |
490 *states |= IA2_STATE_EDITABLE; | |
491 | |
492 return S_OK; | |
493 } | |
494 | |
495 STDMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* unique_id) { | |
496 COM_OBJECT_VALIDATE_1_ARG(unique_id); | |
497 *unique_id = unique_id_win_; | |
498 return S_OK; | |
499 } | |
500 | |
501 STDMETHODIMP AXPlatformNodeWin::get_windowHandle(HWND* window_handle) { | |
502 COM_OBJECT_VALIDATE_1_ARG(window_handle); | |
503 *window_handle = delegate_->GetTargetForNativeAccessibilityEvent(); | |
504 return *window_handle ? S_OK : S_FALSE; | |
505 } | |
506 | |
507 STDMETHODIMP AXPlatformNodeWin::get_relationTargetsOfType( | |
508 BSTR type_bstr, | |
509 long max_targets, | |
510 IUnknown ***targets, | |
511 long *n_targets) { | |
512 COM_OBJECT_VALIDATE_2_ARGS(targets, n_targets); | |
513 | |
514 *n_targets = 0; | |
515 *targets = nullptr; | |
516 | |
517 // Only respond to requests for relations of type "alerts". | |
518 base::string16 type(type_bstr); | |
519 if (type != L"alerts") | |
520 return S_FALSE; | |
521 | |
522 // Collect all of the objects that have had an alert fired on them that | |
523 // are a descendant of this object. | |
524 std::vector<AXPlatformNodeWin*> alert_targets; | |
525 for (auto iter = g_alert_targets.Get().begin(); | |
526 iter != g_alert_targets.Get().end(); | |
527 ++iter) { | |
528 AXPlatformNodeWin* target = *iter; | |
529 if (IsDescendant(target)) | |
530 alert_targets.push_back(target); | |
531 } | |
532 | |
533 long count = static_cast<long>(alert_targets.size()); | |
534 if (count == 0) | |
535 return S_FALSE; | |
536 | |
537 // Don't return more targets than max_targets - but note that the caller | |
538 // is allowed to specify max_targets=0 to mean no limit. | |
539 if (max_targets > 0 && count > max_targets) | |
540 count = max_targets; | |
541 | |
542 // Return the number of targets. | |
543 *n_targets = count; | |
544 | |
545 // Allocate COM memory for the result array and populate it. | |
546 *targets = static_cast<IUnknown**>( | |
547 CoTaskMemAlloc(count * sizeof(IUnknown*))); | |
548 for (long i = 0; i < count; ++i) { | |
549 (*targets)[i] = static_cast<IAccessible*>(alert_targets[i]); | |
550 (*targets)[i]->AddRef(); | |
551 } | |
552 return S_OK; | |
553 } | |
554 | |
555 STDMETHODIMP AXPlatformNodeWin::get_attributes(BSTR* attributes) { | |
556 COM_OBJECT_VALIDATE_1_ARG(attributes); | |
557 base::string16 attributes_str; | |
558 | |
559 // Text fields need to report the attribute "text-model:a1" to instruct | |
560 // screen readers to use IAccessible2 APIs to handle text editing in this | |
561 // object (as opposed to treating it like a native Windows text box). | |
562 // The text-model:a1 attribute is documented here: | |
563 // http://www.linuxfoundation.org/collaborate/workgroups/accessibility/ia2/ia2
_implementation_guide | |
564 if (GetData().role == ui::AX_ROLE_TEXT_FIELD) { | |
565 attributes_str = L"text-model:a1;"; | |
566 } | |
567 | |
568 *attributes = SysAllocString(attributes_str.c_str()); | |
569 DCHECK(*attributes); | |
570 return S_OK; | |
571 } | |
572 | |
573 // | |
574 // IAccessibleText | |
575 // | |
576 | |
577 STDMETHODIMP AXPlatformNodeWin::get_nCharacters(LONG* n_characters) { | |
578 COM_OBJECT_VALIDATE_1_ARG(n_characters); | |
579 base::string16 text = TextForIAccessibleText(); | |
580 *n_characters = static_cast<LONG>(text.size()); | |
581 | |
582 return S_OK; | |
583 } | |
584 | |
585 STDMETHODIMP AXPlatformNodeWin::get_caretOffset(LONG* offset) { | |
586 COM_OBJECT_VALIDATE_1_ARG(offset); | |
587 *offset = static_cast<LONG>(GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END)); | |
588 return S_OK; | |
589 } | |
590 | |
591 STDMETHODIMP AXPlatformNodeWin::get_nSelections(LONG* n_selections) { | |
592 COM_OBJECT_VALIDATE_1_ARG(n_selections); | |
593 int sel_start = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START); | |
594 int sel_end = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END); | |
595 if (sel_start != sel_end) | |
596 *n_selections = 1; | |
597 else | |
598 *n_selections = 0; | |
599 return S_OK; | |
600 } | |
601 | |
602 STDMETHODIMP AXPlatformNodeWin::get_selection(LONG selection_index, | |
603 LONG* start_offset, | |
604 LONG* end_offset) { | |
605 COM_OBJECT_VALIDATE_2_ARGS(start_offset, end_offset); | |
606 if (selection_index != 0) | |
607 return E_INVALIDARG; | |
608 | |
609 *start_offset = static_cast<LONG>( | |
610 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START)); | |
611 *end_offset = static_cast<LONG>( | |
612 GetIntAttribute(ui::AX_ATTR_TEXT_SEL_END)); | |
613 return S_OK; | |
614 } | |
615 | |
616 STDMETHODIMP AXPlatformNodeWin::get_text(LONG start_offset, | |
617 LONG end_offset, | |
618 BSTR* text) { | |
619 COM_OBJECT_VALIDATE_1_ARG(text); | |
620 int sel_end = GetIntAttribute(ui::AX_ATTR_TEXT_SEL_START); | |
621 base::string16 text_str = TextForIAccessibleText(); | |
622 LONG len = static_cast<LONG>(text_str.size()); | |
623 | |
624 if (start_offset == IA2_TEXT_OFFSET_LENGTH) { | |
625 start_offset = len; | |
626 } else if (start_offset == IA2_TEXT_OFFSET_CARET) { | |
627 start_offset = static_cast<LONG>(sel_end); | |
628 } | |
629 if (end_offset == IA2_TEXT_OFFSET_LENGTH) { | |
630 end_offset = static_cast<LONG>(text_str.size()); | |
631 } else if (end_offset == IA2_TEXT_OFFSET_CARET) { | |
632 end_offset = static_cast<LONG>(sel_end); | |
633 } | |
634 | |
635 // The spec allows the arguments to be reversed. | |
636 if (start_offset > end_offset) { | |
637 LONG tmp = start_offset; | |
638 start_offset = end_offset; | |
639 end_offset = tmp; | |
640 } | |
641 | |
642 // The spec does not allow the start or end offsets to be out or range; | |
643 // we must return an error if so. | |
644 if (start_offset < 0) | |
645 return E_INVALIDARG; | |
646 if (end_offset > len) | |
647 return E_INVALIDARG; | |
648 | |
649 base::string16 substr = | |
650 text_str.substr(start_offset, end_offset - start_offset); | |
651 if (substr.empty()) | |
652 return S_FALSE; | |
653 | |
654 *text = SysAllocString(substr.c_str()); | |
655 DCHECK(*text); | |
656 return S_OK; | |
657 } | |
658 | |
659 STDMETHODIMP AXPlatformNodeWin::get_textAtOffset( | |
660 LONG offset, | |
661 enum IA2TextBoundaryType boundary_type, | |
662 LONG* start_offset, LONG* end_offset, | |
663 BSTR* text) { | |
664 COM_OBJECT_VALIDATE_3_ARGS(start_offset, end_offset, text); | |
665 // The IAccessible2 spec says we don't have to implement the "sentence" | |
666 // boundary type, we can just let the screen reader handle it. | |
667 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
668 *start_offset = 0; | |
669 *end_offset = 0; | |
670 *text = nullptr; | |
671 return S_FALSE; | |
672 } | |
673 | |
674 const base::string16& text_str = TextForIAccessibleText(); | |
675 | |
676 *start_offset = FindBoundary( | |
677 text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); | |
678 *end_offset = FindBoundary( | |
679 text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); | |
680 return get_text(*start_offset, *end_offset, text); | |
681 } | |
682 | |
683 STDMETHODIMP AXPlatformNodeWin::get_textBeforeOffset( | |
684 LONG offset, | |
685 enum IA2TextBoundaryType boundary_type, | |
686 LONG* start_offset, LONG* end_offset, | |
687 BSTR* text) { | |
688 if (!start_offset || !end_offset || !text) | |
689 return E_INVALIDARG; | |
690 | |
691 // The IAccessible2 spec says we don't have to implement the "sentence" | |
692 // boundary type, we can just let the screenreader handle it. | |
693 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
694 *start_offset = 0; | |
695 *end_offset = 0; | |
696 *text = nullptr; | |
697 return S_FALSE; | |
698 } | |
699 | |
700 const base::string16& text_str = TextForIAccessibleText(); | |
701 | |
702 *start_offset = FindBoundary( | |
703 text_str, boundary_type, offset, ui::BACKWARDS_DIRECTION); | |
704 *end_offset = offset; | |
705 return get_text(*start_offset, *end_offset, text); | |
706 } | |
707 | |
708 STDMETHODIMP AXPlatformNodeWin::get_textAfterOffset( | |
709 LONG offset, | |
710 enum IA2TextBoundaryType boundary_type, | |
711 LONG* start_offset, LONG* end_offset, | |
712 BSTR* text) { | |
713 if (!start_offset || !end_offset || !text) | |
714 return E_INVALIDARG; | |
715 | |
716 // The IAccessible2 spec says we don't have to implement the "sentence" | |
717 // boundary type, we can just let the screenreader handle it. | |
718 if (boundary_type == IA2_TEXT_BOUNDARY_SENTENCE) { | |
719 *start_offset = 0; | |
720 *end_offset = 0; | |
721 *text = nullptr; | |
722 return S_FALSE; | |
723 } | |
724 | |
725 const base::string16& text_str = TextForIAccessibleText(); | |
726 | |
727 *start_offset = offset; | |
728 *end_offset = FindBoundary( | |
729 text_str, boundary_type, offset, ui::FORWARDS_DIRECTION); | |
730 return get_text(*start_offset, *end_offset, text); | |
731 } | |
732 | |
733 STDMETHODIMP AXPlatformNodeWin::get_offsetAtPoint( | |
734 LONG x, LONG y, enum IA2CoordinateType coord_type, LONG* offset) { | |
735 COM_OBJECT_VALIDATE_1_ARG(offset); | |
736 // We don't support this method, but we have to return something | |
737 // rather than E_NOTIMPL or screen readers will complain. | |
738 *offset = 0; | |
739 return S_OK; | |
740 } | |
741 | |
742 // | |
743 // IServiceProvider implementation. | |
744 // | |
745 | |
746 STDMETHODIMP AXPlatformNodeWin::QueryService( | |
747 REFGUID guidService, REFIID riid, void** object) { | |
748 COM_OBJECT_VALIDATE_1_ARG(object); | |
749 if (guidService == IID_IAccessible || | |
750 guidService == IID_IAccessible2 || | |
751 guidService == IID_IAccessible2_2 || | |
752 guidService == IID_IAccessibleText) { | |
753 return QueryInterface(riid, object); | |
754 } | |
755 | |
756 *object = nullptr; | |
757 return E_FAIL; | |
758 } | |
759 | |
760 // | |
761 // Private member functions. | |
762 // | |
763 | |
764 bool AXPlatformNodeWin::IsValidId(const VARIANT& child) const { | |
765 // Since we have a separate IAccessible COM object for each node, we only | |
766 // support the CHILDID_SELF id. | |
767 return (VT_I4 == child.vt) && (CHILDID_SELF == child.lVal); | |
768 } | |
769 | |
770 int AXPlatformNodeWin::MSAARole() { | |
771 switch (GetData().role) { | |
772 case ui::AX_ROLE_ALERT: | |
773 return ROLE_SYSTEM_ALERT; | |
774 case ui::AX_ROLE_APPLICATION: | |
775 return ROLE_SYSTEM_APPLICATION; | |
776 case ui::AX_ROLE_BUTTON_DROP_DOWN: | |
777 return ROLE_SYSTEM_BUTTONDROPDOWN; | |
778 case ui::AX_ROLE_POP_UP_BUTTON: | |
779 return ROLE_SYSTEM_BUTTONMENU; | |
780 case ui::AX_ROLE_CHECK_BOX: | |
781 return ROLE_SYSTEM_CHECKBUTTON; | |
782 case ui::AX_ROLE_COMBO_BOX: | |
783 return ROLE_SYSTEM_COMBOBOX; | |
784 case ui::AX_ROLE_DIALOG: | |
785 return ROLE_SYSTEM_DIALOG; | |
786 case ui::AX_ROLE_GROUP: | |
787 return ROLE_SYSTEM_GROUPING; | |
788 case ui::AX_ROLE_IMAGE: | |
789 return ROLE_SYSTEM_GRAPHIC; | |
790 case ui::AX_ROLE_LINK: | |
791 return ROLE_SYSTEM_LINK; | |
792 case ui::AX_ROLE_LOCATION_BAR: | |
793 return ROLE_SYSTEM_GROUPING; | |
794 case ui::AX_ROLE_MENU_BAR: | |
795 return ROLE_SYSTEM_MENUBAR; | |
796 case ui::AX_ROLE_MENU_ITEM: | |
797 return ROLE_SYSTEM_MENUITEM; | |
798 case ui::AX_ROLE_MENU_LIST_POPUP: | |
799 return ROLE_SYSTEM_MENUPOPUP; | |
800 case ui::AX_ROLE_TREE: | |
801 return ROLE_SYSTEM_OUTLINE; | |
802 case ui::AX_ROLE_TREE_ITEM: | |
803 return ROLE_SYSTEM_OUTLINEITEM; | |
804 case ui::AX_ROLE_TAB: | |
805 return ROLE_SYSTEM_PAGETAB; | |
806 case ui::AX_ROLE_TAB_LIST: | |
807 return ROLE_SYSTEM_PAGETABLIST; | |
808 case ui::AX_ROLE_PANE: | |
809 return ROLE_SYSTEM_PANE; | |
810 case ui::AX_ROLE_PROGRESS_INDICATOR: | |
811 return ROLE_SYSTEM_PROGRESSBAR; | |
812 case ui::AX_ROLE_BUTTON: | |
813 return ROLE_SYSTEM_PUSHBUTTON; | |
814 case ui::AX_ROLE_RADIO_BUTTON: | |
815 return ROLE_SYSTEM_RADIOBUTTON; | |
816 case ui::AX_ROLE_SCROLL_BAR: | |
817 return ROLE_SYSTEM_SCROLLBAR; | |
818 case ui::AX_ROLE_SPLITTER: | |
819 return ROLE_SYSTEM_SEPARATOR; | |
820 case ui::AX_ROLE_SLIDER: | |
821 return ROLE_SYSTEM_SLIDER; | |
822 case ui::AX_ROLE_STATIC_TEXT: | |
823 return ROLE_SYSTEM_STATICTEXT; | |
824 case ui::AX_ROLE_TEXT_FIELD: | |
825 return ROLE_SYSTEM_TEXT; | |
826 case ui::AX_ROLE_TITLE_BAR: | |
827 return ROLE_SYSTEM_TITLEBAR; | |
828 case ui::AX_ROLE_TOOLBAR: | |
829 return ROLE_SYSTEM_TOOLBAR; | |
830 case ui::AX_ROLE_WEB_VIEW: | |
831 return ROLE_SYSTEM_GROUPING; | |
832 case ui::AX_ROLE_WINDOW: | |
833 return ROLE_SYSTEM_WINDOW; | |
834 case ui::AX_ROLE_CLIENT: | |
835 default: | |
836 // This is the default role for MSAA. | |
837 return ROLE_SYSTEM_CLIENT; | |
838 } | |
839 } | |
840 | |
841 int AXPlatformNodeWin::MSAAState() { | |
842 uint32 state = GetData().state; | |
843 | |
844 int msaa_state = 0; | |
845 if (state & (1 << ui::AX_STATE_CHECKED)) | |
846 msaa_state |= STATE_SYSTEM_CHECKED; | |
847 if (state & (1 << ui::AX_STATE_COLLAPSED)) | |
848 msaa_state |= STATE_SYSTEM_COLLAPSED; | |
849 if (state & (1 << ui::AX_STATE_DEFAULT)) | |
850 msaa_state |= STATE_SYSTEM_DEFAULT; | |
851 if (state & (1 << ui::AX_STATE_EXPANDED)) | |
852 msaa_state |= STATE_SYSTEM_EXPANDED; | |
853 if (state & (1 << ui::AX_STATE_FOCUSABLE)) | |
854 msaa_state |= STATE_SYSTEM_FOCUSABLE; | |
855 if (state & (1 << ui::AX_STATE_FOCUSED)) | |
856 msaa_state |= STATE_SYSTEM_FOCUSED; | |
857 if (state & (1 << ui::AX_STATE_HASPOPUP)) | |
858 msaa_state |= STATE_SYSTEM_HASPOPUP; | |
859 if (state & (1 << ui::AX_STATE_HOVERED)) | |
860 msaa_state |= STATE_SYSTEM_HOTTRACKED; | |
861 if (state & (1 << ui::AX_STATE_INVISIBLE)) | |
862 msaa_state |= STATE_SYSTEM_INVISIBLE; | |
863 if (state & (1 << ui::AX_STATE_LINKED)) | |
864 msaa_state |= STATE_SYSTEM_LINKED; | |
865 if (state & (1 << ui::AX_STATE_OFFSCREEN)) | |
866 msaa_state |= STATE_SYSTEM_OFFSCREEN; | |
867 if (state & (1 << ui::AX_STATE_PRESSED)) | |
868 msaa_state |= STATE_SYSTEM_PRESSED; | |
869 if (state & (1 << ui::AX_STATE_PROTECTED)) | |
870 msaa_state |= STATE_SYSTEM_PROTECTED; | |
871 if (state & (1 << ui::AX_STATE_READ_ONLY)) | |
872 msaa_state |= STATE_SYSTEM_READONLY; | |
873 if (state & (1 << ui::AX_STATE_SELECTABLE)) | |
874 msaa_state |= STATE_SYSTEM_SELECTABLE; | |
875 if (state & (1 << ui::AX_STATE_SELECTED)) | |
876 msaa_state |= STATE_SYSTEM_SELECTED; | |
877 if (state & (1 << ui::AX_STATE_DISABLED)) | |
878 msaa_state |= STATE_SYSTEM_UNAVAILABLE; | |
879 | |
880 return msaa_state; | |
881 } | |
882 | |
883 int AXPlatformNodeWin::MSAAEvent(ui::AXEvent event) { | |
884 switch (event) { | |
885 case ui::AX_EVENT_ALERT: | |
886 return EVENT_SYSTEM_ALERT; | |
887 case ui::AX_EVENT_FOCUS: | |
888 return EVENT_OBJECT_FOCUS; | |
889 case ui::AX_EVENT_MENU_START: | |
890 return EVENT_SYSTEM_MENUSTART; | |
891 case ui::AX_EVENT_MENU_END: | |
892 return EVENT_SYSTEM_MENUEND; | |
893 case ui::AX_EVENT_MENU_POPUP_START: | |
894 return EVENT_SYSTEM_MENUPOPUPSTART; | |
895 case ui::AX_EVENT_MENU_POPUP_END: | |
896 return EVENT_SYSTEM_MENUPOPUPEND; | |
897 case ui::AX_EVENT_SELECTION: | |
898 return EVENT_OBJECT_SELECTION; | |
899 case ui::AX_EVENT_SELECTION_ADD: | |
900 return EVENT_OBJECT_SELECTIONADD; | |
901 case ui::AX_EVENT_SELECTION_REMOVE: | |
902 return EVENT_OBJECT_SELECTIONREMOVE; | |
903 case ui::AX_EVENT_TEXT_CHANGED: | |
904 return EVENT_OBJECT_NAMECHANGE; | |
905 case ui::AX_EVENT_TEXT_SELECTION_CHANGED: | |
906 return IA2_EVENT_TEXT_CARET_MOVED; | |
907 case ui::AX_EVENT_VALUE_CHANGED: | |
908 return EVENT_OBJECT_VALUECHANGE; | |
909 default: | |
910 return -1; | |
911 } | |
912 } | |
913 | |
914 HRESULT AXPlatformNodeWin::GetStringAttributeAsBstr( | |
915 ui::AXStringAttribute attribute, | |
916 BSTR* value_bstr) const { | |
917 base::string16 str; | |
918 | |
919 if (!GetString16Attribute(attribute, &str)) | |
920 return S_FALSE; | |
921 | |
922 if (str.empty()) | |
923 return S_FALSE; | |
924 | |
925 *value_bstr = SysAllocString(str.c_str()); | |
926 DCHECK(*value_bstr); | |
927 | |
928 return S_OK; | |
929 } | |
930 | |
931 void AXPlatformNodeWin::AddAlertTarget() { | |
932 g_alert_targets.Get().insert(this); | |
933 } | |
934 | |
935 void AXPlatformNodeWin::RemoveAlertTarget() { | |
936 if (g_alert_targets.Get().find(this) != g_alert_targets.Get().end()) | |
937 g_alert_targets.Get().erase(this); | |
938 } | |
939 | |
940 base::string16 AXPlatformNodeWin::TextForIAccessibleText() { | |
941 if (GetData().role == ui::AX_ROLE_TEXT_FIELD) | |
942 return GetString16Attribute(ui::AX_ATTR_VALUE); | |
943 else | |
944 return GetString16Attribute(ui::AX_ATTR_NAME); | |
945 } | |
946 | |
947 void AXPlatformNodeWin::HandleSpecialTextOffset( | |
948 const base::string16& text, LONG* offset) { | |
949 if (*offset == IA2_TEXT_OFFSET_LENGTH) { | |
950 *offset = static_cast<LONG>(text.size()); | |
951 } else if (*offset == IA2_TEXT_OFFSET_CARET) { | |
952 get_caretOffset(offset); | |
953 } | |
954 } | |
955 | |
956 ui::TextBoundaryType AXPlatformNodeWin::IA2TextBoundaryToTextBoundary( | |
957 IA2TextBoundaryType ia2_boundary) { | |
958 switch(ia2_boundary) { | |
959 case IA2_TEXT_BOUNDARY_CHAR: return ui::CHAR_BOUNDARY; | |
960 case IA2_TEXT_BOUNDARY_WORD: return ui::WORD_BOUNDARY; | |
961 case IA2_TEXT_BOUNDARY_LINE: return ui::LINE_BOUNDARY; | |
962 case IA2_TEXT_BOUNDARY_SENTENCE: return ui::SENTENCE_BOUNDARY; | |
963 case IA2_TEXT_BOUNDARY_PARAGRAPH: return ui::PARAGRAPH_BOUNDARY; | |
964 case IA2_TEXT_BOUNDARY_ALL: return ui::ALL_BOUNDARY; | |
965 default: | |
966 NOTREACHED(); | |
967 return ui::CHAR_BOUNDARY; | |
968 } | |
969 } | |
970 | |
971 LONG AXPlatformNodeWin::FindBoundary( | |
972 const base::string16& text, | |
973 IA2TextBoundaryType ia2_boundary, | |
974 LONG start_offset, | |
975 ui::TextBoundaryDirection direction) { | |
976 HandleSpecialTextOffset(text, &start_offset); | |
977 ui::TextBoundaryType boundary = IA2TextBoundaryToTextBoundary(ia2_boundary); | |
978 std::vector<int32> line_breaks; | |
979 return static_cast<LONG>(ui::FindAccessibleTextBoundary( | |
980 text, line_breaks, boundary, start_offset, direction)); | |
981 } | |
982 | |
983 } // namespace ui | |
OLD | NEW |