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