tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ia2Accessible.cpp (17269B)


      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 "AccessibleWrap.h"
      8 
      9 #include "Accessible2_i.c"
     10 #include "Accessible2_2_i.c"
     11 #include "AccessibleRole.h"
     12 #include "AccessibleStates.h"
     13 
     14 #include "AccAttributes.h"
     15 #include "ApplicationAccessible.h"
     16 #include "Compatibility.h"
     17 #include "ia2AccessibleRelation.h"
     18 #include "IUnknownImpl.h"
     19 #include "nsAccUtils.h"
     20 #include "nsCoreUtils.h"
     21 #include "nsIAccessibleTypes.h"
     22 #include "mozilla/a11y/PDocAccessible.h"
     23 #include "Relation.h"
     24 #include "TextRange-inl.h"
     25 #include "nsAccessibilityService.h"
     26 
     27 #include "mozilla/PresShell.h"
     28 #include "nsISimpleEnumerator.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::a11y;
     32 
     33 ////////////////////////////////////////////////////////////////////////////////
     34 // ia2Accessible
     35 ////////////////////////////////////////////////////////////////////////////////
     36 
     37 STDMETHODIMP
     38 ia2Accessible::QueryInterface(REFIID iid, void** ppv) {
     39  if (!ppv) return E_INVALIDARG;
     40 
     41  *ppv = nullptr;
     42 
     43  // NOTE: If any new versions of IAccessible2 are added here, they should
     44  // also be added to the IA2 Handler in
     45  // /accessible/ipc/win/handler/AccessibleHandler.cpp
     46 
     47  if (IID_IAccessible2_2 == iid) {
     48    *ppv = static_cast<IAccessible2_2*>(this);
     49  } else if (IID_IAccessible2 == iid) {
     50    *ppv = static_cast<IAccessible2*>(this);
     51  }
     52 
     53  if (*ppv) {
     54    (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
     55    return S_OK;
     56  }
     57 
     58  return E_NOINTERFACE;
     59 }
     60 
     61 AccessibleWrap* ia2Accessible::LocalAcc() {
     62  return static_cast<MsaaAccessible*>(this)->LocalAcc();
     63 }
     64 
     65 Accessible* ia2Accessible::Acc() {
     66  return static_cast<MsaaAccessible*>(this)->Acc();
     67 }
     68 
     69 ////////////////////////////////////////////////////////////////////////////////
     70 // IAccessible2
     71 
     72 STDMETHODIMP
     73 ia2Accessible::get_nRelations(long* aNRelations) {
     74  if (!aNRelations) return E_INVALIDARG;
     75  *aNRelations = 0;
     76 
     77  Accessible* acc = Acc();
     78  if (!acc) {
     79    return CO_E_OBJNOTCONNECTED;
     80  }
     81 
     82  for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) {
     83    if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue;
     84 
     85    Relation rel = acc->RelationByType(sRelationTypePairs[idx].first);
     86    if (rel.Next()) (*aNRelations)++;
     87  }
     88  return S_OK;
     89 }
     90 
     91 STDMETHODIMP
     92 ia2Accessible::get_relation(long aRelationIndex,
     93                            IAccessibleRelation** aRelation) {
     94  if (!aRelation || aRelationIndex < 0) return E_INVALIDARG;
     95  *aRelation = nullptr;
     96 
     97  Accessible* acc = Acc();
     98  if (!acc) {
     99    return CO_E_OBJNOTCONNECTED;
    100  }
    101 
    102  long relIdx = 0;
    103  for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) {
    104    if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue;
    105 
    106    RelationType relationType = sRelationTypePairs[idx].first;
    107    Relation rel = acc->RelationByType(relationType);
    108    RefPtr<ia2AccessibleRelation> ia2Relation =
    109        new ia2AccessibleRelation(relationType, &rel);
    110    if (ia2Relation->HasTargets()) {
    111      if (relIdx == aRelationIndex) {
    112        ia2Relation.forget(aRelation);
    113        return S_OK;
    114      }
    115 
    116      relIdx++;
    117    }
    118  }
    119 
    120  return E_INVALIDARG;
    121 }
    122 
    123 STDMETHODIMP
    124 ia2Accessible::get_relations(long aMaxRelations,
    125                             IAccessibleRelation** aRelation,
    126                             long* aNRelations) {
    127  if (!aRelation || !aNRelations || aMaxRelations <= 0) return E_INVALIDARG;
    128  *aNRelations = 0;
    129 
    130  Accessible* acc = Acc();
    131  if (!acc) {
    132    return CO_E_OBJNOTCONNECTED;
    133  }
    134 
    135  for (uint32_t idx = 0;
    136       idx < std::size(sRelationTypePairs) && *aNRelations < aMaxRelations;
    137       idx++) {
    138    if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue;
    139 
    140    RelationType relationType = sRelationTypePairs[idx].first;
    141    Relation rel = acc->RelationByType(relationType);
    142    RefPtr<ia2AccessibleRelation> ia2Rel =
    143        new ia2AccessibleRelation(relationType, &rel);
    144    if (ia2Rel->HasTargets()) {
    145      ia2Rel.forget(aRelation + (*aNRelations));
    146      (*aNRelations)++;
    147    }
    148  }
    149  return S_OK;
    150 }
    151 
    152 STDMETHODIMP
    153 ia2Accessible::role(long* aRole) {
    154  if (!aRole) return E_INVALIDARG;
    155  *aRole = 0;
    156 
    157  Accessible* acc = Acc();
    158  if (!acc) return CO_E_OBJNOTCONNECTED;
    159 
    160 #define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
    161             msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType,  \
    162             nameRule)                                                       \
    163  case roles::_geckoRole:                                                    \
    164    *aRole = ia2Role;                                                        \
    165    break;
    166 
    167  a11y::role geckoRole;
    168  geckoRole = acc->Role();
    169  switch (geckoRole) {
    170 #include "RoleMap.h"
    171    default:
    172      MOZ_CRASH("Unknown role.");
    173  }
    174 
    175 #undef ROLE
    176 
    177  // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call
    178  // the IA2 role a ROLE_OUTLINEITEM.
    179  if (geckoRole == roles::ROW) {
    180    Accessible* xpParent = acc->Parent();
    181    if (xpParent && xpParent->Role() == roles::TREE_TABLE)
    182      *aRole = ROLE_SYSTEM_OUTLINEITEM;
    183  }
    184 
    185  return S_OK;
    186 }
    187 
    188 // XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
    189 MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP
    190 ia2Accessible::scrollTo(enum IA2ScrollType aScrollType) {
    191  Accessible* acc = Acc();
    192  if (!acc) {
    193    return CO_E_OBJNOTCONNECTED;
    194  }
    195 
    196  acc->ScrollTo(aScrollType);
    197  return S_OK;
    198 }
    199 
    200 STDMETHODIMP
    201 ia2Accessible::scrollToPoint(enum IA2CoordinateType aCoordType, long aX,
    202                             long aY) {
    203  Accessible* acc = Acc();
    204  if (!acc) {
    205    return CO_E_OBJNOTCONNECTED;
    206  }
    207 
    208  uint32_t geckoCoordType =
    209      (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE)
    210          ? nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE
    211          : nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
    212 
    213  acc->ScrollToPoint(geckoCoordType, aX, aY);
    214 
    215  return S_OK;
    216 }
    217 
    218 STDMETHODIMP
    219 ia2Accessible::get_groupPosition(long* aGroupLevel, long* aSimilarItemsInGroup,
    220                                 long* aPositionInGroup) {
    221  if (!aGroupLevel || !aSimilarItemsInGroup || !aPositionInGroup)
    222    return E_INVALIDARG;
    223 
    224  *aGroupLevel = 0;
    225  *aSimilarItemsInGroup = 0;
    226  *aPositionInGroup = 0;
    227 
    228  Accessible* acc = Acc();
    229  if (!acc) {
    230    return CO_E_OBJNOTCONNECTED;
    231  }
    232 
    233  GroupPos groupPos = acc->GroupPosition();
    234 
    235  // Group information for accessibles having level only (like html headings
    236  // elements) isn't exposed by this method. AT should look for 'level' object
    237  // attribute.
    238  if (!groupPos.setSize && !groupPos.posInSet) return S_FALSE;
    239 
    240  *aGroupLevel = groupPos.level;
    241  *aSimilarItemsInGroup = groupPos.setSize;
    242  *aPositionInGroup = groupPos.posInSet;
    243 
    244  return S_OK;
    245 }
    246 
    247 STDMETHODIMP
    248 ia2Accessible::get_states(AccessibleStates* aStates) {
    249  if (!aStates) return E_INVALIDARG;
    250  *aStates = 0;
    251 
    252  // XXX: bug 344674 should come with better approach that we have here.
    253 
    254  Accessible* acc = Acc();
    255  if (!acc) {
    256    *aStates = IA2_STATE_DEFUNCT;
    257    return S_OK;
    258  }
    259 
    260  uint64_t state;
    261  state = acc->State();
    262 
    263  if (state & states::INVALID) *aStates |= IA2_STATE_INVALID_ENTRY;
    264  if (state & states::REQUIRED) *aStates |= IA2_STATE_REQUIRED;
    265 
    266  // The following IA2 states are not supported by Gecko
    267  // IA2_STATE_ARMED
    268  // IA2_STATE_MANAGES_DESCENDANTS
    269  // IA2_STATE_ICONIFIED
    270  // IA2_STATE_INVALID // This is not a state, it is the absence of a state
    271 
    272  if (state & states::ACTIVE) *aStates |= IA2_STATE_ACTIVE;
    273  if (state & states::DEFUNCT) *aStates |= IA2_STATE_DEFUNCT;
    274  if (state & states::EDITABLE) *aStates |= IA2_STATE_EDITABLE;
    275  if (state & states::HORIZONTAL) *aStates |= IA2_STATE_HORIZONTAL;
    276  if (state & states::MODAL) *aStates |= IA2_STATE_MODAL;
    277  if (state & states::MULTI_LINE) *aStates |= IA2_STATE_MULTI_LINE;
    278  if (state & states::OPAQUE1) *aStates |= IA2_STATE_OPAQUE;
    279  if (state & states::SELECTABLE_TEXT) *aStates |= IA2_STATE_SELECTABLE_TEXT;
    280  if (state & states::SINGLE_LINE) *aStates |= IA2_STATE_SINGLE_LINE;
    281  if (state & states::STALE) *aStates |= IA2_STATE_STALE;
    282  if (state & states::SUPPORTS_AUTOCOMPLETION)
    283    *aStates |= IA2_STATE_SUPPORTS_AUTOCOMPLETION;
    284  if (state & states::TRANSIENT) *aStates |= IA2_STATE_TRANSIENT;
    285  if (state & states::VERTICAL) *aStates |= IA2_STATE_VERTICAL;
    286  if (state & states::CHECKED) *aStates |= IA2_STATE_CHECKABLE;
    287  if (state & states::PINNED) *aStates |= IA2_STATE_PINNED;
    288 
    289  return S_OK;
    290 }
    291 
    292 STDMETHODIMP
    293 ia2Accessible::get_extendedRole(BSTR* aExtendedRole) {
    294  if (!aExtendedRole) return E_INVALIDARG;
    295 
    296  *aExtendedRole = nullptr;
    297  return E_NOTIMPL;
    298 }
    299 
    300 STDMETHODIMP
    301 ia2Accessible::get_localizedExtendedRole(BSTR* aLocalizedExtendedRole) {
    302  if (!aLocalizedExtendedRole) return E_INVALIDARG;
    303 
    304  *aLocalizedExtendedRole = nullptr;
    305  return E_NOTIMPL;
    306 }
    307 
    308 STDMETHODIMP
    309 ia2Accessible::get_nExtendedStates(long* aNExtendedStates) {
    310  if (!aNExtendedStates) return E_INVALIDARG;
    311 
    312  *aNExtendedStates = 0;
    313  return E_NOTIMPL;
    314 }
    315 
    316 STDMETHODIMP
    317 ia2Accessible::get_extendedStates(long aMaxExtendedStates,
    318                                  BSTR** aExtendedStates,
    319                                  long* aNExtendedStates) {
    320  if (!aExtendedStates || !aNExtendedStates) return E_INVALIDARG;
    321 
    322  *aExtendedStates = nullptr;
    323  *aNExtendedStates = 0;
    324  return E_NOTIMPL;
    325 }
    326 
    327 STDMETHODIMP
    328 ia2Accessible::get_localizedExtendedStates(long aMaxLocalizedExtendedStates,
    329                                           BSTR** aLocalizedExtendedStates,
    330                                           long* aNLocalizedExtendedStates) {
    331  if (!aLocalizedExtendedStates || !aNLocalizedExtendedStates)
    332    return E_INVALIDARG;
    333 
    334  *aLocalizedExtendedStates = nullptr;
    335  *aNLocalizedExtendedStates = 0;
    336  return E_NOTIMPL;
    337 }
    338 
    339 STDMETHODIMP
    340 ia2Accessible::get_uniqueID(long* aUniqueID) {
    341  if (!aUniqueID) return E_INVALIDARG;
    342 
    343  Accessible* acc = Acc();
    344  *aUniqueID = MsaaAccessible::GetChildIDFor(acc);
    345  return S_OK;
    346 }
    347 
    348 STDMETHODIMP
    349 ia2Accessible::get_windowHandle(HWND* aWindowHandle) {
    350  if (!aWindowHandle) return E_INVALIDARG;
    351  *aWindowHandle = 0;
    352 
    353  Accessible* acc = Acc();
    354  if (!acc) return CO_E_OBJNOTCONNECTED;
    355 
    356  *aWindowHandle = MsaaAccessible::GetHWNDFor(acc);
    357  if (!*aWindowHandle && !Compatibility::IsUiaEnabled()) {
    358    // Bug 1890155: This can happen if a document is detached from its embedder.
    359    // The document might be about to die or it might be moving to a different
    360    // embedder; e.g. a tab in a different window. The IA2 -> UIA proxy may
    361    // crash if we return a null HWND. For now, pick an arbitrary top level
    362    // Gecko HWND. This might be wrong, but only briefly, since the document
    363    // will either die or move very soon, at which point this method will
    364    // return the correct answer.
    365    // TODO This hack should be removed once we only use our native UIA
    366    // implementation.
    367    if (ApplicationAccessible* app = ApplicationAcc()) {
    368      if (LocalAccessible* firstRoot = app->LocalFirstChild()) {
    369        *aWindowHandle = MsaaAccessible::GetHWNDFor(firstRoot);
    370      }
    371    }
    372  }
    373  return S_OK;
    374 }
    375 
    376 STDMETHODIMP
    377 ia2Accessible::get_indexInParent(long* aIndexInParent) {
    378  if (!aIndexInParent) return E_INVALIDARG;
    379  *aIndexInParent = -1;
    380 
    381  Accessible* acc = Acc();
    382  if (!acc) return CO_E_OBJNOTCONNECTED;
    383 
    384  *aIndexInParent = acc->IndexInParent();
    385 
    386  if (*aIndexInParent == -1) return S_FALSE;
    387 
    388  return S_OK;
    389 }
    390 
    391 STDMETHODIMP
    392 ia2Accessible::get_locale(IA2Locale* aLocale) {
    393  if (!aLocale) return E_INVALIDARG;
    394 
    395  // Language codes consist of a primary code and a possibly empty series of
    396  // subcodes: language-code = primary-code ( "-" subcode )*
    397  // Two-letter primary codes are reserved for [ISO639] language abbreviations.
    398  // Any two-letter subcode is understood to be a [ISO3166] country code.
    399 
    400  Accessible* acc = Acc();
    401  if (!acc) {
    402    return CO_E_OBJNOTCONNECTED;
    403  }
    404 
    405  nsAutoString lang;
    406  acc->Language(lang);
    407 
    408  // If primary code consists from two letters then expose it as language.
    409  int32_t offset = lang.FindChar('-', 0);
    410  if (offset == -1) {
    411    if (lang.Length() == 2) {
    412      aLocale->language = ::SysAllocString(lang.get());
    413      return S_OK;
    414    }
    415  } else if (offset == 2) {
    416    aLocale->language = ::SysAllocStringLen(lang.get(), 2);
    417 
    418    // If the first subcode consists from two letters then expose it as
    419    // country.
    420    offset = lang.FindChar('-', 3);
    421    if (offset == -1) {
    422      if (lang.Length() == 5) {
    423        aLocale->country = ::SysAllocString(lang.get() + 3);
    424        return S_OK;
    425      }
    426    } else if (offset == 5) {
    427      aLocale->country = ::SysAllocStringLen(lang.get() + 3, 2);
    428    }
    429  }
    430 
    431  // Expose as a string if primary code or subcode cannot point to language or
    432  // country abbreviations or if there are more than one subcode.
    433  aLocale->variant = ::SysAllocString(lang.get());
    434  return S_OK;
    435 }
    436 
    437 STDMETHODIMP
    438 ia2Accessible::get_attributes(BSTR* aAttributes) {
    439  if (!aAttributes) return E_INVALIDARG;
    440  *aAttributes = nullptr;
    441 
    442  Accessible* acc = Acc();
    443  if (!acc) {
    444    return CO_E_OBJNOTCONNECTED;
    445  }
    446 
    447  // The format is name:value;name:value; with \ for escaping these
    448  // characters ":;=,\".
    449  RefPtr<AccAttributes> attributes = acc->Attributes();
    450  if (acc->Role() == roles::HEADING) {
    451    // IAccessible2 expects heading level to be exposed as an object attribute.
    452    // However, all other group position info is exposed via groupPosition.
    453    nsAccUtils::SetAccGroupAttrs(attributes, acc);
    454  }
    455  return ConvertToIA2Attributes(attributes, aAttributes);
    456 }
    457 
    458 ////////////////////////////////////////////////////////////////////////////////
    459 // IAccessible2_2
    460 
    461 STDMETHODIMP
    462 ia2Accessible::get_attribute(BSTR name, VARIANT* aAttribute) {
    463  if (!aAttribute) return E_INVALIDARG;
    464 
    465  return E_NOTIMPL;
    466 }
    467 
    468 STDMETHODIMP
    469 ia2Accessible::get_accessibleWithCaret(IUnknown** aAccessible,
    470                                       long* aCaretOffset) {
    471  if (!aAccessible || !aCaretOffset) return E_INVALIDARG;
    472 
    473  *aAccessible = nullptr;
    474  *aCaretOffset = -1;
    475 
    476  if (!Acc()) {
    477    return CO_E_OBJNOTCONNECTED;
    478  }
    479  AccessibleWrap* acc = LocalAcc();
    480  if (!acc) {
    481    return E_NOTIMPL;  // XXX Not supported for RemoteAccessible yet.
    482  }
    483 
    484  int32_t caretOffset = -1;
    485  LocalAccessible* accWithCaret =
    486      SelectionMgr()->AccessibleWithCaret(&caretOffset);
    487  if (!accWithCaret || acc->Document() != accWithCaret->Document())
    488    return S_FALSE;
    489 
    490  LocalAccessible* child = accWithCaret;
    491  while (!child->IsDoc() && child != acc) child = child->LocalParent();
    492 
    493  if (child != acc) return S_FALSE;
    494 
    495  RefPtr<IAccessible2> ia2WithCaret;
    496  accWithCaret->GetNativeInterface(getter_AddRefs(ia2WithCaret));
    497  ia2WithCaret.forget(aAccessible);
    498  *aCaretOffset = caretOffset;
    499  return S_OK;
    500 }
    501 
    502 STDMETHODIMP
    503 ia2Accessible::get_relationTargetsOfType(BSTR aType, long aMaxTargets,
    504                                         IUnknown*** aTargets,
    505                                         long* aNTargets) {
    506  if (!aTargets || !aNTargets || aMaxTargets < 0) return E_INVALIDARG;
    507  *aNTargets = 0;
    508 
    509  Maybe<RelationType> relationType;
    510  for (uint32_t idx = 0; idx < std::size(sRelationTypePairs); idx++) {
    511    if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) {
    512      relationType.emplace(sRelationTypePairs[idx].first);
    513      break;
    514    }
    515  }
    516  if (!relationType) return E_INVALIDARG;
    517 
    518  Accessible* acc = Acc();
    519  if (!acc) {
    520    return CO_E_OBJNOTCONNECTED;
    521  }
    522 
    523  nsTArray<Accessible*> targets;
    524  Relation rel = acc->RelationByType(*relationType);
    525  Accessible* target = nullptr;
    526  while (
    527      (target = rel.Next()) &&
    528      (aMaxTargets == 0 || static_cast<long>(targets.Length()) < aMaxTargets)) {
    529    targets.AppendElement(target);
    530  }
    531 
    532  *aNTargets = targets.Length();
    533  *aTargets =
    534      static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets));
    535  if (!*aTargets) return E_OUTOFMEMORY;
    536 
    537  for (int32_t i = 0; i < *aNTargets; i++) {
    538    (*aTargets)[i] = MsaaAccessible::NativeAccessible(targets[i]);
    539  }
    540 
    541  return S_OK;
    542 }
    543 
    544 ////////////////////////////////////////////////////////////////////////////////
    545 // Helpers
    546 
    547 static inline void EscapeAttributeChars(nsString& aStr) {
    548  int32_t offset = 0;
    549  static const char16_t kCharsToEscape[] = u":;=,\\";
    550  while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
    551    aStr.Insert('\\', offset);
    552    offset += 2;
    553  }
    554 }
    555 
    556 HRESULT
    557 ia2Accessible::ConvertToIA2Attributes(AccAttributes* aAttributes,
    558                                      BSTR* aIA2Attributes) {
    559  *aIA2Attributes = nullptr;
    560 
    561  // The format is name:value;name:value; with \ for escaping these
    562  // characters ":;=,\".
    563 
    564  if (!aAttributes) return S_FALSE;
    565 
    566  nsAutoString strAttrs;
    567 
    568  for (auto iter : *aAttributes) {
    569    nsAutoString name;
    570    iter.NameAsString(name);
    571    EscapeAttributeChars(name);
    572 
    573    nsAutoString value;
    574    iter.ValueAsString(value);
    575    EscapeAttributeChars(value);
    576 
    577    strAttrs.Append(name);
    578    strAttrs.Append(':');
    579    strAttrs.Append(value);
    580    strAttrs.Append(';');
    581  }
    582 
    583  if (strAttrs.IsEmpty()) return S_FALSE;
    584 
    585  *aIA2Attributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length());
    586  return *aIA2Attributes ? S_OK : E_OUTOFMEMORY;
    587 }