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(&currentValue);
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(&currentValue);
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