1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "Accessible2.h"
8 #include "ProxyAccessible.h"
9 #include "ia2AccessibleRelation.h"
10 #include "ia2AccessibleValue.h"
11 #include "IGeckoCustom.h"
12 #include "mozilla/a11y/DocAccessibleParent.h"
13 #include "DocAccessible.h"
14 #include "mozilla/a11y/DocManager.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/BrowserParent.h"
17 #include "mozilla/Unused.h"
18 #include "mozilla/a11y/Platform.h"
19 #include "RelationType.h"
20 #include "mozilla/a11y/Role.h"
21
22 #include <comutil.h>
23
24 static const VARIANT kChildIdSelf = {{{VT_I4}}};
25
26 namespace mozilla {
27 namespace a11y {
28
GetCOMInterface(void ** aOutAccessible) const29 bool ProxyAccessible::GetCOMInterface(void** aOutAccessible) const {
30 if (!aOutAccessible) {
31 return false;
32 }
33
34 if (!mCOMProxy && mSafeToRecurse) {
35 // See if we can lazily obtain a COM proxy
36 AccessibleWrap* wrap = WrapperFor(this);
37 bool isDefunct = false;
38 ProxyAccessible* thisPtr = const_cast<ProxyAccessible*>(this);
39 // NB: Don't pass CHILDID_SELF here, use the absolute MSAA ID. Otherwise
40 // GetIAccessibleFor will recurse into this function and we will just
41 // overflow the stack.
42 VARIANT realId = {{{VT_I4}}};
43 realId.ulVal = wrap->GetExistingID();
44 MOZ_DIAGNOSTIC_ASSERT(realId.ulVal != CHILDID_SELF);
45 thisPtr->mCOMProxy = wrap->GetIAccessibleFor(realId, &isDefunct);
46 }
47
48 RefPtr<IAccessible> addRefed = mCOMProxy;
49 addRefed.forget(aOutAccessible);
50 return !!mCOMProxy;
51 }
52
53 /**
54 * Specializations of this template map an IAccessible type to its IID
55 */
56 template <typename Interface>
57 struct InterfaceIID {};
58
59 template <>
60 struct InterfaceIID<IAccessibleValue> {
Valuemozilla::a11y::InterfaceIID61 static REFIID Value() { return IID_IAccessibleValue; }
62 };
63
64 template <>
65 struct InterfaceIID<IAccessibleText> {
Valuemozilla::a11y::InterfaceIID66 static REFIID Value() { return IID_IAccessibleText; }
67 };
68
69 template <>
70 struct InterfaceIID<IAccessibleHyperlink> {
Valuemozilla::a11y::InterfaceIID71 static REFIID Value() { return IID_IAccessibleHyperlink; }
72 };
73
74 template <>
75 struct InterfaceIID<IGeckoCustom> {
Valuemozilla::a11y::InterfaceIID76 static REFIID Value() { return IID_IGeckoCustom; }
77 };
78
79 template <>
80 struct InterfaceIID<IAccessible2_2> {
Valuemozilla::a11y::InterfaceIID81 static REFIID Value() { return IID_IAccessible2_2; }
82 };
83
84 /**
85 * Get the COM proxy for this proxy accessible and QueryInterface it with the
86 * correct IID
87 */
88 template <typename Interface>
QueryInterface(const ProxyAccessible * aProxy)89 static already_AddRefed<Interface> QueryInterface(
90 const ProxyAccessible* aProxy) {
91 RefPtr<IAccessible> acc;
92 if (!aProxy->GetCOMInterface((void**)getter_AddRefs(acc))) {
93 return nullptr;
94 }
95
96 RefPtr<Interface> acc2;
97 if (FAILED(acc->QueryInterface(InterfaceIID<Interface>::Value(),
98 (void**)getter_AddRefs(acc2)))) {
99 return nullptr;
100 }
101
102 return acc2.forget();
103 }
104
GetProxyFor(DocAccessibleParent * aDoc,IUnknown * aCOMProxy)105 static ProxyAccessible* GetProxyFor(DocAccessibleParent* aDoc,
106 IUnknown* aCOMProxy) {
107 RefPtr<IGeckoCustom> custom;
108 if (FAILED(aCOMProxy->QueryInterface(IID_IGeckoCustom,
109 (void**)getter_AddRefs(custom)))) {
110 return nullptr;
111 }
112
113 uint64_t id;
114 if (FAILED(custom->get_ID(&id))) {
115 return nullptr;
116 }
117
118 return aDoc->GetAccessible(id);
119 }
120
Name(nsString & aName) const121 uint32_t ProxyAccessible::Name(nsString& aName) const {
122 /* The return values here exist only to match behvaiour required
123 * by the header declaration of this function. On Mac, we'd like
124 * to return the associated ENameValueFlag, but we don't have
125 * access to that here, so we return a dummy eNameOK value instead.
126 */
127 aName.Truncate();
128 RefPtr<IAccessible> acc;
129 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
130 return eNameOK;
131 }
132
133 BSTR result;
134 HRESULT hr = acc->get_accName(kChildIdSelf, &result);
135 _bstr_t resultWrap(result, false);
136 if (FAILED(hr)) {
137 return eNameOK;
138 }
139 aName = (wchar_t*)resultWrap;
140 return eNameOK;
141 }
142
Value(nsString & aValue) const143 void ProxyAccessible::Value(nsString& aValue) const {
144 aValue.Truncate();
145 RefPtr<IAccessible> acc;
146 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
147 return;
148 }
149
150 BSTR result;
151 HRESULT hr = acc->get_accValue(kChildIdSelf, &result);
152 _bstr_t resultWrap(result, false);
153 if (FAILED(hr)) {
154 return;
155 }
156 aValue = (wchar_t*)resultWrap;
157 }
158
Step()159 double ProxyAccessible::Step() {
160 RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
161 if (!custom) {
162 return 0;
163 }
164
165 double increment;
166 HRESULT hr = custom->get_minimumIncrement(&increment);
167 if (FAILED(hr)) {
168 return 0;
169 }
170
171 return increment;
172 }
173
Description(nsString & aDesc) const174 void ProxyAccessible::Description(nsString& aDesc) const {
175 aDesc.Truncate();
176 RefPtr<IAccessible> acc;
177 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
178 return;
179 }
180
181 BSTR result;
182 HRESULT hr = acc->get_accDescription(kChildIdSelf, &result);
183 _bstr_t resultWrap(result, false);
184 if (FAILED(hr)) {
185 return;
186 }
187 aDesc = (wchar_t*)resultWrap;
188 }
189
State() const190 uint64_t ProxyAccessible::State() const {
191 RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
192 if (!custom) {
193 return 0;
194 }
195
196 uint64_t state;
197 HRESULT hr = custom->get_mozState(&state);
198 if (FAILED(hr)) {
199 return 0;
200 }
201 return state;
202 }
203
Bounds()204 nsIntRect ProxyAccessible::Bounds() {
205 nsIntRect rect;
206
207 RefPtr<IAccessible> acc;
208 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
209 return rect;
210 }
211
212 long left;
213 long top;
214 long width;
215 long height;
216 HRESULT hr = acc->accLocation(&left, &top, &width, &height, kChildIdSelf);
217 if (FAILED(hr)) {
218 return rect;
219 }
220 rect.SetRect(left, top, width, height);
221 return rect;
222 }
223
BoundsInCSSPixels()224 nsIntRect ProxyAccessible::BoundsInCSSPixels() {
225 RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
226 if (!custom) {
227 return nsIntRect();
228 }
229
230 nsIntRect rect;
231 Unused << custom->get_boundsInCSSPixels(&rect.x, &rect.y, &rect.width,
232 &rect.height);
233 return rect;
234 }
235
Language(nsString & aLocale)236 void ProxyAccessible::Language(nsString& aLocale) {
237 aLocale.Truncate();
238
239 RefPtr<IAccessible> acc;
240 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
241 return;
242 }
243
244 RefPtr<IAccessible2> acc2;
245 if (FAILED(acc->QueryInterface(IID_IAccessible2,
246 (void**)getter_AddRefs(acc2)))) {
247 return;
248 }
249
250 IA2Locale locale;
251 HRESULT hr = acc2->get_locale(&locale);
252
253 _bstr_t langWrap(locale.language, false);
254 _bstr_t countryWrap(locale.country, false);
255 _bstr_t variantWrap(locale.variant, false);
256
257 if (FAILED(hr)) {
258 return;
259 }
260
261 // The remaining code should essentially be the inverse of the
262 // ia2Accessible::get_locale conversion to IA2Locale.
263
264 if (!!variantWrap) {
265 aLocale = (wchar_t*)variantWrap;
266 return;
267 }
268
269 if (!!langWrap) {
270 aLocale = (wchar_t*)langWrap;
271 if (!!countryWrap) {
272 aLocale += L"-";
273 aLocale += (wchar_t*)countryWrap;
274 }
275 }
276 }
277
IsEscapedChar(const wchar_t c)278 static bool IsEscapedChar(const wchar_t c) {
279 return c == L'\\' || c == L':' || c == ',' || c == '=' || c == ';';
280 }
281
ConvertBSTRAttributesToArray(const nsAString & aStr,nsTArray<Attribute> * aAttrs)282 static bool ConvertBSTRAttributesToArray(const nsAString& aStr,
283 nsTArray<Attribute>* aAttrs) {
284 if (!aAttrs) {
285 return false;
286 }
287
288 enum { eName = 0, eValue = 1, eNumStates } state;
289 nsAutoString tokens[eNumStates];
290 auto itr = aStr.BeginReading(), end = aStr.EndReading();
291
292 state = eName;
293 while (itr != end) {
294 switch (*itr) {
295 case L'\\':
296 // Skip the backslash so that we're looking at the escaped char
297 ++itr;
298 if (itr == end || !IsEscapedChar(*itr)) {
299 // Invalid state
300 return false;
301 }
302 break;
303 case L':':
304 if (state != eName) {
305 // Bad, should be looking at name
306 return false;
307 }
308 state = eValue;
309 ++itr;
310 continue;
311 case L';':
312 if (state != eValue) {
313 // Bad, should be looking at value
314 return false;
315 }
316 state = eName;
317 aAttrs->AppendElement(
318 Attribute(NS_ConvertUTF16toUTF8(tokens[eName]), tokens[eValue]));
319 tokens[eName].Truncate();
320 tokens[eValue].Truncate();
321 ++itr;
322 continue;
323 default:
324 break;
325 }
326 tokens[state] += *itr;
327 ++itr;
328 }
329 return true;
330 }
331
Attributes(nsTArray<Attribute> * aAttrs) const332 void ProxyAccessible::Attributes(nsTArray<Attribute>* aAttrs) const {
333 aAttrs->Clear();
334
335 RefPtr<IAccessible> acc;
336 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
337 return;
338 }
339
340 RefPtr<IAccessible2> acc2;
341 if (FAILED(acc->QueryInterface(IID_IAccessible2,
342 (void**)getter_AddRefs(acc2)))) {
343 return;
344 }
345
346 BSTR attrs;
347 HRESULT hr = acc2->get_attributes(&attrs);
348 _bstr_t attrsWrap(attrs, false);
349 if (FAILED(hr)) {
350 return;
351 }
352
353 ConvertBSTRAttributesToArray(
354 nsDependentString((wchar_t*)attrs, attrsWrap.length()), aAttrs);
355 }
356
RelationByType(RelationType aType) const357 nsTArray<ProxyAccessible*> ProxyAccessible::RelationByType(
358 RelationType aType) const {
359 RefPtr<IAccessible2_2> acc = QueryInterface<IAccessible2_2>(this);
360 if (!acc) {
361 return nsTArray<ProxyAccessible*>();
362 }
363
364 _bstr_t relationType;
365 for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
366 if (aType == sRelationTypePairs[idx].first) {
367 relationType = sRelationTypePairs[idx].second;
368 break;
369 }
370 }
371
372 if (!relationType) {
373 return nsTArray<ProxyAccessible*>();
374 }
375
376 IUnknown** targets;
377 long nTargets = 0;
378 HRESULT hr =
379 acc->get_relationTargetsOfType(relationType, 0, &targets, &nTargets);
380 if (FAILED(hr)) {
381 return nsTArray<ProxyAccessible*>();
382 }
383
384 nsTArray<ProxyAccessible*> proxies;
385 for (long idx = 0; idx < nTargets; idx++) {
386 IUnknown* target = targets[idx];
387 proxies.AppendElement(GetProxyFor(Document(), target));
388 target->Release();
389 }
390 CoTaskMemFree(targets);
391
392 return proxies;
393 }
394
CurValue()395 double ProxyAccessible::CurValue() {
396 RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
397 if (!acc) {
398 return UnspecifiedNaN<double>();
399 }
400
401 VARIANT currentValue;
402 HRESULT hr = acc->get_currentValue(¤tValue);
403 if (FAILED(hr) || currentValue.vt != VT_R8) {
404 return UnspecifiedNaN<double>();
405 }
406
407 return currentValue.dblVal;
408 }
409
SetCurValue(double aValue)410 bool ProxyAccessible::SetCurValue(double aValue) {
411 RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
412 if (!acc) {
413 return false;
414 }
415
416 VARIANT currentValue;
417 VariantInit(¤tValue);
418 currentValue.vt = VT_R8;
419 currentValue.dblVal = aValue;
420 HRESULT hr = acc->setCurrentValue(currentValue);
421 return SUCCEEDED(hr);
422 }
423
MinValue()424 double ProxyAccessible::MinValue() {
425 RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
426 if (!acc) {
427 return UnspecifiedNaN<double>();
428 }
429
430 VARIANT minimumValue;
431 HRESULT hr = acc->get_minimumValue(&minimumValue);
432 if (FAILED(hr) || minimumValue.vt != VT_R8) {
433 return UnspecifiedNaN<double>();
434 }
435
436 return minimumValue.dblVal;
437 }
438
MaxValue()439 double ProxyAccessible::MaxValue() {
440 RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
441 if (!acc) {
442 return UnspecifiedNaN<double>();
443 }
444
445 VARIANT maximumValue;
446 HRESULT hr = acc->get_maximumValue(&maximumValue);
447 if (FAILED(hr) || maximumValue.vt != VT_R8) {
448 return UnspecifiedNaN<double>();
449 }
450
451 return maximumValue.dblVal;
452 }
453
GetIA2TextBoundary(AccessibleTextBoundary aGeckoBoundaryType)454 static IA2TextBoundaryType GetIA2TextBoundary(
455 AccessibleTextBoundary aGeckoBoundaryType) {
456 switch (aGeckoBoundaryType) {
457 case nsIAccessibleText::BOUNDARY_CHAR:
458 return IA2_TEXT_BOUNDARY_CHAR;
459 case nsIAccessibleText::BOUNDARY_WORD_START:
460 return IA2_TEXT_BOUNDARY_WORD;
461 case nsIAccessibleText::BOUNDARY_LINE_START:
462 return IA2_TEXT_BOUNDARY_LINE;
463 default:
464 MOZ_CRASH();
465 }
466 }
467
OffsetAtPoint(int32_t aX,int32_t aY,uint32_t aCoordinateType)468 int32_t ProxyAccessible::OffsetAtPoint(int32_t aX, int32_t aY,
469 uint32_t aCoordinateType) {
470 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
471 if (!acc) {
472 return -1;
473 }
474
475 IA2CoordinateType coordType;
476 if (aCoordinateType ==
477 nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) {
478 coordType = IA2_COORDTYPE_SCREEN_RELATIVE;
479 } else if (aCoordinateType ==
480 nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE) {
481 coordType = IA2_COORDTYPE_PARENT_RELATIVE;
482 } else {
483 MOZ_CRASH("unsupported coord type");
484 }
485
486 long offset;
487 HRESULT hr = acc->get_offsetAtPoint(
488 static_cast<long>(aX), static_cast<long>(aY), coordType, &offset);
489 if (FAILED(hr)) {
490 return -1;
491 }
492
493 return static_cast<int32_t>(offset);
494 }
495
TextSubstring(int32_t aStartOffset,int32_t aEndOffset,nsString & aText) const496 bool ProxyAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
497 nsString& aText) const {
498 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
499 if (!acc) {
500 return false;
501 }
502
503 BSTR result;
504 HRESULT hr = acc->get_text(static_cast<long>(aStartOffset),
505 static_cast<long>(aEndOffset), &result);
506 if (FAILED(hr)) {
507 return false;
508 }
509
510 _bstr_t resultWrap(result, false);
511 aText = (wchar_t*)result;
512
513 return true;
514 }
515
GetTextBeforeOffset(int32_t aOffset,AccessibleTextBoundary aBoundaryType,nsString & aText,int32_t * aStartOffset,int32_t * aEndOffset)516 void ProxyAccessible::GetTextBeforeOffset(int32_t aOffset,
517 AccessibleTextBoundary aBoundaryType,
518 nsString& aText,
519 int32_t* aStartOffset,
520 int32_t* aEndOffset) {
521 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
522 if (!acc) {
523 return;
524 }
525
526 BSTR result;
527 long start, end;
528 HRESULT hr = acc->get_textBeforeOffset(
529 aOffset, GetIA2TextBoundary(aBoundaryType), &start, &end, &result);
530 if (FAILED(hr)) {
531 return;
532 }
533
534 _bstr_t resultWrap(result, false);
535 *aStartOffset = start;
536 *aEndOffset = end;
537 aText = (wchar_t*)result;
538 }
539
GetTextAfterOffset(int32_t aOffset,AccessibleTextBoundary aBoundaryType,nsString & aText,int32_t * aStartOffset,int32_t * aEndOffset)540 void ProxyAccessible::GetTextAfterOffset(int32_t aOffset,
541 AccessibleTextBoundary aBoundaryType,
542 nsString& aText, int32_t* aStartOffset,
543 int32_t* aEndOffset) {
544 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
545 if (!acc) {
546 return;
547 }
548
549 BSTR result;
550 long start, end;
551 HRESULT hr = acc->get_textAfterOffset(
552 aOffset, GetIA2TextBoundary(aBoundaryType), &start, &end, &result);
553 if (FAILED(hr)) {
554 return;
555 }
556
557 _bstr_t resultWrap(result, false);
558 aText = (wchar_t*)result;
559 *aStartOffset = start;
560 *aEndOffset = end;
561 }
562
GetTextAtOffset(int32_t aOffset,AccessibleTextBoundary aBoundaryType,nsString & aText,int32_t * aStartOffset,int32_t * aEndOffset)563 void ProxyAccessible::GetTextAtOffset(int32_t aOffset,
564 AccessibleTextBoundary aBoundaryType,
565 nsString& aText, int32_t* aStartOffset,
566 int32_t* aEndOffset) {
567 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
568 if (!acc) {
569 return;
570 }
571
572 BSTR result;
573 long start, end;
574 HRESULT hr = acc->get_textAtOffset(aOffset, GetIA2TextBoundary(aBoundaryType),
575 &start, &end, &result);
576 if (FAILED(hr)) {
577 return;
578 }
579
580 _bstr_t resultWrap(result, false);
581 aText = (wchar_t*)result;
582 *aStartOffset = start;
583 *aEndOffset = end;
584 }
585
AddToSelection(int32_t aStartOffset,int32_t aEndOffset)586 bool ProxyAccessible::AddToSelection(int32_t aStartOffset, int32_t aEndOffset) {
587 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
588 if (!acc) {
589 return false;
590 }
591
592 return SUCCEEDED(acc->addSelection(static_cast<long>(aStartOffset),
593 static_cast<long>(aEndOffset)));
594 }
595
RemoveFromSelection(int32_t aSelectionNum)596 bool ProxyAccessible::RemoveFromSelection(int32_t aSelectionNum) {
597 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
598 if (!acc) {
599 return false;
600 }
601
602 return SUCCEEDED(acc->removeSelection(static_cast<long>(aSelectionNum)));
603 }
604
CaretOffset()605 int32_t ProxyAccessible::CaretOffset() {
606 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
607 if (!acc) {
608 return -1;
609 }
610
611 long offset;
612 HRESULT hr = acc->get_caretOffset(&offset);
613 if (FAILED(hr)) {
614 return -1;
615 }
616
617 return static_cast<int32_t>(offset);
618 }
619
SetCaretOffset(int32_t aOffset)620 void ProxyAccessible::SetCaretOffset(int32_t aOffset) {
621 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
622 if (!acc) {
623 return;
624 }
625
626 acc->setCaretOffset(static_cast<long>(aOffset));
627 }
628
629 /**
630 * aScrollType should be one of the nsIAccessiblescrollType constants.
631 */
ScrollSubstringTo(int32_t aStartOffset,int32_t aEndOffset,uint32_t aScrollType)632 void ProxyAccessible::ScrollSubstringTo(int32_t aStartOffset,
633 int32_t aEndOffset,
634 uint32_t aScrollType) {
635 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
636 if (!acc) {
637 return;
638 }
639
640 acc->scrollSubstringTo(static_cast<long>(aStartOffset),
641 static_cast<long>(aEndOffset),
642 static_cast<IA2ScrollType>(aScrollType));
643 }
644
645 /**
646 * aCoordinateType is one of the nsIAccessibleCoordinateType constants.
647 */
ScrollSubstringToPoint(int32_t aStartOffset,int32_t aEndOffset,uint32_t aCoordinateType,int32_t aX,int32_t aY)648 void ProxyAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
649 int32_t aEndOffset,
650 uint32_t aCoordinateType,
651 int32_t aX, int32_t aY) {
652 RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
653 if (!acc) {
654 return;
655 }
656
657 IA2CoordinateType coordType;
658 if (aCoordinateType ==
659 nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) {
660 coordType = IA2_COORDTYPE_SCREEN_RELATIVE;
661 } else if (aCoordinateType ==
662 nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE) {
663 coordType = IA2_COORDTYPE_PARENT_RELATIVE;
664 } else {
665 MOZ_CRASH("unsupported coord type");
666 }
667
668 acc->scrollSubstringToPoint(static_cast<long>(aStartOffset),
669 static_cast<long>(aEndOffset), coordType,
670 static_cast<long>(aX), static_cast<long>(aY));
671 }
672
StartOffset(bool * aOk)673 uint32_t ProxyAccessible::StartOffset(bool* aOk) {
674 RefPtr<IAccessibleHyperlink> acc = QueryInterface<IAccessibleHyperlink>(this);
675 if (!acc) {
676 *aOk = false;
677 return 0;
678 }
679
680 long startOffset;
681 *aOk = SUCCEEDED(acc->get_startIndex(&startOffset));
682 return static_cast<uint32_t>(startOffset);
683 }
684
EndOffset(bool * aOk)685 uint32_t ProxyAccessible::EndOffset(bool* aOk) {
686 RefPtr<IAccessibleHyperlink> acc = QueryInterface<IAccessibleHyperlink>(this);
687 if (!acc) {
688 *aOk = false;
689 return 0;
690 }
691
692 long endOffset;
693 *aOk = SUCCEEDED(acc->get_endIndex(&endOffset));
694 return static_cast<uint32_t>(endOffset);
695 }
696
IsLinkValid()697 bool ProxyAccessible::IsLinkValid() {
698 RefPtr<IAccessibleHyperlink> acc = QueryInterface<IAccessibleHyperlink>(this);
699 if (!acc) {
700 return false;
701 }
702
703 boolean valid;
704 if (FAILED(acc->get_valid(&valid))) {
705 return false;
706 }
707
708 return valid;
709 }
710
AnchorCount(bool * aOk)711 uint32_t ProxyAccessible::AnchorCount(bool* aOk) {
712 *aOk = false;
713 RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
714 if (!custom) {
715 return 0;
716 }
717
718 long count;
719 if (FAILED(custom->get_anchorCount(&count))) {
720 return 0;
721 }
722
723 *aOk = true;
724 return count;
725 }
726
AnchorAt(uint32_t aIdx)727 ProxyAccessible* ProxyAccessible::AnchorAt(uint32_t aIdx) {
728 RefPtr<IAccessibleHyperlink> link =
729 QueryInterface<IAccessibleHyperlink>(this);
730 if (!link) {
731 return nullptr;
732 }
733
734 VARIANT anchor;
735 if (FAILED(link->get_anchor(aIdx, &anchor))) {
736 return nullptr;
737 }
738
739 MOZ_ASSERT(anchor.vt == VT_UNKNOWN);
740 ProxyAccessible* proxyAnchor = GetProxyFor(Document(), anchor.punkVal);
741 anchor.punkVal->Release();
742 return proxyAnchor;
743 }
744
DOMNodeID(nsString & aID)745 void ProxyAccessible::DOMNodeID(nsString& aID) {
746 aID.Truncate();
747 RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
748 if (!custom) {
749 return;
750 }
751
752 BSTR result;
753 HRESULT hr = custom->get_DOMNodeID(&result);
754 _bstr_t resultWrap(result, false);
755 if (FAILED(hr)) {
756 return;
757 }
758 aID = (wchar_t*)resultWrap;
759 }
760
TakeFocus()761 void ProxyAccessible::TakeFocus() {
762 RefPtr<IAccessible> acc;
763 if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
764 return;
765 }
766 acc->accSelect(SELFLAG_TAKEFOCUS, kChildIdSelf);
767 }
768
ChildAtPoint(int32_t aX,int32_t aY,Accessible::EWhichChildAtPoint aWhichChild)769 ProxyAccessible* ProxyAccessible::ChildAtPoint(
770 int32_t aX, int32_t aY, Accessible::EWhichChildAtPoint aWhichChild) {
771 RefPtr<IAccessible2_2> target = QueryInterface<IAccessible2_2>(this);
772 if (!target) {
773 return nullptr;
774 }
775 DocAccessibleParent* doc = Document();
776 ProxyAccessible* proxy = this;
777 // accHitTest only does direct children, but we might want the deepest child.
778 for (;;) {
779 VARIANT childVar;
780 if (FAILED(target->accHitTest(aX, aY, &childVar)) ||
781 childVar.vt == VT_EMPTY) {
782 return nullptr;
783 }
784 if (childVar.vt == VT_I4 && childVar.lVal == CHILDID_SELF) {
785 break;
786 }
787 MOZ_ASSERT(childVar.vt == VT_DISPATCH && childVar.pdispVal);
788 target = nullptr;
789 childVar.pdispVal->QueryInterface(IID_IAccessible2_2,
790 getter_AddRefs(target));
791 childVar.pdispVal->Release();
792 if (!target) {
793 return nullptr;
794 }
795 // We can't always use GetProxyFor because it can't cross document
796 // boundaries.
797 if (proxy->ChildrenCount() == 1) {
798 proxy = proxy->ChildAt(0);
799 if (proxy->IsDoc()) {
800 // We're crossing into a child document.
801 doc = proxy->AsDoc();
802 }
803 } else {
804 proxy = GetProxyFor(doc, target);
805 }
806 if (aWhichChild == Accessible::eDirectChild) {
807 break;
808 }
809 }
810 return proxy;
811 }
812
813 } // namespace a11y
814 } // namespace mozilla
815