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