tor-browser

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

MsaaAccessible.cpp (38650B)


      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 "EnumVariant.h"
      8 #include "ia2AccessibleApplication.h"
      9 #include "ia2AccessibleHypertext.h"
     10 #include "ia2AccessibleImage.h"
     11 #include "ia2AccessibleTable.h"
     12 #include "ia2AccessibleTableCell.h"
     13 #include "LocalAccessible-inl.h"
     14 #include "mozilla/a11y/AccessibleWrap.h"
     15 #include "mozilla/a11y/Compatibility.h"
     16 #include "mozilla/a11y/DocAccessibleParent.h"
     17 #include "MsaaAccessible.h"
     18 #include "MsaaDocAccessible.h"
     19 #include "MsaaRootAccessible.h"
     20 #include "MsaaXULMenuAccessible.h"
     21 #include "nsEventMap.h"
     22 #include "nsWinUtils.h"
     23 #include "Relation.h"
     24 #include "sdnAccessible.h"
     25 #include "HyperTextAccessible-inl.h"
     26 #include "ServiceProvider.h"
     27 #include "ARIAMap.h"
     28 #include "mozilla/PresShell.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::a11y;
     32 
     33 static const VARIANT kVarChildIdSelf = {{{VT_I4}}};
     34 
     35 // Used internally to safely get an MsaaAccessible from a COM pointer provided
     36 // to us by a client.
     37 static const GUID IID_MsaaAccessible = {
     38    /* a94aded3-1a9c-4afc-a32c-d6b5c010046b */
     39    0xa94aded3,
     40    0x1a9c,
     41    0x4afc,
     42    {0xa3, 0x2c, 0xd6, 0xb5, 0xc0, 0x10, 0x04, 0x6b}};
     43 
     44 MOZ_RUNINIT MsaaIdGenerator MsaaAccessible::sIDGen;
     45 ITypeInfo* MsaaAccessible::gTypeInfo = nullptr;
     46 
     47 /* static */
     48 MsaaAccessible* MsaaAccessible::Create(Accessible* aAcc) {
     49  // This should only ever be called in the parent process.
     50  MOZ_ASSERT(XRE_IsParentProcess());
     51  // The order of some of these is important! For example, when isRoot is true,
     52  // IsDoc will also be true, so we must check IsRoot first. IsTable/Cell and
     53  // IsHyperText are a similar case.
     54  if (aAcc->IsRoot()) {
     55    MOZ_ASSERT(aAcc->IsLocal());
     56    return new MsaaRootAccessible(aAcc);
     57  }
     58  if (aAcc->IsDoc()) {
     59    return new MsaaDocAccessible(aAcc);
     60  }
     61  if (aAcc->IsTable()) {
     62    return new ia2AccessibleTable(aAcc);
     63  }
     64  if (aAcc->IsTableCell()) {
     65    return new ia2AccessibleTableCell(aAcc);
     66  }
     67  if (aAcc->IsApplication()) {
     68    MOZ_ASSERT(aAcc->IsLocal());
     69    return new ia2AccessibleApplication(aAcc);
     70  }
     71  if (aAcc->IsImage()) {
     72    return new ia2AccessibleImage(aAcc);
     73  }
     74  if (LocalAccessible* localAcc = aAcc->AsLocal()) {
     75    if (localAcc->GetContent() &&
     76        localAcc->GetContent()->IsXULElement(nsGkAtoms::menuitem)) {
     77      return new MsaaXULMenuitemAccessible(aAcc);
     78    }
     79  }
     80  if (aAcc->IsHyperText()) {
     81    return new ia2AccessibleHypertext(aAcc);
     82  }
     83  return new MsaaAccessible(aAcc);
     84 }
     85 
     86 MsaaAccessible::MsaaAccessible(Accessible* aAcc) : mAcc(aAcc), mID(kNoID) {}
     87 
     88 MsaaAccessible::~MsaaAccessible() {
     89  MOZ_ASSERT(!mAcc, "MsaaShutdown wasn't called!");
     90  if (mID != kNoID) {
     91    sIDGen.ReleaseID(WrapNotNull(this));
     92  }
     93 }
     94 
     95 void MsaaAccessible::MsaaShutdown() {
     96  // Accessibles can be shut down twice in some cases. If that happens,
     97  // MsaaShutdown will also be called twice because AccessibleWrap holds
     98  // the reference until its destructor is called; see the comments in
     99  // AccessibleWrap::Shutdown.
    100  if (!mAcc) {
    101    return;
    102  }
    103 
    104  if (mID != kNoID) {
    105    auto doc = MsaaDocAccessible::GetFromOwned(mAcc);
    106    MOZ_ASSERT(doc);
    107    doc->RemoveID(mID);
    108  }
    109 
    110  mAcc = nullptr;
    111 }
    112 
    113 int32_t MsaaAccessible::GetChildIDFor(Accessible* aAccessible) {
    114  // A child ID of the window is required, when we use NotifyWinEvent,
    115  // so that the 3rd party application can call back and get the IAccessible
    116  // the event occurred on.
    117 
    118  if (!aAccessible) {
    119    return 0;
    120  }
    121 
    122  auto doc = MsaaDocAccessible::GetFromOwned(aAccessible);
    123  if (!doc) {
    124    return 0;
    125  }
    126 
    127  uint32_t* id = &MsaaAccessible::GetFrom(aAccessible)->mID;
    128  if (*id != kNoID) return *id;
    129 
    130  *id = sIDGen.GetID();
    131  doc->AddID(*id, aAccessible);
    132 
    133  return *id;
    134 }
    135 
    136 HWND MsaaAccessible::GetHWNDFor(Accessible* aAccessible) {
    137  if (!aAccessible) {
    138    return nullptr;
    139  }
    140 
    141  LocalAccessible* localAcc = aAccessible->AsLocal();
    142  if (!localAcc) {
    143    RemoteAccessible* proxy = aAccessible->AsRemote();
    144    if (!proxy) {
    145      return nullptr;
    146    }
    147 
    148    // If window emulation is enabled, retrieve the emulated window from the
    149    // containing document document proxy.
    150    if (nsWinUtils::IsWindowEmulationStarted()) {
    151      DocAccessibleParent* doc = proxy->Document();
    152      HWND hWnd = doc->GetEmulatedWindowHandle();
    153      if (hWnd) {
    154        return hWnd;
    155      }
    156    }
    157 
    158    // Accessibles in child processes are said to have the HWND of the window
    159    // their tab is within.  Popups are always in the parent process, and so
    160    // never proxied, which means this is basically correct.
    161    LocalAccessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
    162    if (!outerDoc) {
    163      // In some cases, the outer document accessible may be unattached from its
    164      // document at this point, if it is scheduled for removal. Do not assert
    165      // in such case. An example: putting aria-hidden="true" on HTML:iframe
    166      // element will destroy iframe's document asynchroniously, but
    167      // the document may be a target of selection events until then, and thus
    168      // it may attempt to deliever these events to MSAA clients.
    169      return nullptr;
    170    }
    171 
    172    return GetHWNDFor(outerDoc);
    173  }
    174 
    175  DocAccessible* document = localAcc->Document();
    176  if (!document) return nullptr;
    177 
    178  // Popup lives in own windows, use its HWND until the popup window is
    179  // hidden to make old JAWS versions work with collapsed comboboxes (see
    180  // discussion in bug 379678).
    181  if (nsIFrame* frame = localAcc->GetFrame()) {
    182    nsIWidget* widget = frame->GetNearestWidget();
    183    if (widget && widget->IsVisible()) {
    184      nsCOMPtr<nsIWidget> rootWidget =
    185          document->PresShellPtr()->GetRootWidget();
    186      // Make sure the accessible belongs to popup. If not then use
    187      // document HWND (which might be different from root widget in the
    188      // case of window emulation).
    189      if (rootWidget != widget) {
    190        return static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
    191      }
    192    }
    193  }
    194 
    195  return static_cast<HWND>(document->GetNativeWindow());
    196 }
    197 
    198 void MsaaAccessible::FireWinEvent(Accessible* aTarget, uint32_t aEventType) {
    199  MOZ_ASSERT(XRE_IsParentProcess());
    200  static_assert(sizeof(gWinEventMap) / sizeof(gWinEventMap[0]) ==
    201                    nsIAccessibleEvent::EVENT_LAST_ENTRY,
    202                "MSAA event map skewed");
    203 
    204  if (aEventType == 0 || aEventType >= std::size(gWinEventMap)) {
    205    MOZ_ASSERT_UNREACHABLE("invalid event type");
    206    return;
    207  }
    208 
    209  uint32_t winEvent = gWinEventMap[aEventType];
    210  if (!winEvent) return;
    211 
    212  int32_t childID = MsaaAccessible::GetChildIDFor(aTarget);
    213  if (!childID) return;  // Can't fire an event without a child ID
    214 
    215  HWND hwnd = GetHWNDFor(aTarget);
    216  if (!hwnd) {
    217    return;
    218  }
    219 
    220  // Fire MSAA event for client area window.
    221  ::NotifyWinEvent(winEvent, hwnd, OBJID_CLIENT, childID);
    222 }
    223 
    224 AccessibleWrap* MsaaAccessible::LocalAcc() {
    225  if (!mAcc || mAcc->IsRemote()) {
    226    return nullptr;
    227  }
    228  auto acc = static_cast<AccessibleWrap*>(mAcc);
    229  MOZ_ASSERT(!acc || !acc->IsDefunct(),
    230             "mAcc defunct but MsaaShutdown wasn't called");
    231  return acc;
    232 }
    233 
    234 /**
    235 * This function is a helper for implementing IAccessible methods that accept
    236 * a Child ID as a parameter. If the child ID is CHILDID_SELF, the function
    237 * returns S_OK but a null *aOutInterface. Otherwise, *aOutInterface points
    238 * to the resolved IAccessible.
    239 *
    240 * The CHILDID_SELF case is special because in that case we actually execute
    241 * the implementation of the IAccessible method, whereas in the non-self case,
    242 * we delegate the method call to that object for execution.
    243 *
    244 * A sample invocation of this would look like:
    245 *
    246 *  RefPtr<IAccessible> accessible;
    247 *  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    248 *  if (FAILED(hr)) {
    249 *    return hr;
    250 *  }
    251 *
    252 *  if (accessible) {
    253 *    return accessible->get_accFoo(kVarChildIdSelf, pszName);
    254 *  }
    255 *
    256 *  // Implementation for CHILDID_SELF case goes here
    257 */
    258 HRESULT
    259 MsaaAccessible::ResolveChild(const VARIANT& aVarChild,
    260                             IAccessible** aOutInterface) {
    261  MOZ_ASSERT(aOutInterface);
    262  *aOutInterface = nullptr;
    263 
    264  if (aVarChild.vt != VT_I4) {
    265    return E_INVALIDARG;
    266  }
    267 
    268  if (!mAcc) {
    269    return CO_E_OBJNOTCONNECTED;
    270  }
    271 
    272  if (aVarChild.lVal == CHILDID_SELF) {
    273    return S_OK;
    274  }
    275 
    276  bool isDefunct = false;
    277  RefPtr<IAccessible> accessible = GetIAccessibleFor(aVarChild, &isDefunct);
    278  if (!accessible) {
    279    return E_INVALIDARG;
    280  }
    281 
    282  if (isDefunct) {
    283    return CO_E_OBJNOTCONNECTED;
    284  }
    285 
    286  accessible.forget(aOutInterface);
    287  return S_OK;
    288 }
    289 
    290 static Accessible* GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID) {
    291  Accessible* child = MsaaDocAccessible::GetFrom(aDoc)->GetAccessibleByID(aID);
    292  if (child) return child;
    293 
    294  uint32_t childDocCount = aDoc->ChildDocumentCount();
    295  for (uint32_t i = 0; i < childDocCount; i++) {
    296    child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID);
    297    if (child) return child;
    298  }
    299 
    300  return nullptr;
    301 }
    302 
    303 static Accessible* GetAccessibleInSubtree(DocAccessibleParent* aDoc,
    304                                          uint32_t aID) {
    305  Accessible* child = MsaaDocAccessible::GetFrom(aDoc)->GetAccessibleByID(aID);
    306  if (child) {
    307    return child;
    308  }
    309 
    310  size_t childDocCount = aDoc->ChildDocCount();
    311  for (size_t i = 0; i < childDocCount; i++) {
    312    child = GetAccessibleInSubtree(aDoc->ChildDocAt(i), aID);
    313    if (child) {
    314      return child;
    315    }
    316  }
    317 
    318  return nullptr;
    319 }
    320 
    321 static bool IsInclusiveDescendantOf(DocAccessible* aAncestor,
    322                                    DocAccessible* aDescendant) {
    323  for (DocAccessible* doc = aDescendant; doc; doc = doc->ParentDocument()) {
    324    if (doc == aAncestor) {
    325      return true;
    326    }
    327  }
    328  return false;
    329 }
    330 
    331 already_AddRefed<IAccessible> MsaaAccessible::GetIAccessibleFor(
    332    const VARIANT& aVarChild, bool* aIsDefunct) {
    333  if (aVarChild.vt != VT_I4) return nullptr;
    334 
    335  VARIANT varChild = aVarChild;
    336 
    337  MOZ_ASSERT(aIsDefunct);
    338  *aIsDefunct = false;
    339 
    340  RefPtr<IAccessible> result;
    341 
    342  if (!mAcc) {
    343    *aIsDefunct = true;
    344    return nullptr;
    345  }
    346 
    347  if (varChild.lVal == CHILDID_SELF) {
    348    result = this;
    349    return result.forget();
    350  }
    351 
    352  if (varChild.ulVal != GetExistingID() && nsAccUtils::MustPrune(mAcc)) {
    353    // This accessible should have no subtree in platform, return null for its
    354    // children.
    355    return nullptr;
    356  }
    357 
    358  if (varChild.lVal > 0) {
    359    // Gecko child indices are 0-based in contrast to indices used in MSAA.
    360    Accessible* xpAcc = mAcc->ChildAt(varChild.lVal - 1);
    361    if (!xpAcc) {
    362      return nullptr;
    363    }
    364    MOZ_ASSERT(xpAcc->IsRemote() || !xpAcc->AsLocal()->IsDefunct(),
    365               "Shouldn't get a defunct child");
    366    result = MsaaAccessible::GetFrom(xpAcc);
    367    return result.forget();
    368  }
    369 
    370  // If lVal negative then it is treated as child ID and we should look for
    371  // accessible through whole accessible subtree including subdocuments.
    372  Accessible* doc = nullptr;
    373  Accessible* child = nullptr;
    374  auto id = static_cast<uint32_t>(varChild.lVal);
    375  if (LocalAccessible* localAcc = mAcc->AsLocal()) {
    376    DocAccessible* localDoc = localAcc->Document();
    377    doc = localDoc;
    378    child = GetAccessibleInSubtree(localDoc, id);
    379    if (!child) {
    380      // Search remote documents which are descendants of this local document.
    381      const auto remoteDocs = DocManager::TopLevelRemoteDocs();
    382      if (!remoteDocs) {
    383        return nullptr;
    384      }
    385      for (DocAccessibleParent* remoteDoc : *remoteDocs) {
    386        LocalAccessible* outerDoc = remoteDoc->OuterDocOfRemoteBrowser();
    387        if (!outerDoc ||
    388            !IsInclusiveDescendantOf(localDoc, outerDoc->Document())) {
    389          continue;
    390        }
    391        child = GetAccessibleInSubtree(remoteDoc, id);
    392        if (child) {
    393          break;
    394        }
    395      }
    396    }
    397  } else {
    398    DocAccessibleParent* remoteDoc = mAcc->AsRemote()->Document();
    399    doc = remoteDoc;
    400    child = GetAccessibleInSubtree(remoteDoc, id);
    401  }
    402  if (!child) {
    403    return nullptr;
    404  }
    405 
    406  MOZ_ASSERT(child->IsRemote() || !child->AsLocal()->IsDefunct(),
    407             "Shouldn't get a defunct child");
    408  // If this method is being called on the document we searched, we can just
    409  // return child.
    410  if (mAcc == doc) {
    411    result = MsaaAccessible::GetFrom(child);
    412    return result.forget();
    413  }
    414 
    415  // Otherwise, this method was called on a descendant, so we searched an
    416  // ancestor. We must check whether child is really a descendant. This is used
    417  // for ARIA documents and popups.
    418  Accessible* parent = child;
    419  while (parent && parent != doc) {
    420    if (parent == mAcc) {
    421      result = MsaaAccessible::GetFrom(child);
    422      return result.forget();
    423    }
    424 
    425    parent = parent->Parent();
    426  }
    427 
    428  return nullptr;
    429 }
    430 
    431 IDispatch* MsaaAccessible::NativeAccessible(Accessible* aAccessible) {
    432  if (!aAccessible) {
    433    NS_WARNING("Not passing in an aAccessible");
    434    return nullptr;
    435  }
    436 
    437  RefPtr<IDispatch> disp;
    438  disp = MsaaAccessible::GetFrom(aAccessible);
    439  IDispatch* rawDisp;
    440  disp.forget(&rawDisp);
    441  return rawDisp;
    442 }
    443 
    444 ITypeInfo* MsaaAccessible::GetTI(LCID lcid) {
    445  if (gTypeInfo) return gTypeInfo;
    446 
    447  ITypeLib* typeLib = nullptr;
    448  HRESULT hr = LoadRegTypeLib(LIBID_Accessibility, 1, 0, lcid, &typeLib);
    449  if (FAILED(hr)) return nullptr;
    450 
    451  hr = typeLib->GetTypeInfoOfGuid(IID_IAccessible, &gTypeInfo);
    452  typeLib->Release();
    453 
    454  if (FAILED(hr)) return nullptr;
    455 
    456  return gTypeInfo;
    457 }
    458 
    459 /* static */
    460 MsaaAccessible* MsaaAccessible::GetFrom(Accessible* aAcc) {
    461  if (!aAcc) {
    462    return nullptr;
    463  }
    464 
    465  if (RemoteAccessible* remoteAcc = aAcc->AsRemote()) {
    466    return reinterpret_cast<MsaaAccessible*>(remoteAcc->GetWrapper());
    467  }
    468  return static_cast<AccessibleWrap*>(aAcc)->GetMsaa();
    469 }
    470 
    471 /* static */
    472 Accessible* MsaaAccessible::GetAccessibleFrom(IUnknown* aUnknown) {
    473  RefPtr<MsaaAccessible> msaa;
    474  aUnknown->QueryInterface(IID_MsaaAccessible, getter_AddRefs(msaa));
    475  if (!msaa) {
    476    return nullptr;
    477  }
    478  return msaa->Acc();
    479 }
    480 
    481 // IUnknown methods
    482 STDMETHODIMP
    483 MsaaAccessible::QueryInterface(REFIID iid, void** ppv) {
    484  if (!ppv) return E_INVALIDARG;
    485 
    486  *ppv = nullptr;
    487 
    488  if (IID_IClientSecurity == iid) {
    489    // Some code might QI(IID_IClientSecurity) to detect whether or not we are
    490    // a proxy. Right now that can potentially happen off the main thread, so we
    491    // look for this condition immediately so that we don't trigger other code
    492    // that might not be thread-safe.
    493    return E_NOINTERFACE;
    494  }
    495 
    496  if (NS_WARN_IF(!NS_IsMainThread())) {
    497    // Bug 1896816: JAWS sometimes traverses into Gecko UI from a file dialog
    498    // thread. It shouldn't do that, but let's fail gracefully instead of
    499    // crashing.
    500    return RPC_E_WRONG_THREAD;
    501  }
    502 
    503  // These interfaces are always available. We can support querying to them
    504  // even if the Accessible is dead.
    505  if (IID_IUnknown == iid) {
    506    *ppv = static_cast<IAccessible*>(this);
    507  } else if (IID_MsaaAccessible == iid) {
    508    *ppv = static_cast<MsaaAccessible*>(this);
    509  } else if (IID_IDispatch == iid || IID_IAccessible == iid) {
    510    *ppv = static_cast<IAccessible*>(this);
    511  } else if (IID_IServiceProvider == iid) {
    512    *ppv = new ServiceProvider(this);
    513  } else {
    514    HRESULT hr = ia2Accessible::QueryInterface(iid, ppv);
    515    if (SUCCEEDED(hr)) {
    516      return hr;
    517    }
    518    if (Compatibility::IsUiaEnabled()) {
    519      hr = uiaRawElmProvider::QueryInterface(iid, ppv);
    520      if (SUCCEEDED(hr)) {
    521        return hr;
    522      }
    523    }
    524  }
    525  if (*ppv) {
    526    (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
    527    return S_OK;
    528  }
    529 
    530  // For interfaces below this point, we have to query the Accessible to
    531  // determine if they are available.
    532  if (!mAcc) {
    533    // Some callers expect either S_OK or E_NOINTERFACE, so don't return
    534    // CO_E_OBJNOTCONNECTED like we normally would for a dead object.
    535    return E_NOINTERFACE;
    536  }
    537  AccessibleWrap* localAcc = LocalAcc();
    538  if (IID_IEnumVARIANT == iid) {
    539    // We don't support this interface for leaf elements.
    540    if (!mAcc->HasChildren() || nsAccUtils::MustPrune(mAcc)) {
    541      return E_NOINTERFACE;
    542    }
    543    *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
    544  } else if (IID_ISimpleDOMNode == iid) {
    545    if (mAcc->IsDoc() || (localAcc && !localAcc->HasOwnContent())) {
    546      return E_NOINTERFACE;
    547    }
    548 
    549    *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(WrapNotNull(this)));
    550  }
    551 
    552  if (!*ppv && localAcc) {
    553    HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv);
    554    if (SUCCEEDED(hr)) return hr;
    555  }
    556 
    557  if (!*ppv) {
    558    HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv);
    559    if (SUCCEEDED(hr)) return hr;
    560  }
    561 
    562  if (!*ppv) {
    563    HRESULT hr = ia2AccessibleValue::QueryInterface(iid, ppv);
    564    if (SUCCEEDED(hr)) return hr;
    565  }
    566 
    567  if (nullptr == *ppv) return E_NOINTERFACE;
    568 
    569  (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
    570  return S_OK;
    571 }
    572 
    573 // IAccessible methods
    574 
    575 STDMETHODIMP
    576 MsaaAccessible::get_accParent(IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) {
    577  if (!ppdispParent) return E_INVALIDARG;
    578 
    579  *ppdispParent = nullptr;
    580 
    581  if (!mAcc) {
    582    return CO_E_OBJNOTCONNECTED;
    583  }
    584 
    585  Accessible* xpParentAcc = mAcc->Parent();
    586  if (!xpParentAcc) return S_FALSE;
    587 
    588  *ppdispParent = NativeAccessible(xpParentAcc);
    589  return S_OK;
    590 }
    591 
    592 STDMETHODIMP
    593 MsaaAccessible::get_accChildCount(long __RPC_FAR* pcountChildren) {
    594  if (!pcountChildren) return E_INVALIDARG;
    595 
    596  *pcountChildren = 0;
    597 
    598  if (!mAcc) return CO_E_OBJNOTCONNECTED;
    599 
    600  if ((Compatibility::A11ySuppressionReasons() &
    601       SuppressionReasons::Clipboard) &&
    602      mAcc->IsDoc()) {
    603    // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 22H2)
    604    // might walk the entire a11y tree using UIA whenever anything is copied to
    605    // the clipboard. This causes an unacceptable hang. We prevent this tree
    606    // walk by returning a 0 child count for documents, from which Windows might
    607    // walk.
    608    return S_OK;
    609  }
    610 
    611  if (nsAccUtils::MustPrune(mAcc)) {
    612    return S_OK;
    613  }
    614 
    615  *pcountChildren = mAcc->ChildCount();
    616  return S_OK;
    617 }
    618 
    619 STDMETHODIMP
    620 MsaaAccessible::get_accChild(
    621    /* [in] */ VARIANT varChild,
    622    /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispChild) {
    623  if (!ppdispChild) return E_INVALIDARG;
    624 
    625  *ppdispChild = nullptr;
    626  if (!mAcc) return CO_E_OBJNOTCONNECTED;
    627 
    628  // IAccessible::accChild is used to return this accessible or child accessible
    629  // at the given index or to get an accessible by child ID in the case of
    630  // document accessible.
    631  // The getting an accessible by child ID is used by
    632  // AccessibleObjectFromEvent() called by AT when AT handles our MSAA event.
    633  bool isDefunct = false;
    634  RefPtr<IAccessible> child = GetIAccessibleFor(varChild, &isDefunct);
    635  if (!child) {
    636    return E_INVALIDARG;
    637  }
    638 
    639  if (isDefunct) {
    640    return CO_E_OBJNOTCONNECTED;
    641  }
    642 
    643  child.forget(ppdispChild);
    644  return S_OK;
    645 }
    646 
    647 STDMETHODIMP
    648 MsaaAccessible::get_accName(
    649    /* [optional][in] */ VARIANT varChild,
    650    /* [retval][out] */ BSTR __RPC_FAR* pszName) {
    651  if (!pszName || varChild.vt != VT_I4) return E_INVALIDARG;
    652 
    653  *pszName = nullptr;
    654 
    655  RefPtr<IAccessible> accessible;
    656  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    657  if (FAILED(hr)) {
    658    return hr;
    659  }
    660 
    661  if (accessible) {
    662    return accessible->get_accName(kVarChildIdSelf, pszName);
    663  }
    664 
    665  nsAutoString name;
    666  Acc()->Name(name);
    667 
    668  if (name.IsVoid()) return S_FALSE;
    669 
    670  *pszName = ::SysAllocStringLen(name.get(), name.Length());
    671  if (!*pszName) return E_OUTOFMEMORY;
    672  return S_OK;
    673 }
    674 
    675 STDMETHODIMP
    676 MsaaAccessible::get_accValue(
    677    /* [optional][in] */ VARIANT varChild,
    678    /* [retval][out] */ BSTR __RPC_FAR* pszValue) {
    679  if (!pszValue) return E_INVALIDARG;
    680 
    681  *pszValue = nullptr;
    682 
    683  RefPtr<IAccessible> accessible;
    684  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    685  if (FAILED(hr)) {
    686    return hr;
    687  }
    688 
    689  if (accessible) {
    690    return accessible->get_accValue(kVarChildIdSelf, pszValue);
    691  }
    692 
    693  nsAutoString value;
    694  Acc()->Value(value);
    695 
    696  // See bug 438784: need to expose URL on doc's value attribute. For this,
    697  // reverting part of fix for bug 425693 to make this MSAA method behave
    698  // IAccessible2-style.
    699  if (value.IsEmpty()) return S_FALSE;
    700 
    701  *pszValue = ::SysAllocStringLen(value.get(), value.Length());
    702  if (!*pszValue) return E_OUTOFMEMORY;
    703  return S_OK;
    704 }
    705 
    706 STDMETHODIMP
    707 MsaaAccessible::get_accDescription(VARIANT varChild,
    708                                   BSTR __RPC_FAR* pszDescription) {
    709  if (!pszDescription) return E_INVALIDARG;
    710 
    711  *pszDescription = nullptr;
    712 
    713  RefPtr<IAccessible> accessible;
    714  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    715  if (FAILED(hr)) {
    716    return hr;
    717  }
    718 
    719  if (accessible) {
    720    return accessible->get_accDescription(kVarChildIdSelf, pszDescription);
    721  }
    722 
    723  nsAutoString description;
    724  Acc()->Description(description);
    725 
    726  *pszDescription =
    727      ::SysAllocStringLen(description.get(), description.Length());
    728  return *pszDescription ? S_OK : E_OUTOFMEMORY;
    729 }
    730 
    731 STDMETHODIMP
    732 MsaaAccessible::get_accRole(
    733    /* [optional][in] */ VARIANT varChild,
    734    /* [retval][out] */ VARIANT __RPC_FAR* pvarRole) {
    735  if (!pvarRole) return E_INVALIDARG;
    736 
    737  VariantInit(pvarRole);
    738 
    739  RefPtr<IAccessible> accessible;
    740  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    741  if (FAILED(hr)) {
    742    return hr;
    743  }
    744 
    745  if (accessible) {
    746    return accessible->get_accRole(kVarChildIdSelf, pvarRole);
    747  }
    748 
    749  a11y::role geckoRole;
    750 #ifdef DEBUG
    751  if (mAcc->IsLocal()) {
    752    NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(mAcc->AsLocal()),
    753                 "Does not support Text when it should");
    754  }
    755 #endif
    756  geckoRole = mAcc->Role();
    757 
    758  uint32_t msaaRole = 0;
    759 
    760 #define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
    761             _msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
    762             nameRule)                                                       \
    763  case roles::_geckoRole:                                                    \
    764    msaaRole = _msaaRole;                                                    \
    765    break;
    766 
    767  switch (geckoRole) {
    768 #include "RoleMap.h"
    769    default:
    770      MOZ_CRASH("Unknown role.");
    771  }
    772 
    773 #undef ROLE
    774 
    775  // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call
    776  // the MSAA role a ROLE_OUTLINEITEM for consistency and compatibility. We need
    777  // this because ARIA has a role of "row" for both grid and treegrid
    778  if (geckoRole == roles::ROW) {
    779    Accessible* xpParent = mAcc->Parent();
    780    if (xpParent && xpParent->Role() == roles::TREE_TABLE)
    781      msaaRole = ROLE_SYSTEM_OUTLINEITEM;
    782  }
    783 
    784  pvarRole->vt = VT_I4;
    785  pvarRole->lVal = msaaRole;
    786  return S_OK;
    787 }
    788 
    789 STDMETHODIMP
    790 MsaaAccessible::get_accState(
    791    /* [optional][in] */ VARIANT varChild,
    792    /* [retval][out] */ VARIANT __RPC_FAR* pvarState) {
    793  if (!pvarState) return E_INVALIDARG;
    794 
    795  VariantInit(pvarState);
    796  pvarState->vt = VT_I4;
    797  pvarState->lVal = 0;
    798 
    799  RefPtr<IAccessible> accessible;
    800  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    801  if (FAILED(hr)) {
    802    return hr;
    803  }
    804 
    805  if (accessible) {
    806    return accessible->get_accState(kVarChildIdSelf, pvarState);
    807  }
    808 
    809  // MSAA only has 31 states and the lowest 31 bits of our state bit mask
    810  // are the same states as MSAA.
    811  // Note: we map the following Gecko states to different MSAA states:
    812  //   REQUIRED -> ALERT_LOW
    813  //   ALERT -> ALERT_MEDIUM
    814  //   INVALID -> ALERT_HIGH
    815  //   CHECKABLE -> MARQUEED
    816 
    817  uint64_t state = Acc()->State();
    818 
    819  uint32_t msaaState = 0;
    820  nsAccUtils::To32States(state, &msaaState, nullptr);
    821  pvarState->lVal = msaaState;
    822  return S_OK;
    823 }
    824 
    825 STDMETHODIMP
    826 MsaaAccessible::get_accHelp(
    827    /* [optional][in] */ VARIANT varChild,
    828    /* [retval][out] */ BSTR __RPC_FAR* pszHelp) {
    829  if (!pszHelp) return E_INVALIDARG;
    830 
    831  *pszHelp = nullptr;
    832  return S_FALSE;
    833 }
    834 
    835 STDMETHODIMP
    836 MsaaAccessible::get_accHelpTopic(
    837    /* [out] */ BSTR __RPC_FAR* pszHelpFile,
    838    /* [optional][in] */ VARIANT varChild,
    839    /* [retval][out] */ long __RPC_FAR* pidTopic) {
    840  if (!pszHelpFile || !pidTopic) return E_INVALIDARG;
    841 
    842  *pszHelpFile = nullptr;
    843  *pidTopic = 0;
    844  return S_FALSE;
    845 }
    846 
    847 STDMETHODIMP
    848 MsaaAccessible::get_accKeyboardShortcut(
    849    /* [optional][in] */ VARIANT varChild,
    850    /* [retval][out] */ BSTR __RPC_FAR* pszKeyboardShortcut) {
    851  if (!pszKeyboardShortcut) return E_INVALIDARG;
    852  *pszKeyboardShortcut = nullptr;
    853 
    854  RefPtr<IAccessible> accessible;
    855  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
    856  if (FAILED(hr)) {
    857    return hr;
    858  }
    859 
    860  if (accessible) {
    861    return accessible->get_accKeyboardShortcut(kVarChildIdSelf,
    862                                               pszKeyboardShortcut);
    863  }
    864 
    865  nsAutoString shortcut;
    866 
    867  if (!mAcc->GetStringARIAAttr(nsGkAtoms::aria_keyshortcuts, shortcut)) {
    868    KeyBinding keyBinding = mAcc->AccessKey();
    869    if (keyBinding.IsEmpty()) {
    870      if (LocalAccessible* localAcc = mAcc->AsLocal()) {
    871        keyBinding = localAcc->KeyboardShortcut();
    872      }
    873    }
    874 
    875    keyBinding.ToString(shortcut);
    876  }
    877 
    878  *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(), shortcut.Length());
    879  return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY;
    880 }
    881 
    882 STDMETHODIMP
    883 MsaaAccessible::get_accFocus(
    884    /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) {
    885  if (!pvarChild) return E_INVALIDARG;
    886 
    887  VariantInit(pvarChild);
    888 
    889  // clang-format off
    890  // VT_EMPTY:    None. This object does not have the keyboard focus itself
    891  //              and does not contain a child that has the keyboard focus.
    892  // VT_I4:       lVal is CHILDID_SELF. The object itself has the keyboard focus.
    893  // VT_I4:       lVal contains the child ID of the child element with the keyboard focus.
    894  // VT_DISPATCH: pdispVal member is the address of the IDispatch interface
    895  //              for the child object with the keyboard focus.
    896  // clang-format on
    897  if (!mAcc) {
    898    return CO_E_OBJNOTCONNECTED;
    899  }
    900  // Return the current IAccessible child that has focus
    901  Accessible* focusedAccessible = mAcc->FocusedChild();
    902  if (focusedAccessible == mAcc) {
    903    pvarChild->vt = VT_I4;
    904    pvarChild->lVal = CHILDID_SELF;
    905  } else if (focusedAccessible) {
    906    pvarChild->vt = VT_DISPATCH;
    907    pvarChild->pdispVal = NativeAccessible(focusedAccessible);
    908  } else {
    909    pvarChild->vt = VT_EMPTY;  // No focus or focus is not a child
    910  }
    911 
    912  return S_OK;
    913 }
    914 
    915 /**
    916 * This helper class implements IEnumVARIANT for a nsTArray containing
    917 * accessible objects.
    918 */
    919 class AccessibleEnumerator final : public IEnumVARIANT {
    920 public:
    921  explicit AccessibleEnumerator(const nsTArray<Accessible*>& aArray)
    922      : mArray(aArray.Clone()), mCurIndex(0) {}
    923  AccessibleEnumerator(const AccessibleEnumerator& toCopy)
    924      : mArray(toCopy.mArray.Clone()), mCurIndex(toCopy.mCurIndex) {}
    925  ~AccessibleEnumerator() {}
    926 
    927  // IUnknown
    928  DECL_IUNKNOWN
    929 
    930  // IEnumVARIANT
    931  STDMETHODIMP Next(unsigned long celt, VARIANT FAR* rgvar,
    932                    unsigned long FAR* pceltFetched);
    933  STDMETHODIMP Skip(unsigned long celt);
    934  STDMETHODIMP Reset() {
    935    mCurIndex = 0;
    936    return S_OK;
    937  }
    938  STDMETHODIMP Clone(IEnumVARIANT FAR* FAR* ppenum);
    939 
    940 private:
    941  nsTArray<Accessible*> mArray;
    942  uint32_t mCurIndex;
    943 };
    944 
    945 STDMETHODIMP
    946 AccessibleEnumerator::QueryInterface(REFIID iid, void** ppvObject) {
    947  if (iid == IID_IEnumVARIANT) {
    948    *ppvObject = static_cast<IEnumVARIANT*>(this);
    949    AddRef();
    950    return S_OK;
    951  }
    952  if (iid == IID_IUnknown) {
    953    *ppvObject = static_cast<IUnknown*>(this);
    954    AddRef();
    955    return S_OK;
    956  }
    957 
    958  *ppvObject = nullptr;
    959  return E_NOINTERFACE;
    960 }
    961 
    962 STDMETHODIMP
    963 AccessibleEnumerator::Next(unsigned long celt, VARIANT FAR* rgvar,
    964                           unsigned long FAR* pceltFetched) {
    965  uint32_t length = mArray.Length();
    966  HRESULT hr = S_OK;
    967 
    968  // Can't get more elements than there are...
    969  if (celt > length - mCurIndex) {
    970    hr = S_FALSE;
    971    celt = length - mCurIndex;
    972  }
    973 
    974  // Copy the elements of the array into rgvar.
    975  for (uint32_t i = 0; i < celt; ++i, ++mCurIndex) {
    976    rgvar[i].vt = VT_DISPATCH;
    977    rgvar[i].pdispVal = MsaaAccessible::NativeAccessible(mArray[mCurIndex]);
    978  }
    979 
    980  if (pceltFetched) *pceltFetched = celt;
    981 
    982  return hr;
    983 }
    984 
    985 STDMETHODIMP
    986 AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum) {
    987  *ppenum = new AccessibleEnumerator(*this);
    988  NS_ADDREF(*ppenum);
    989  return S_OK;
    990 }
    991 
    992 STDMETHODIMP
    993 AccessibleEnumerator::Skip(unsigned long celt) {
    994  uint32_t length = mArray.Length();
    995  // Check if we can skip the requested number of elements
    996  if (celt > length - mCurIndex) {
    997    mCurIndex = length;
    998    return S_FALSE;
    999  }
   1000  mCurIndex += celt;
   1001  return S_OK;
   1002 }
   1003 
   1004 /**
   1005 * This method is called when a client wants to know which children of a node
   1006 *  are selected. Note that this method can only find selected children for
   1007 *  accessible object which implement SelectAccessible.
   1008 *
   1009 * The VARIANT return value arguement is expected to either contain a single
   1010 * IAccessible or an IEnumVARIANT of IAccessibles. We return the IEnumVARIANT
   1011 * regardless of the number of children selected, unless there are none selected
   1012 * in which case we return an empty VARIANT.
   1013 *
   1014 * We get the selected options from the select's accessible object and wrap
   1015 *  those in an AccessibleEnumerator which we then put in the return VARIANT.
   1016 *
   1017 * returns a VT_EMPTY VARIANT if:
   1018 *  - there are no selected children for this object
   1019 *  - the object is not the type that can have children selected
   1020 */
   1021 STDMETHODIMP
   1022 MsaaAccessible::get_accSelection(VARIANT __RPC_FAR* pvarChildren) {
   1023  if (!pvarChildren) return E_INVALIDARG;
   1024 
   1025  VariantInit(pvarChildren);
   1026  pvarChildren->vt = VT_EMPTY;
   1027 
   1028  if (!mAcc) {
   1029    return CO_E_OBJNOTCONNECTED;
   1030  }
   1031  Accessible* acc = Acc();
   1032 
   1033  if (!acc->IsSelect()) {
   1034    return S_OK;
   1035  }
   1036 
   1037  AutoTArray<Accessible*, 10> selectedItems;
   1038  acc->SelectedItems(&selectedItems);
   1039  uint32_t count = selectedItems.Length();
   1040  if (count == 1) {
   1041    pvarChildren->vt = VT_DISPATCH;
   1042    pvarChildren->pdispVal = NativeAccessible(selectedItems[0]);
   1043  } else if (count > 1) {
   1044    RefPtr<AccessibleEnumerator> pEnum =
   1045        new AccessibleEnumerator(selectedItems);
   1046    pvarChildren->vt =
   1047        VT_UNKNOWN;  // this must be VT_UNKNOWN for an IEnumVARIANT
   1048    NS_ADDREF(pvarChildren->punkVal = pEnum);
   1049  }
   1050  // If count == 0, vt is already VT_EMPTY, so there's nothing else to do.
   1051 
   1052  return S_OK;
   1053 }
   1054 
   1055 STDMETHODIMP
   1056 MsaaAccessible::get_accDefaultAction(
   1057    /* [optional][in] */ VARIANT varChild,
   1058    /* [retval][out] */ BSTR __RPC_FAR* pszDefaultAction) {
   1059  if (!pszDefaultAction) return E_INVALIDARG;
   1060 
   1061  *pszDefaultAction = nullptr;
   1062 
   1063  RefPtr<IAccessible> accessible;
   1064  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
   1065  if (FAILED(hr)) {
   1066    return hr;
   1067  }
   1068 
   1069  if (accessible) {
   1070    return accessible->get_accDefaultAction(kVarChildIdSelf, pszDefaultAction);
   1071  }
   1072 
   1073  nsAutoString defaultAction;
   1074  mAcc->ActionNameAt(0, defaultAction);
   1075 
   1076  *pszDefaultAction =
   1077      ::SysAllocStringLen(defaultAction.get(), defaultAction.Length());
   1078  return *pszDefaultAction ? S_OK : E_OUTOFMEMORY;
   1079 }
   1080 
   1081 STDMETHODIMP
   1082 MsaaAccessible::accSelect(
   1083    /* [in] */ long flagsSelect,
   1084    /* [optional][in] */ VARIANT varChild) {
   1085  RefPtr<IAccessible> accessible;
   1086  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
   1087  if (FAILED(hr)) {
   1088    return hr;
   1089  }
   1090 
   1091  if (accessible) {
   1092    return accessible->accSelect(flagsSelect, kVarChildIdSelf);
   1093  }
   1094 
   1095  if (flagsSelect & SELFLAG_TAKEFOCUS) {
   1096    mAcc->TakeFocus();
   1097    return S_OK;
   1098  }
   1099 
   1100  if (flagsSelect & SELFLAG_TAKESELECTION) {
   1101    mAcc->TakeSelection();
   1102    return S_OK;
   1103  }
   1104 
   1105  if (flagsSelect & SELFLAG_ADDSELECTION) {
   1106    mAcc->SetSelected(true);
   1107    return S_OK;
   1108  }
   1109 
   1110  if (flagsSelect & SELFLAG_REMOVESELECTION) {
   1111    mAcc->SetSelected(false);
   1112    return S_OK;
   1113  }
   1114 
   1115  return E_FAIL;
   1116 }
   1117 
   1118 STDMETHODIMP
   1119 MsaaAccessible::accLocation(
   1120    /* [out] */ long __RPC_FAR* pxLeft,
   1121    /* [out] */ long __RPC_FAR* pyTop,
   1122    /* [out] */ long __RPC_FAR* pcxWidth,
   1123    /* [out] */ long __RPC_FAR* pcyHeight,
   1124    /* [optional][in] */ VARIANT varChild) {
   1125  if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight) return E_INVALIDARG;
   1126 
   1127  *pxLeft = 0;
   1128  *pyTop = 0;
   1129  *pcxWidth = 0;
   1130  *pcyHeight = 0;
   1131 
   1132  RefPtr<IAccessible> accessible;
   1133  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
   1134  if (FAILED(hr)) {
   1135    return hr;
   1136  }
   1137 
   1138  if (accessible) {
   1139    return accessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight,
   1140                                   kVarChildIdSelf);
   1141  }
   1142 
   1143  LayoutDeviceIntRect rect = Acc()->Bounds();
   1144  *pxLeft = rect.X();
   1145  *pyTop = rect.Y();
   1146  *pcxWidth = rect.Width();
   1147  *pcyHeight = rect.Height();
   1148  return S_OK;
   1149 }
   1150 
   1151 STDMETHODIMP
   1152 MsaaAccessible::accNavigate(
   1153    /* [in] */ long navDir,
   1154    /* [optional][in] */ VARIANT varStart,
   1155    /* [retval][out] */ VARIANT __RPC_FAR* pvarEndUpAt) {
   1156  if (!pvarEndUpAt) return E_INVALIDARG;
   1157 
   1158  VariantInit(pvarEndUpAt);
   1159 
   1160  RefPtr<IAccessible> accessible;
   1161  HRESULT hr = ResolveChild(varStart, getter_AddRefs(accessible));
   1162  if (FAILED(hr)) {
   1163    return hr;
   1164  }
   1165 
   1166  if (accessible) {
   1167    return accessible->accNavigate(navDir, kVarChildIdSelf, pvarEndUpAt);
   1168  }
   1169 
   1170  Accessible* navAccessible = nullptr;
   1171  Maybe<RelationType> xpRelation;
   1172 
   1173 #define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
   1174  case msaaType:                                                        \
   1175    xpRelation.emplace(RelationType::geckoType);                        \
   1176    break;
   1177 
   1178  switch (navDir) {
   1179    case NAVDIR_FIRSTCHILD:
   1180      if (!nsAccUtils::MustPrune(mAcc)) {
   1181        navAccessible = mAcc->FirstChild();
   1182      }
   1183      break;
   1184    case NAVDIR_LASTCHILD:
   1185      if (!nsAccUtils::MustPrune(mAcc)) {
   1186        navAccessible = mAcc->LastChild();
   1187      }
   1188      break;
   1189    case NAVDIR_NEXT:
   1190      navAccessible = mAcc->NextSibling();
   1191      break;
   1192    case NAVDIR_PREVIOUS:
   1193      navAccessible = mAcc->PrevSibling();
   1194      break;
   1195    case NAVDIR_DOWN:
   1196    case NAVDIR_LEFT:
   1197    case NAVDIR_RIGHT:
   1198    case NAVDIR_UP:
   1199      return E_NOTIMPL;
   1200 
   1201      // MSAA relationship extensions to accNavigate
   1202 #include "RelationTypeMap.h"
   1203 
   1204    default:
   1205      return E_INVALIDARG;
   1206  }
   1207 
   1208 #undef RELATIONTYPE
   1209 
   1210  pvarEndUpAt->vt = VT_EMPTY;
   1211 
   1212  if (xpRelation) {
   1213    Relation rel = mAcc->RelationByType(*xpRelation);
   1214    navAccessible = rel.Next();
   1215  }
   1216 
   1217  if (!navAccessible) return E_FAIL;
   1218 
   1219  pvarEndUpAt->pdispVal = NativeAccessible(navAccessible);
   1220  pvarEndUpAt->vt = VT_DISPATCH;
   1221  return S_OK;
   1222 }
   1223 
   1224 STDMETHODIMP
   1225 MsaaAccessible::accHitTest(
   1226    /* [in] */ long xLeft,
   1227    /* [in] */ long yTop,
   1228    /* [retval][out] */ VARIANT __RPC_FAR* pvarChild) {
   1229  if (!pvarChild) return E_INVALIDARG;
   1230 
   1231  VariantInit(pvarChild);
   1232 
   1233  if (!mAcc) {
   1234    return CO_E_OBJNOTCONNECTED;
   1235  }
   1236 
   1237  // The MSAA documentation says accHitTest should return a child. However,
   1238  // clients call AccessibleObjectFromPoint, which ends up walking the
   1239  // descendants calling accHitTest on each one. Since clients want the
   1240  // deepest descendant anyway, it's faster and probably more accurate to
   1241  // just do this ourselves.
   1242  Accessible* accessible = mAcc->ChildAtPoint(
   1243      xLeft, yTop, Accessible::EWhichChildAtPoint::DeepestChild);
   1244 
   1245  // if we got a child
   1246  if (accessible) {
   1247    if (accessible != mAcc && accessible->IsTextLeaf()) {
   1248      Accessible* parent = accessible->Parent();
   1249      if (parent != mAcc && parent->Role() == roles::LINK) {
   1250        // Bug 1843832: The UI Automation -> IAccessible2 proxy barfs if we
   1251        // return the text leaf child of a link when hit testing an ancestor of
   1252        // the link. Therefore, we return the link instead. MSAA clients which
   1253        // call AccessibleObjectFromPoint will still get to the text leaf, since
   1254        // AccessibleObjectFromPoint keeps calling accHitTest until it can't
   1255        // descend any further. We should remove this tragic hack once we have
   1256        // a native UIA implementation.
   1257        accessible = parent;
   1258      }
   1259    }
   1260    if (accessible == mAcc) {
   1261      pvarChild->vt = VT_I4;
   1262      pvarChild->lVal = CHILDID_SELF;
   1263    } else {
   1264      pvarChild->vt = VT_DISPATCH;
   1265      pvarChild->pdispVal = NativeAccessible(accessible);
   1266    }
   1267  } else {
   1268    // no child at that point
   1269    pvarChild->vt = VT_EMPTY;
   1270    return S_FALSE;
   1271  }
   1272  return S_OK;
   1273 }
   1274 
   1275 STDMETHODIMP
   1276 MsaaAccessible::accDoDefaultAction(
   1277    /* [optional][in] */ VARIANT varChild) {
   1278  RefPtr<IAccessible> accessible;
   1279  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
   1280  if (FAILED(hr)) {
   1281    return hr;
   1282  }
   1283 
   1284  if (accessible) {
   1285    return accessible->accDoDefaultAction(kVarChildIdSelf);
   1286  }
   1287 
   1288  return mAcc->DoAction(0) ? S_OK : E_INVALIDARG;
   1289 }
   1290 
   1291 STDMETHODIMP
   1292 MsaaAccessible::put_accName(
   1293    /* [optional][in] */ VARIANT varChild,
   1294    /* [in] */ BSTR szName) {
   1295  return E_NOTIMPL;
   1296 }
   1297 
   1298 STDMETHODIMP
   1299 MsaaAccessible::put_accValue(
   1300    /* [optional][in] */ VARIANT varChild,
   1301    /* [in] */ BSTR szValue) {
   1302  RefPtr<IAccessible> accessible;
   1303  HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
   1304  if (FAILED(hr)) {
   1305    return hr;
   1306  }
   1307 
   1308  if (accessible) {
   1309    return accessible->put_accValue(kVarChildIdSelf, szValue);
   1310  }
   1311 
   1312  HyperTextAccessibleBase* ht = mAcc->AsHyperTextBase();
   1313  if (!ht) {
   1314    return E_NOTIMPL;
   1315  }
   1316 
   1317  uint32_t length = ::SysStringLen(szValue);
   1318  nsAutoString text(szValue, length);
   1319  ht->ReplaceText(text);
   1320  return S_OK;
   1321 }
   1322 
   1323 // IDispatch methods
   1324 
   1325 STDMETHODIMP
   1326 MsaaAccessible::GetTypeInfoCount(UINT* pctinfo) {
   1327  if (!pctinfo) return E_INVALIDARG;
   1328 
   1329  *pctinfo = 1;
   1330  return S_OK;
   1331 }
   1332 
   1333 STDMETHODIMP
   1334 MsaaAccessible::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) {
   1335  if (!ppTInfo) return E_INVALIDARG;
   1336 
   1337  *ppTInfo = nullptr;
   1338 
   1339  if (iTInfo != 0) return DISP_E_BADINDEX;
   1340 
   1341  ITypeInfo* typeInfo = GetTI(lcid);
   1342  if (!typeInfo) return E_FAIL;
   1343 
   1344  typeInfo->AddRef();
   1345  *ppTInfo = typeInfo;
   1346 
   1347  return S_OK;
   1348 }
   1349 
   1350 STDMETHODIMP
   1351 MsaaAccessible::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames,
   1352                              LCID lcid, DISPID* rgDispId) {
   1353  ITypeInfo* typeInfo = GetTI(lcid);
   1354  if (!typeInfo) return E_FAIL;
   1355 
   1356  HRESULT hr = DispGetIDsOfNames(typeInfo, rgszNames, cNames, rgDispId);
   1357  return hr;
   1358 }
   1359 
   1360 STDMETHODIMP
   1361 MsaaAccessible::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
   1362                       DISPPARAMS* pDispParams, VARIANT* pVarResult,
   1363                       EXCEPINFO* pExcepInfo, UINT* puArgErr) {
   1364  ITypeInfo* typeInfo = GetTI(lcid);
   1365  if (!typeInfo) return E_FAIL;
   1366 
   1367  return typeInfo->Invoke(static_cast<IAccessible*>(this), dispIdMember, wFlags,
   1368                          pDispParams, pVarResult, pExcepInfo, puArgErr);
   1369 }