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 }