diff options
Diffstat (limited to 'accessible/ipc/win/handler')
17 files changed, 4127 insertions, 0 deletions
diff --git a/accessible/ipc/win/handler/AccessibleHandler.cpp b/accessible/ipc/win/handler/AccessibleHandler.cpp new file mode 100644 index 0000000000..bf70af4f35 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.cpp @@ -0,0 +1,2189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "AccessibleHandler.h" +#include "AccessibleHandlerControl.h" +#include "HandlerChildEnumerator.h" +#include "HandlerRelation.h" + +#include "Factory.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/a11y/HandlerDataCleanup.h" +#include "mozilla/mscom/Registration.h" +#include "mozilla/UniquePtr.h" + +#include <objbase.h> +#include <uiautomation.h> +#include <winreg.h> + +#include "AccessibleHypertext.h" +#include "AccessibleHypertext2.h" +#include "AccessibleRole.h" +#include "Accessible2_i.c" +#include "Accessible2_2_i.c" +#include "Accessible2_3_i.c" +#include "AccessibleAction_i.c" +#include "AccessibleHyperlink_i.c" +#include "AccessibleHypertext_i.c" +#include "AccessibleHypertext2_i.c" +#include "AccessibleTable_i.c" +#include "AccessibleTable2_i.c" +#include "AccessibleTableCell_i.c" +#include "AccessibleText_i.c" + +namespace mozilla { +namespace a11y { + +// Must be kept in sync with kClassNameTabContent in +// accessible/windows/msaa/nsWinUtils.h. +const WCHAR kEmulatedWindowClassName[] = L"MozillaContentWindowClass"; +const uint32_t kEmulatedWindowClassNameNChars = + sizeof(kEmulatedWindowClassName) / sizeof(WCHAR); +// Mask to get the content process portion of a Windows accessible unique id. +// This is bits 23 through 30 (LSB 0) of the id. This must be kept in sync +// with kNumContentProcessIDBits in accessible/windows/msaa/MsaaIdGenerator.cpp. +const uint32_t kIdContentProcessMask = 0x7F800000; + +static mscom::Factory<AccessibleHandler> sHandlerFactory; + +HRESULT +AccessibleHandler::Create(IUnknown* aOuter, REFIID aIid, void** aOutInterface) { + if (!aOutInterface || !aOuter || aIid != IID_IUnknown) { + return E_INVALIDARG; + } + + *aOutInterface = nullptr; + + HRESULT hr; + RefPtr<AccessibleHandler> handler(new AccessibleHandler(aOuter, &hr)); + if (FAILED(hr)) { + return hr; + } + + return handler->InternalQueryInterface(aIid, aOutInterface); +} + +AccessibleHandler::AccessibleHandler(IUnknown* aOuter, HRESULT* aResult) + : mscom::Handler(aOuter, aResult), + mDispatch(nullptr), + mIA2PassThru(nullptr), + mServProvPassThru(nullptr), + mIAHyperlinkPassThru(nullptr), + mIATableCellPassThru(nullptr), + mIAHypertextPassThru(nullptr), + mCachedData(), + mCachedDynamicDataMarshaledByCom(false), + mCacheGen(0), + mCachedHyperlinks(nullptr), + mCachedNHyperlinks(-1), + mCachedTextAttribRuns(nullptr), + mCachedNTextAttribRuns(-1), + mCachedRelations(nullptr), + mCachedNRelations(-1), + mIsEmulatedWindow(false) { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + if (aResult) { + *aResult = E_UNEXPECTED; + } + return; + } + + mCacheGen = ctl->GetCacheGen(); +} + +AccessibleHandler::~AccessibleHandler() { + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + if (mCachedData.mGeckoBackChannel) { + mCachedData.mGeckoBackChannel->Release(); + } + ClearTextCache(); + ClearRelationCache(); +} + +HRESULT +AccessibleHandler::ResolveIA2() { + if (mIA2PassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = proxy->QueryInterface(NEWEST_IA2_IID, + reinterpret_cast<void**>(&mIA2PassThru)); + if (SUCCEEDED(hr)) { + // mIA2PassThru is a weak reference (see comments in AccesssibleHandler.h) + mIA2PassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIAHyperlink() { + if (mIAHyperlinkPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleHyperlink, + reinterpret_cast<void**>(&mIAHyperlinkPassThru)); + if (SUCCEEDED(hr)) { + // mIAHyperlinkPassThru is a weak reference + // (see comments in AccesssibleHandler.h) + mIAHyperlinkPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIATableCell() { + if (mIATableCellPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleTableCell, + reinterpret_cast<void**>(&mIATableCellPassThru)); + if (SUCCEEDED(hr)) { + // mIATableCellPassThru is a weak reference + // (see comments in AccesssibleHandler.h) + mIATableCellPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::ResolveIAHypertext() { + if (mIAHypertextPassThru) { + return S_OK; + } + + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = + proxy->QueryInterface(IID_IAccessibleHypertext2, + reinterpret_cast<void**>(&mIAHypertextPassThru)); + if (SUCCEEDED(hr)) { + // mIAHypertextPassThru is a weak reference + // (see comments in AccessibleHandler.h) + mIAHypertextPassThru->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::MaybeUpdateCachedData() { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + uint32_t gen = ctl->GetCacheGen(); + if (gen == mCacheGen) { + // Cache is already up to date. + return S_OK; + } + + if (!mCachedData.mGeckoBackChannel) { + return E_POINTER; + } + + // While we're making the outgoing COM call below, an incoming COM call can + // be handled which calls ReadHandlerPayload or re-enters this function. + // Therefore, we mustn't update the cached data directly lest it be mutated + // elsewhere before the outgoing COM call returns and cause corruption or + // memory leaks. Instead, pass a temporary struct and update the cached data + // only after this call completes. + DynamicIA2Data newData; + HRESULT hr = mCachedData.mGeckoBackChannel->Refresh(&newData); + if (SUCCEEDED(hr)) { + // Clean up the old data. + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + mCachedData.mDynamicData = newData; + mCachedDynamicDataMarshaledByCom = true; + // We just updated the cache, so update this object's cache generation + // so we only update the cache again after the next change. + mCacheGen = gen; + } + return hr; +} + +HRESULT +AccessibleHandler::GetAllTextInfo(BSTR* aText) { + MOZ_ASSERT(mCachedData.mGeckoBackChannel); + + ClearTextCache(); + + return mCachedData.mGeckoBackChannel->get_AllTextInfo( + aText, &mCachedHyperlinks, &mCachedNHyperlinks, &mCachedTextAttribRuns, + &mCachedNTextAttribRuns); +} + +void AccessibleHandler::ClearTextCache() { + if (mCachedNHyperlinks >= 0) { + // We cached hyperlinks, but the caller never retrieved them. + for (long index = 0; index < mCachedNHyperlinks; ++index) { + mCachedHyperlinks[index]->Release(); + } + // mCachedHyperlinks might already be null if there are no hyperlinks. + if (mCachedHyperlinks) { + ::CoTaskMemFree(mCachedHyperlinks); + mCachedHyperlinks = nullptr; + } + mCachedNHyperlinks = -1; + } + + if (mCachedTextAttribRuns) { + for (long index = 0; index < mCachedNTextAttribRuns; ++index) { + if (mCachedTextAttribRuns[index].text) { + // The caller never requested this attribute run. + ::SysFreeString(mCachedTextAttribRuns[index].text); + } + } + // This array is internal to us, so we must always free it. + ::CoTaskMemFree(mCachedTextAttribRuns); + mCachedTextAttribRuns = nullptr; + mCachedNTextAttribRuns = -1; + } +} + +HRESULT +AccessibleHandler::GetRelationsInfo() { + MOZ_ASSERT(mCachedData.mGeckoBackChannel); + + ClearRelationCache(); + + return mCachedData.mGeckoBackChannel->get_RelationsInfo(&mCachedRelations, + &mCachedNRelations); +} + +void AccessibleHandler::ClearRelationCache() { + if (mCachedNRelations == -1) { + // No cache; nothing to do. + return; + } + + // We cached relations, but the client never retrieved them. + if (mCachedRelations) { + for (long index = 0; index < mCachedNRelations; ++index) { + IARelationData& relData = mCachedRelations[index]; + if (relData.mType) { + ::SysFreeString(relData.mType); + } + } + // This array is internal to us, so we must always free it. + ::CoTaskMemFree(mCachedRelations); + mCachedRelations = nullptr; + } + mCachedNRelations = -1; +} + +HRESULT +AccessibleHandler::ResolveIDispatch() { + if (mDispatch) { + return S_OK; + } + + HRESULT hr; + + if (!mDispatchUnk) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + RefPtr<ITypeInfo> typeinfo; + hr = ctl->GetHandlerTypeInfo(getter_AddRefs(typeinfo)); + if (FAILED(hr)) { + return hr; + } + + hr = ::CreateStdDispatch(GetOuter(), + static_cast<NEWEST_IA2_INTERFACE*>(this), typeinfo, + getter_AddRefs(mDispatchUnk)); + if (FAILED(hr)) { + return hr; + } + } + + hr = mDispatchUnk->QueryInterface(IID_IDispatch, + reinterpret_cast<void**>(&mDispatch)); + if (SUCCEEDED(hr)) { + // mDispatch is weak (see comments in AccessibleHandler.h) + mDispatch->Release(); + } + + return hr; +} + +HRESULT +AccessibleHandler::QueryHandlerInterface(IUnknown* aProxyUnknown, REFIID aIid, + void** aOutInterface) { + MOZ_ASSERT(aProxyUnknown); + + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IDispatch || aIid == IID_IAccessible2_3 || + aIid == IID_IAccessible2_2 || aIid == IID_IAccessible2 || + aIid == IID_IAccessible) { + RefPtr<NEWEST_IA2_INTERFACE> ia2(static_cast<NEWEST_IA2_INTERFACE*>(this)); + ia2.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IServiceProvider) { + RefPtr<IServiceProvider> svcProv(static_cast<IServiceProvider*>(this)); + svcProv.forget(aOutInterface); + return S_OK; + } + + if (HasPayload()) { + // The proxy manager caches interfaces marshaled in the payload + // and returns them on QI without a cross-process call. + // However, it doesn't know about interfaces which don't exist. + // We can determine this from the payload. + if (((aIid == IID_IAccessibleText || aIid == IID_IAccessibleHypertext || + aIid == IID_IAccessibleHypertext2) && + !mCachedData.mStaticData.mIAHypertext) || + ((aIid == IID_IAccessibleAction || aIid == IID_IAccessibleHyperlink) && + !mCachedData.mStaticData.mIAHyperlink) || + (aIid == IID_IAccessibleTable && !mCachedData.mStaticData.mIATable) || + (aIid == IID_IAccessibleTable2 && !mCachedData.mStaticData.mIATable2) || + (aIid == IID_IAccessibleTableCell && + !mCachedData.mStaticData.mIATableCell)) { + // We already know this interface is not available, so don't query + // the proxy, thus avoiding a pointless cross-process call. + // If we return E_NOINTERFACE here, mscom::Handler will try the COM + // proxy. S_FALSE signals that the proxy should not be tried. + return S_FALSE; + } + } + + if (aIid == IID_IAccessibleAction || aIid == IID_IAccessibleHyperlink) { + RefPtr<IAccessibleHyperlink> iaLink( + static_cast<IAccessibleHyperlink*>(this)); + iaLink.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IAccessibleTableCell) { + RefPtr<IAccessibleTableCell> iaCell( + static_cast<IAccessibleTableCell*>(this)); + iaCell.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IAccessibleText || aIid == IID_IAccessibleHypertext || + aIid == IID_IAccessibleHypertext2) { + RefPtr<IAccessibleHypertext2> iaHt( + static_cast<IAccessibleHypertext2*>(this)); + iaHt.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IProvideClassInfo) { + RefPtr<IProvideClassInfo> clsInfo(this); + clsInfo.forget(aOutInterface); + return S_OK; + } + + if (aIid == IID_IEnumVARIANT && mCachedData.mGeckoBackChannel) { + if (mCachedData.mDynamicData.mChildCount == 0) { + return E_NOINTERFACE; + } + if (!mCachedData.mStaticData.mIAHypertext && + mCachedData.mDynamicData.mChildCount == 1) { + // This might be an OOP iframe. (We can't check the role because it might + // be overridden by ARIA.) HandlerChildEnumerator works fine for iframes + // rendered in the same content process. However, for out-of-process + // iframes, HandlerProvider::get_AllChildren (called by + // HandlerChildEnumerator) will fail. This is because we only send down + // an IDispatch COM proxy for the embedded document, but get_AllChildren + // will try to QueryInterface this to IAccessible2 to reduce QI calls + // from the parent process. Because the content process is sandboxed, + // it can't make the outgoing COM call to QI the proxy from IDispatch to + // IAccessible2 and so it fails. + // Since this Accessible only has one child anyway, we don't need the bulk + // fetch optimization offered by HandlerChildEnumerator or even + // IEnumVARIANT. Therefore, we explicitly tell the client this interface + // is not supported, which will cause the oleacc AccessibleChildren + // function to fall back to accChild. If we return E_NOINTERFACE here, + // mscom::Handler will try the COM proxy. S_FALSE signals that the proxy + // should not be tried. + return S_FALSE; + } + RefPtr<IEnumVARIANT> childEnum( + new HandlerChildEnumerator(this, mCachedData.mGeckoBackChannel)); + childEnum.forget(aOutInterface); + return S_OK; + } + + return E_NOINTERFACE; +} + +HRESULT +AccessibleHandler::ReadHandlerPayload(IStream* aStream, REFIID aIid) { + if (!aStream) { + return E_INVALIDARG; + } + + mscom::StructFromStream deserializer(aStream); + if (!deserializer) { + return E_FAIL; + } + if (deserializer.IsEmpty()) { + return S_FALSE; + } + + // QueryHandlerInterface might get called while we deserialize the payload, + // but that checks the interface pointers in the payload to determine what + // interfaces are available. Therefore, deserialize into a temporary struct + // and update mCachedData only after deserialization completes. + // The decoding functions can misbehave if their target memory is not zeroed + // beforehand, so ensure we do that. + IA2Payload newData{}; + if (!deserializer.Read(&newData, &IA2Payload_Decode)) { + return E_FAIL; + } + // Clean up the old data. + CleanupDynamicIA2Data(mCachedData.mDynamicData, + mCachedDynamicDataMarshaledByCom); + mCachedData = newData; + mCachedDynamicDataMarshaledByCom = false; + + // These interfaces have been aggregated into the proxy manager. + // The proxy manager will resolve these interfaces now on QI, + // so we can release these pointers. + // However, we don't null them out because we use their presence + // to determine whether the interface is available + // so as to avoid pointless cross-proc QI calls returning E_NOINTERFACE. + // Note that if pointers to other objects (in contrast to + // interfaces of *this* object) are added in future, we should not release + // those pointers. + ReleaseStaticIA2DataInterfaces(mCachedData.mStaticData); + + WCHAR className[kEmulatedWindowClassNameNChars]; + if (mCachedData.mDynamicData.mHwnd && + ::GetClassName( + reinterpret_cast<HWND>(uintptr_t(mCachedData.mDynamicData.mHwnd)), + className, kEmulatedWindowClassNameNChars) > 0 && + wcscmp(className, kEmulatedWindowClassName) == 0) { + mIsEmulatedWindow = true; + } + + if (!mCachedData.mGeckoBackChannel) { + return S_OK; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_COLUMNHEADER || + mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_ROWHEADER) { + // Because the same headers can apply to many cells, handler payloads + // include the ids of header cells, rather than potentially marshaling the + // same objects many times. We need to cache header cells here so we can + // get them by id later. + ctl->CacheAccessible(mCachedData.mDynamicData.mUniqueId, this); + } + + return ctl->Register(WrapNotNull(mCachedData.mGeckoBackChannel)); +} + +REFIID +AccessibleHandler::MarshalAs(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2 || aIid == IID_IAccessible || + aIid == IID_IDispatch) { + return NEWEST_IA2_IID; + } + + return aIid; +} + +HRESULT +AccessibleHandler::GetMarshalInterface(REFIID aMarshalAsIid, + NotNull<IUnknown*> aProxy, + NotNull<IID*> aOutIid, + NotNull<IUnknown**> aOutUnk) { + if (aMarshalAsIid == NEWEST_IA2_IID) { + *aOutIid = IID_IAccessible; + } else { + *aOutIid = aMarshalAsIid; + } + + return aProxy->QueryInterface( + aMarshalAsIid, + reinterpret_cast<void**>(static_cast<IUnknown**>(aOutUnk))); +} + +HRESULT +AccessibleHandler::GetHandlerPayloadSize(REFIID aIid, DWORD* aOutPayloadSize) { + if (!aOutPayloadSize) { + return E_INVALIDARG; + } + + // If we're sending the payload to somebody else, we'd better make sure that + // it is up to date. If the cache update fails then we'll return a 0 payload + // size so that we don't transfer obsolete data. + if (FAILED(MaybeUpdateCachedData())) { + *aOutPayloadSize = mscom::StructToStream::GetEmptySize(); + return S_OK; + } + + mSerializer = + MakeUnique<mscom::StructToStream>(mCachedData, &IA2Payload_Encode); + if (!mSerializer) { + return E_FAIL; + } + + *aOutPayloadSize = mSerializer->GetSize(); + return S_OK; +} + +HRESULT +AccessibleHandler::WriteHandlerPayload(IStream* aStream, REFIID aIid) { + if (!aStream) { + return E_INVALIDARG; + } + + if (!mSerializer) { + return E_UNEXPECTED; + } + + HRESULT hr = mSerializer->Write(aStream); + mSerializer.reset(); + return hr; +} + +HRESULT +AccessibleHandler::QueryInterface(REFIID riid, void** ppv) { + return Handler::QueryInterface(riid, ppv); +} + +ULONG +AccessibleHandler::AddRef() { return Handler::AddRef(); } + +ULONG +AccessibleHandler::Release() { return Handler::Release(); } + +HRESULT +AccessibleHandler::GetTypeInfoCount(UINT* pctinfo) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetTypeInfoCount(pctinfo); +} + +HRESULT +AccessibleHandler::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo); +} + +HRESULT +AccessibleHandler::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); +} + +HRESULT +AccessibleHandler::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS* pDispParams, + VARIANT* pVarResult, EXCEPINFO* pExcepInfo, + UINT* puArgErr) { + HRESULT hr = ResolveIDispatch(); + if (FAILED(hr)) { + return hr; + } + + return mDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, + pVarResult, pExcepInfo, puArgErr); +} + +#define BEGIN_CACHE_ACCESS \ + { \ + HRESULT hr; \ + if (FAILED(hr = MaybeUpdateCachedData())) { \ + return hr; \ + } \ + } + +#define GET_FIELD(member, assignTo) \ + { assignTo = mCachedData.mDynamicData.member; } + +#define GET_BSTR(member, assignTo) \ + { assignTo = CopyBSTR(mCachedData.mDynamicData.member); } + +/*** IAccessible ***/ + +HRESULT +AccessibleHandler::get_accParent(IDispatch** ppdispParent) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accParent(ppdispParent); +} + +HRESULT +AccessibleHandler::get_accChildCount(long* pcountChildren) { + if (!pcountChildren) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accChildCount(pcountChildren); + } + + BEGIN_CACHE_ACCESS; + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_DOCUMENT) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + if (ctl->IsA11ySuppressedForClipboardCopy()) { + // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 + // 22H2) might walk the document a11y tree using UIA whenever anything + // is copied to the clipboard. This causes an unacceptable hang, + // particularly when the cache is disabled. Even though we lie about the + // selection in nSelections, it falls back to a normal tree walk on the + // document if it doesn't get a proper text selection. Prevent that by + // returning a 0 child count on the document. + *pcountChildren = 0; + return S_OK; + } + } + + GET_FIELD(mChildCount, *pcountChildren); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { + if (!ppdispChild) { + return E_INVALIDARG; + } + // Unlikely, but we might as well optimize for it + if (varChild.vt == VT_I4 && varChild.lVal == CHILDID_SELF) { + RefPtr<IDispatch> disp(this); + disp.forget(ppdispChild); + return S_OK; + } + + if (mIsEmulatedWindow && varChild.vt == VT_I4 && varChild.lVal < 0 && + (varChild.lVal & kIdContentProcessMask) != + (mCachedData.mDynamicData.mUniqueId & kIdContentProcessMask)) { + // Window emulation is enabled and the target id is in a different + // process to this accessible. + // When window emulation is enabled, each tab document gets its own HWND. + // OOP iframes get the same HWND as their tab document and fire events with + // that HWND. However, the root accessible for the HWND (the tab document) + // can't return accessibles for OOP iframes. Therefore, we must get the root + // accessible from the main HWND and call accChild on that instead. + // We don't need an oleacc proxy, so send WM_GETOBJECT directly instead of + // calling AccessibleObjectFromEvent. + HWND rootHwnd = GetParent( + reinterpret_cast<HWND>(uintptr_t(mCachedData.mDynamicData.mHwnd))); + MOZ_ASSERT(rootHwnd); + LRESULT lresult = ::SendMessage(rootHwnd, WM_GETOBJECT, 0, OBJID_CLIENT); + if (lresult > 0) { + RefPtr<IAccessible2_3> rootAcc; + HRESULT hr = ::ObjectFromLresult(lresult, IID_IAccessible2_3, 0, + getter_AddRefs(rootAcc)); + if (hr == S_OK) { + return rootAcc->get_accChild(varChild, ppdispChild); + } + } + } + + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accChild(varChild, ppdispChild); +} + +HRESULT +AccessibleHandler::get_accName(VARIANT varChild, BSTR* pszName) { + if (!pszName) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accName(varChild, pszName); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mName, *pszName); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accValue(VARIANT varChild, BSTR* pszValue) { + if (!pszValue) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accValue(varChild, pszValue); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mValue, *pszValue); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accDescription(VARIANT varChild, BSTR* pszDescription) { + if (!pszDescription) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accDescription(varChild, pszDescription); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mDescription, *pszDescription); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accRole(VARIANT varChild, VARIANT* pvarRole) { + if (!pvarRole) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accRole(varChild, pvarRole); + } + + BEGIN_CACHE_ACCESS; + return ::VariantCopy(pvarRole, &mCachedData.mDynamicData.mRole); +} + +HRESULT +AccessibleHandler::get_accState(VARIANT varChild, VARIANT* pvarState) { + if (!pvarState) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accState(varChild, pvarState); + } + + pvarState->vt = VT_I4; + BEGIN_CACHE_ACCESS; + GET_FIELD(mState, pvarState->lVal); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accHelp(VARIANT varChild, BSTR* pszHelp) { + // This matches what AccessibleWrap does + if (!pszHelp) { + return E_INVALIDARG; + } + *pszHelp = nullptr; + return S_FALSE; +} + +HRESULT +AccessibleHandler::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) { + // This matches what AccessibleWrap does + if (!pszHelpFile || !pidTopic) { + return E_INVALIDARG; + } + *pszHelpFile = nullptr; + *pidTopic = 0; + return S_FALSE; +} + +HRESULT +AccessibleHandler::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + if (!pszKeyboardShortcut) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accKeyboardShortcut(varChild, pszKeyboardShortcut); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mKeyboardShortcut, *pszKeyboardShortcut); + return S_OK; +} + +HRESULT +AccessibleHandler::get_accFocus(VARIANT* pvarChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accFocus(pvarChild); +} + +HRESULT +AccessibleHandler::get_accSelection(VARIANT* pvarChildren) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accSelection(pvarChildren); +} + +HRESULT +AccessibleHandler::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + if (!pszDefaultAction) { + return E_INVALIDARG; + } + + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accDefaultAction(varChild, pszDefaultAction); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mDefaultAction, *pszDefaultAction); + return S_OK; +} + +HRESULT +AccessibleHandler::accSelect(long flagsSelect, VARIANT varChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accSelect(flagsSelect, varChild); +} + +HRESULT +AccessibleHandler::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) { + if (varChild.lVal != CHILDID_SELF || !HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, + varChild); + } + + if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight) { + return E_INVALIDARG; + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mLeft, *pxLeft); + GET_FIELD(mTop, *pyTop); + GET_FIELD(mWidth, *pcxWidth); + GET_FIELD(mHeight, *pcyHeight); + return S_OK; +} + +HRESULT +AccessibleHandler::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accNavigate(navDir, varStart, pvarEndUpAt); +} + +HRESULT +AccessibleHandler::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accHitTest(xLeft, yTop, pvarChild); +} + +HRESULT +AccessibleHandler::accDoDefaultAction(VARIANT varChild) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->accDoDefaultAction(varChild); +} + +HRESULT +AccessibleHandler::put_accName(VARIANT varChild, BSTR szName) { + // This matches AccessibleWrap + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::put_accValue(VARIANT varChild, BSTR szValue) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->put_accValue(varChild, szValue); +} + +/*** IAccessible2 ***/ + +HRESULT +AccessibleHandler::get_nRelations(long* nRelations) { + if (!nRelations) { + return E_INVALIDARG; + } + + HRESULT hr; + if (mCachedData.mGeckoBackChannel) { + // If the caller wants nRelations, they will almost certainly want the + // actual relations too. + hr = GetRelationsInfo(); + if (SUCCEEDED(hr)) { + *nRelations = mCachedNRelations; + return S_OK; + } + // We fall back to a normal call if this fails. + } + + hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_nRelations(nRelations); +} + +HRESULT +AccessibleHandler::get_relation(long relationIndex, + IAccessibleRelation** relation) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relation(relationIndex, relation); +} + +HRESULT +AccessibleHandler::get_relations(long maxRelations, + IAccessibleRelation** relations, + long* nRelations) { + if (maxRelations == 0 || !relations || !nRelations) { + return E_INVALIDARG; + } + + // We currently only support retrieval of *all* cached relations at once. + if (mCachedNRelations != -1 && maxRelations >= mCachedNRelations) { + for (long index = 0; index < mCachedNRelations; ++index) { + IARelationData& relData = mCachedRelations[index]; + RefPtr<IAccessibleRelation> hrel(new HandlerRelation(this, relData)); + hrel.forget(&relations[index]); + } + *nRelations = mCachedNRelations; + // Clean up the cache, since we only cache for one call. + // We don't use ClearRelationCache here because that scans for data to free + // in the array and we don't we need that. The HandlerRelation instances + // will handle freeing of the data. + ::CoTaskMemFree(mCachedRelations); + mCachedRelations = nullptr; + mCachedNRelations = -1; + return S_OK; + } + + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relations(maxRelations, relations, nRelations); +} + +HRESULT +AccessibleHandler::role(long* role) { + if (!role) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->role(role); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mIA2Role, *role); + return S_OK; +} + +HRESULT +AccessibleHandler::scrollTo(IA2ScrollType scrollType) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->scrollTo(scrollType); +} + +HRESULT +AccessibleHandler::scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->scrollToPoint(coordinateType, x, y); +} + +HRESULT +AccessibleHandler::get_groupPosition(long* groupLevel, + long* similarItemsInGroup, + long* positionInGroup) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_groupPosition(groupLevel, similarItemsInGroup, + positionInGroup); +} + +HRESULT +AccessibleHandler::get_states(AccessibleStates* states) { + if (!states) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_states(states); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mIA2States, *states); + return S_OK; +} + +HRESULT +AccessibleHandler::get_extendedRole(BSTR* extendedRole) { + // This matches ia2Accessible + if (!extendedRole) { + return E_INVALIDARG; + } + *extendedRole = nullptr; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_localizedExtendedRole(BSTR* localizedExtendedRole) { + // This matches ia2Accessible + if (!localizedExtendedRole) { + return E_INVALIDARG; + } + *localizedExtendedRole = nullptr; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_nExtendedStates(long* nExtendedStates) { + // This matches ia2Accessible + if (!nExtendedStates) { + return E_INVALIDARG; + } + *nExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_extendedStates(long maxExtendedStates, + BSTR** extendedStates, + long* nExtendedStates) { + // This matches ia2Accessible + if (!extendedStates || !nExtendedStates) { + return E_INVALIDARG; + } + *extendedStates = nullptr; + *nExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_localizedExtendedStates(long maxLocalizedExtendedStates, + BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) { + // This matches ia2Accessible + if (!localizedExtendedStates || !nLocalizedExtendedStates) { + return E_INVALIDARG; + } + *localizedExtendedStates = nullptr; + *nLocalizedExtendedStates = 0; + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_uniqueID(long* uniqueID) { + if (!uniqueID) { + return E_INVALIDARG; + } + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_uniqueID(uniqueID); + } + *uniqueID = mCachedData.mDynamicData.mUniqueId; + return S_OK; +} + +HRESULT +AccessibleHandler::get_windowHandle(HWND* windowHandle) { + if (!windowHandle) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_windowHandle(windowHandle); + } + + BEGIN_CACHE_ACCESS; + long hwnd = 0; + GET_FIELD(mHwnd, hwnd); + *windowHandle = reinterpret_cast<HWND>(uintptr_t(hwnd)); + return S_OK; +} + +HRESULT +AccessibleHandler::get_indexInParent(long* indexInParent) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_indexInParent(indexInParent); +} + +HRESULT +AccessibleHandler::get_locale(IA2Locale* locale) { + if (!locale) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_locale(locale); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mIA2Locale.language, locale->language); + GET_BSTR(mIA2Locale.country, locale->country); + GET_BSTR(mIA2Locale.variant, locale->variant); + return S_OK; +} + +HRESULT +AccessibleHandler::get_attributes(BSTR* attributes) { + if (!attributes) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_attributes(attributes); + } + + BEGIN_CACHE_ACCESS; + GET_BSTR(mAttributes, *attributes); + return S_OK; +} + +/*** IAccessible2_2 ***/ + +HRESULT +AccessibleHandler::get_attribute(BSTR name, VARIANT* attribute) { + // Not yet implemented by ia2Accessible. + // Once ia2Accessible implements this, we could either pass it through + // or we could extract these individually from cached mAttributes. + // The latter should be considered if traffic warrants it. + return E_NOTIMPL; +} + +HRESULT +AccessibleHandler::get_accessibleWithCaret(IUnknown** accessible, + long* caretOffset) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_accessibleWithCaret(accessible, caretOffset); +} + +HRESULT +AccessibleHandler::get_relationTargetsOfType(BSTR type, long maxTargets, + IUnknown*** targets, + long* nTargets) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_relationTargetsOfType(type, maxTargets, targets, + nTargets); +} + +/*** IAccessible2_3 ***/ + +HRESULT +AccessibleHandler::get_selectionRanges(IA2Range** ranges, long* nRanges) { + HRESULT hr = ResolveIA2(); + if (FAILED(hr)) { + return hr; + } + return mIA2PassThru->get_selectionRanges(ranges, nRanges); +} + +/*** IServiceProvider ***/ + +HRESULT +AccessibleHandler::QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + /* We're taking advantage of the fact that we are implementing IA2 as part + of our own object to implement this just like a QI. */ + if (aIid == IID_IAccessible2_3 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2) { + RefPtr<NEWEST_IA2_INTERFACE> ia2(this); + ia2.forget(aOutInterface); + return S_OK; + } + + // JAWS uses QueryService for these, but QI will work just fine and we can + // thus avoid a cross-process call. More importantly, if QS is used, the + // handler won't get used for that object, so our caching won't be used. + if (aIid == IID_IAccessibleAction || aIid == IID_IAccessibleText) { + return InternalQueryInterface(aIid, aOutInterface); + } + + for (uint32_t i = 0; i < ArrayLength(kUnsupportedServices); ++i) { + if (aServiceId == kUnsupportedServices[i]) { + return E_NOINTERFACE; + } + } + + if (!mServProvPassThru) { + RefPtr<IUnknown> proxy(GetProxy()); + if (!proxy) { + return E_UNEXPECTED; + } + + HRESULT hr = proxy->QueryInterface( + IID_IServiceProvider, reinterpret_cast<void**>(&mServProvPassThru)); + if (FAILED(hr)) { + return hr; + } + + // mServProvPassThru is a weak reference (see comments in + // AccessibleHandler.h) + mServProvPassThru->Release(); + } + + return mServProvPassThru->QueryService(aServiceId, aIid, aOutInterface); +} + +/*** IProvideClassInfo ***/ + +HRESULT +AccessibleHandler::GetClassInfo(ITypeInfo** aOutTypeInfo) { + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + + return ctl->GetHandlerTypeInfo(aOutTypeInfo); +} + +/*** IAccessibleAction ***/ + +HRESULT +AccessibleHandler::nActions(long* nActions) { + if (!nActions) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->nActions(nActions); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mNActions, *nActions); + return S_OK; +} + +HRESULT +AccessibleHandler::doAction(long actionIndex) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->doAction(actionIndex); +} + +HRESULT +AccessibleHandler::get_description(long actionIndex, BSTR* description) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_description(actionIndex, description); +} + +HRESULT +AccessibleHandler::get_keyBinding(long actionIndex, long nMaxBindings, + BSTR** keyBindings, long* nBindings) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_keyBinding(actionIndex, nMaxBindings, + keyBindings, nBindings); +} + +HRESULT +AccessibleHandler::get_name(long actionIndex, BSTR* name) { + if (!name) { + return E_INVALIDARG; + } + + if (HasPayload()) { + if (actionIndex >= mCachedData.mDynamicData.mNActions) { + // Action does not exist. + return E_INVALIDARG; + } + + if (actionIndex == 0) { + // same as accDefaultAction. + GET_BSTR(mDefaultAction, *name); + return S_OK; + } + } + + // At this point, there's either no payload or actionIndex is > 0. + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_name(actionIndex, name); +} + +HRESULT +AccessibleHandler::get_localizedName(long actionIndex, BSTR* localizedName) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_localizedName(actionIndex, localizedName); +} + +/*** IAccessibleHyperlink ***/ + +HRESULT +AccessibleHandler::get_anchor(long index, VARIANT* anchor) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_anchor(index, anchor); +} + +HRESULT +AccessibleHandler::get_anchorTarget(long index, VARIANT* anchorTarget) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_anchorTarget(index, anchorTarget); +} + +HRESULT +AccessibleHandler::get_startIndex(long* index) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_startIndex(index); +} + +HRESULT +AccessibleHandler::get_endIndex(long* index) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_endIndex(index); +} + +HRESULT +AccessibleHandler::get_valid(boolean* valid) { + HRESULT hr = ResolveIAHyperlink(); + if (FAILED(hr)) { + return hr; + } + return mIAHyperlinkPassThru->get_valid(valid); +} + +/*** IAccessibleTableCell ***/ + +HRESULT +AccessibleHandler::get_columnExtent(long* nColumnsSpanned) { + if (!nColumnsSpanned) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_columnExtent(nColumnsSpanned); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mColumnExtent, *nColumnsSpanned); + return S_OK; +} + +HRESULT +AccessibleHandler::get_columnHeaderCells(IUnknown*** cellAccessibles, + long* nColumnHeaderCells) { + if (!cellAccessibles || !nColumnHeaderCells) { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + if (HasPayload()) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + *nColumnHeaderCells = mCachedData.mDynamicData.mNColumnHeaderCells; + *cellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * *nColumnHeaderCells)); + long i; + for (i = 0; i < *nColumnHeaderCells; ++i) { + RefPtr<AccessibleHandler> headerAcc; + hr = ctl->GetCachedAccessible( + mCachedData.mDynamicData.mColumnHeaderCellIds[i], + getter_AddRefs(headerAcc)); + if (FAILED(hr)) { + break; + } + hr = headerAcc->QueryInterface(IID_IUnknown, + (void**)&(*cellAccessibles)[i]); + if (FAILED(hr)) { + break; + } + } + if (SUCCEEDED(hr)) { + return S_OK; + } + // If we failed to get any of the headers from the cache, don't use the + // cache at all. We need to clean up anything we did so far. + long failedHeader = i; + for (i = 0; i < failedHeader; ++i) { + (*cellAccessibles)[i]->Release(); + } + ::CoTaskMemFree(*cellAccessibles); + *cellAccessibles = nullptr; + } + + hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_columnHeaderCells(cellAccessibles, + nColumnHeaderCells); +} + +HRESULT +AccessibleHandler::get_columnIndex(long* columnIndex) { + if (!columnIndex) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_columnIndex(columnIndex); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mColumnIndex, *columnIndex); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowExtent(long* nRowsSpanned) { + if (!nRowsSpanned) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowExtent(nRowsSpanned); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowExtent, *nRowsSpanned); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowHeaderCells(IUnknown*** cellAccessibles, + long* nRowHeaderCells) { + if (!cellAccessibles || !nRowHeaderCells) { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + if (HasPayload()) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + *nRowHeaderCells = mCachedData.mDynamicData.mNRowHeaderCells; + *cellAccessibles = static_cast<IUnknown**>( + ::CoTaskMemAlloc(sizeof(IUnknown*) * *nRowHeaderCells)); + long i; + for (i = 0; i < *nRowHeaderCells; ++i) { + RefPtr<AccessibleHandler> headerAcc; + hr = ctl->GetCachedAccessible( + mCachedData.mDynamicData.mRowHeaderCellIds[i], + getter_AddRefs(headerAcc)); + if (FAILED(hr)) { + break; + } + hr = headerAcc->QueryInterface(IID_IUnknown, + (void**)&(*cellAccessibles)[i]); + if (FAILED(hr)) { + break; + } + } + if (SUCCEEDED(hr)) { + return S_OK; + } + // If we failed to get any of the headers from the cache, don't use the + // cache at all. We need to clean up anything we did so far. + long failedHeader = i; + for (i = 0; i < failedHeader; ++i) { + (*cellAccessibles)[i]->Release(); + } + ::CoTaskMemFree(*cellAccessibles); + *cellAccessibles = nullptr; + } + + hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_rowHeaderCells(cellAccessibles, + nRowHeaderCells); +} + +HRESULT +AccessibleHandler::get_rowIndex(long* rowIndex) { + if (!rowIndex) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowIndex(rowIndex); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowIndex, *rowIndex); + return S_OK; +} + +HRESULT +AccessibleHandler::get_isSelected(boolean* isSelected) { + if (!isSelected) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_isSelected(isSelected); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mCellIsSelected, *isSelected); + return S_OK; +} + +HRESULT +AccessibleHandler::get_rowColumnExtents(long* row, long* column, + long* rowExtents, long* columnExtents, + boolean* isSelected) { + if (!row || !column || !rowExtents || !columnExtents || !isSelected) { + return E_INVALIDARG; + } + + if (!HasPayload()) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + return mIATableCellPassThru->get_rowColumnExtents( + row, column, rowExtents, columnExtents, isSelected); + } + + BEGIN_CACHE_ACCESS; + GET_FIELD(mRowIndex, *row); + GET_FIELD(mColumnIndex, *column); + GET_FIELD(mRowExtent, *rowExtents); + GET_FIELD(mColumnExtent, *columnExtents); + GET_FIELD(mCellIsSelected, *isSelected); + return S_OK; +} + +HRESULT +AccessibleHandler::get_table(IUnknown** table) { + HRESULT hr = ResolveIATableCell(); + if (FAILED(hr)) { + return hr; + } + + return mIATableCellPassThru->get_table(table); +} + +/*** IAccessibleText ***/ + +HRESULT +AccessibleHandler::addSelection(long startOffset, long endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->addSelection(startOffset, endOffset); +} + +HRESULT +AccessibleHandler::get_attributes(long offset, long* startOffset, + long* endOffset, BSTR* textAttributes) { + if (!startOffset || !endOffset || !textAttributes) { + return E_INVALIDARG; + } + + if (mCachedNTextAttribRuns >= 0) { + // We have cached attributes. + for (long index = 0; index < mCachedNTextAttribRuns; ++index) { + auto& attribRun = mCachedTextAttribRuns[index]; + if (attribRun.start <= offset && offset < attribRun.end) { + *startOffset = attribRun.start; + *endOffset = attribRun.end; + *textAttributes = attribRun.text; + // The caller will clean this up. + // (We only keep each cached attribute run for one call.) + attribRun.text = nullptr; + // The cache for this run is now invalid, so don't visit it again. + attribRun.end = 0; + return S_OK; + } + } + } + + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_attributes(offset, startOffset, endOffset, + textAttributes); +} + +HRESULT +AccessibleHandler::get_caretOffset(long* offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_caretOffset(offset); +} + +HRESULT +AccessibleHandler::get_characterExtents(long offset, + enum IA2CoordinateType coordType, + long* x, long* y, long* width, + long* height) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_characterExtents(offset, coordType, x, y, + width, height); +} + +HRESULT +AccessibleHandler::get_nSelections(long* nSelections) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + hr = mIAHypertextPassThru->get_nSelections(nSelections); + if (SUCCEEDED(hr) && *nSelections == 0 && HasPayload()) { + BEGIN_CACHE_ACCESS; + if (mCachedData.mDynamicData.mIA2Role == ROLE_SYSTEM_DOCUMENT) { + RefPtr<AccessibleHandlerControl> ctl( + gControlFactory.GetOrCreateSingleton()); + if (!ctl) { + return E_OUTOFMEMORY; + } + if (ctl->IsA11ySuppressedForClipboardCopy()) { + // Bug 1798098: Windows Suggested Actions (introduced in Windows 11 + // 22H2) might walk the document a11y tree using UIA whenever anything + // is copied to the clipboard. This causes an unacceptable hang, + // particularly when the cache is disabled. It walks using + // IAccessibleText/IAccessibleHyperText if the document reports no + // selection, so we lie here and say that there is a selection even + // though there isn't. It will subsequently call get_selection, which + // will fail, but this hack here seems to be enough to avoid further + // text calls. + *nSelections = 1; + } + } + } + return hr; +} + +HRESULT +AccessibleHandler::get_offsetAtPoint(long x, long y, + enum IA2CoordinateType coordType, + long* offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_offsetAtPoint(x, y, coordType, offset); +} + +HRESULT +AccessibleHandler::get_selection(long selectionIndex, long* startOffset, + long* endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_selection(selectionIndex, startOffset, + endOffset); +} + +HRESULT +AccessibleHandler::get_text(long startOffset, long endOffset, BSTR* text) { + if (!text) { + return E_INVALIDARG; + } + + HRESULT hr; + if (mCachedData.mGeckoBackChannel && startOffset == 0 && + endOffset == IA2_TEXT_OFFSET_LENGTH) { + // If the caller is retrieving all text, they will probably want all + // hyperlinks and attributes as well. + hr = GetAllTextInfo(text); + if (SUCCEEDED(hr)) { + return hr; + } + // We fall back to a normal call if this fails. + } + + hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_text(startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textBeforeOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textBeforeOffset( + offset, boundaryType, startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textAfterOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textAfterOffset( + offset, boundaryType, startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::get_textAtOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_textAtOffset(offset, boundaryType, + startOffset, endOffset, text); +} + +HRESULT +AccessibleHandler::removeSelection(long selectionIndex) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->removeSelection(selectionIndex); +} + +HRESULT +AccessibleHandler::setCaretOffset(long offset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->setCaretOffset(offset); +} + +HRESULT +AccessibleHandler::setSelection(long selectionIndex, long startOffset, + long endOffset) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->setSelection(selectionIndex, startOffset, + endOffset); +} + +HRESULT +AccessibleHandler::get_nCharacters(long* nCharacters) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_nCharacters(nCharacters); +} + +HRESULT +AccessibleHandler::scrollSubstringTo(long startIndex, long endIndex, + enum IA2ScrollType scrollType) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->scrollSubstringTo(startIndex, endIndex, + scrollType); +} + +HRESULT +AccessibleHandler::scrollSubstringToPoint(long startIndex, long endIndex, + enum IA2CoordinateType coordinateType, + long x, long y) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->scrollSubstringToPoint(startIndex, endIndex, + coordinateType, x, y); +} + +HRESULT +AccessibleHandler::get_newText(IA2TextSegment* newText) { + if (!newText) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + return S_OK; + } + + long id; + HRESULT hr = this->get_uniqueID(&id); + if (FAILED(hr)) { + return hr; + } + + return ctl->GetNewText(id, WrapNotNull(newText)); +} + +HRESULT +AccessibleHandler::get_oldText(IA2TextSegment* oldText) { + if (!oldText) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetSingleton()); + MOZ_ASSERT(ctl); + if (!ctl) { + return S_OK; + } + + long id; + HRESULT hr = this->get_uniqueID(&id); + if (FAILED(hr)) { + return hr; + } + + return ctl->GetOldText(id, WrapNotNull(oldText)); +} + +/*** IAccessibleHypertext ***/ + +HRESULT +AccessibleHandler::get_nHyperlinks(long* hyperlinkCount) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_nHyperlinks(hyperlinkCount); +} + +HRESULT +AccessibleHandler::get_hyperlink(long index, IAccessibleHyperlink** hyperlink) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlink(index, hyperlink); +} + +HRESULT +AccessibleHandler::get_hyperlinkIndex(long charIndex, long* hyperlinkIndex) { + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlinkIndex(charIndex, hyperlinkIndex); +} + +/*** IAccessibleHypertext2 ***/ + +HRESULT +AccessibleHandler::get_hyperlinks(IAccessibleHyperlink*** hyperlinks, + long* nHyperlinks) { + if (!hyperlinks || !nHyperlinks) { + return E_INVALIDARG; + } + + if (mCachedNHyperlinks >= 0) { + // We have cached hyperlinks. + *hyperlinks = mCachedHyperlinks; + *nHyperlinks = mCachedNHyperlinks; + // The client will clean these up. (We only keep the cache for one call.) + mCachedHyperlinks = nullptr; + mCachedNHyperlinks = -1; + return mCachedNHyperlinks == 0 ? S_FALSE : S_OK; + } + + HRESULT hr = ResolveIAHypertext(); + if (FAILED(hr)) { + return hr; + } + + return mIAHypertextPassThru->get_hyperlinks(hyperlinks, nHyperlinks); +} + +} // namespace a11y +} // namespace mozilla + +extern "C" HRESULT __stdcall ProxyDllCanUnloadNow(); + +extern "C" HRESULT __stdcall DllCanUnloadNow() { + return mozilla::mscom::Module::CanUnload() && ProxyDllCanUnloadNow(); +} + +extern "C" HRESULT __stdcall ProxyDllGetClassObject(REFCLSID aClsid, + REFIID aIid, + LPVOID* aOutInterface); + +extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID aClsid, REFIID aIid, + LPVOID* aOutInterface) { + if (aClsid == CLSID_AccessibleHandler) { + return mozilla::a11y::sHandlerFactory.QueryInterface(aIid, aOutInterface); + } + return ProxyDllGetClassObject(aClsid, aIid, aOutInterface); +} + +extern "C" BOOL WINAPI ProxyDllMain(HINSTANCE aInstDll, DWORD aReason, + LPVOID aReserved); + +BOOL WINAPI DllMain(HINSTANCE aInstDll, DWORD aReason, LPVOID aReserved) { + if (aReason == DLL_PROCESS_ATTACH) { + DisableThreadLibraryCalls((HMODULE)aInstDll); + } + // This is required for ProxyDllRegisterServer to work correctly + return ProxyDllMain(aInstDll, aReason, aReserved); +} + +extern "C" HRESULT __stdcall ProxyDllRegisterServer(); + +extern "C" HRESULT __stdcall DllRegisterServer() { + HRESULT hr = mozilla::mscom::Handler::Register(CLSID_AccessibleHandler); + if (FAILED(hr)) { + return hr; + } + + return ProxyDllRegisterServer(); +} + +extern "C" HRESULT __stdcall ProxyDllUnregisterServer(); + +extern "C" HRESULT __stdcall DllUnregisterServer() { + HRESULT hr = mozilla::mscom::Handler::Unregister(CLSID_AccessibleHandler); + if (FAILED(hr)) { + return hr; + } + + return ProxyDllUnregisterServer(); +} + +extern "C" HRESULT __stdcall RegisterMsix() { + return mozilla::mscom::Handler::Register(CLSID_AccessibleHandler, + /* aMsixContainer */ true); +} diff --git a/accessible/ipc/win/handler/AccessibleHandler.def b/accessible/ipc/win/handler/AccessibleHandler.def new file mode 100644 index 0000000000..f1f3513b0e --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.def @@ -0,0 +1,12 @@ +;+# This Source Code Form is subject to the terms of the Mozilla Public +;+# License, v. 2.0. If a copy of the MPL was not distributed with this +;+# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY AccessibleHandler.dll + +EXPORTS DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + GetProxyDllInfo PRIVATE + RegisterMsix PRIVATE diff --git a/accessible/ipc/win/handler/AccessibleHandler.h b/accessible/ipc/win/handler/AccessibleHandler.h new file mode 100644 index 0000000000..f99091d6c3 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.h @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_AccessibleHandler_h +#define mozilla_a11y_AccessibleHandler_h + +#define NEWEST_IA2_BASENAME Accessible2_3 + +#define __GENIFACE(base) I##base +#define INTERFACEFOR(base) __GENIFACE(base) +#define NEWEST_IA2_INTERFACE INTERFACEFOR(NEWEST_IA2_BASENAME) + +#define __GENIID(iface) IID_##iface +#define IIDFOR(iface) __GENIID(iface) +#define NEWEST_IA2_IID IIDFOR(NEWEST_IA2_INTERFACE) + +#if defined(__midl) || defined(__WIDL__) + +import "Accessible2_3.idl"; + +#else + +# include "HandlerData.h" + +# include <windows.h> + +namespace mozilla { +namespace a11y { + +static const GUID kUnsupportedServices[] = { + // clang-format off + // Unknown, queried by Windows on devices with touch screens or similar devices + // connected. + {0x33f139ee, 0xe509, 0x47f7, {0xbf, 0x39, 0x83, 0x76, 0x44, 0xf7, 0x45, 0x76}}, + // Unknown, queried by Windows + {0xFDA075CF, 0x7C8B, 0x498C, { 0xB5, 0x14, 0xA9, 0xCB, 0x52, 0x1B, 0xBF, 0xB4 }}, + // Unknown, queried by Windows + {0x8EDAA462, 0x21F4, 0x4C87, { 0xA0, 0x12, 0xB3, 0xCD, 0xA3, 0xAB, 0x01, 0xFC }}, + // Unknown, queried by Windows + {0xacd46652, 0x829d, 0x41cb, { 0xa5, 0xfc, 0x17, 0xac, 0xf4, 0x36, 0x61, 0xac }}, + // SID_IsUIAutomationObject (undocumented), queried by Windows + {0xb96fdb85, 0x7204, 0x4724, { 0x84, 0x2b, 0xc7, 0x05, 0x9d, 0xed, 0xb9, 0xd0 }}, + // IIS_IsOleaccProxy (undocumented), queried by Windows + {0x902697FA, 0x80E4, 0x4560, {0x80, 0x2A, 0xA1, 0x3F, 0x22, 0xA6, 0x47, 0x09}}, + // IID_IHTMLElement, queried by JAWS + {0x3050F1FF, 0x98B5, 0x11CF, {0xBB, 0x82, 0x00, 0xAA, 0x00, 0xBD, 0xCE, 0x0B}} + // clang-format on +}; + +} +} // namespace mozilla + +# if !defined(MOZILLA_INTERNAL_API) + +# include "Accessible2_3.h" +# include "AccessibleHyperlink.h" +# include "AccessibleHypertext2.h" +# include "AccessibleTableCell.h" +# include "Handler.h" +# include "mozilla/mscom/StructStream.h" +# include "mozilla/UniquePtr.h" + +# include <ocidl.h> +# include <servprov.h> + +namespace mozilla { +namespace a11y { + +class AccessibleHandler final : public mscom::Handler, + public NEWEST_IA2_INTERFACE, + public IServiceProvider, + public IProvideClassInfo, + public IAccessibleHyperlink, + public IAccessibleTableCell, + public IAccessibleHypertext2 { + public: + static HRESULT Create(IUnknown* aOuter, REFIID aIid, void** aOutInterface); + + // mscom::Handler + HRESULT QueryHandlerInterface(IUnknown* aProxyUnknown, REFIID aIid, + void** aOutInterface) override; + HRESULT ReadHandlerPayload(IStream* aStream, REFIID aIid) override; + + REFIID MarshalAs(REFIID aRequestedIid) override; + HRESULT GetMarshalInterface(REFIID aMarshalAsIid, NotNull<IUnknown*> aProxy, + NotNull<IID*> aOutIid, + NotNull<IUnknown**> aOutUnk) override; + HRESULT GetHandlerPayloadSize(REFIID aIid, DWORD* aOutPayloadSize) override; + HRESULT WriteHandlerPayload(IStream* aStream, REFIID aIId) override; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IDispatch + STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override; + STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) override; + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) override; + + // IAccessible + STDMETHODIMP get_accParent(IDispatch** ppdispParent) override; + STDMETHODIMP get_accChildCount(long* pcountChildren) override; + STDMETHODIMP get_accChild(VARIANT varChild, IDispatch** ppdispChild) override; + STDMETHODIMP get_accName(VARIANT varChild, BSTR* pszName) override; + STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pszValue) override; + STDMETHODIMP get_accDescription(VARIANT varChild, + BSTR* pszDescription) override; + STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole) override; + STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState) override; + STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pszHelp) override; + STDMETHODIMP get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) override; + STDMETHODIMP get_accFocus(VARIANT* pvarChild) override; + STDMETHODIMP get_accSelection(VARIANT* pvarChildren) override; + STDMETHODIMP get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) override; + STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override; + STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) override; + STDMETHODIMP accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) override; + STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChild) override; + STDMETHODIMP accDoDefaultAction(VARIANT varChild) override; + STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override; + STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override; + + // IAccessible2 + STDMETHODIMP get_nRelations(long* nRelations) override; + STDMETHODIMP get_relation(long relationIndex, + IAccessibleRelation** relation) override; + STDMETHODIMP get_relations(long maxRelations, IAccessibleRelation** relations, + long* nRelations) override; + STDMETHODIMP role(long* role) override; + STDMETHODIMP scrollTo(IA2ScrollType scrollType) override; + STDMETHODIMP scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) override; + STDMETHODIMP get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) override; + STDMETHODIMP get_states(AccessibleStates* states) override; + STDMETHODIMP get_extendedRole(BSTR* extendedRole) override; + STDMETHODIMP get_localizedExtendedRole(BSTR* localizedExtendedRole) override; + STDMETHODIMP get_nExtendedStates(long* nExtendedStates) override; + STDMETHODIMP get_extendedStates(long maxExtendedStates, BSTR** extendedStates, + long* nExtendedStates) override; + STDMETHODIMP get_localizedExtendedStates( + long maxLocalizedExtendedStates, BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) override; + STDMETHODIMP get_uniqueID(long* uniqueID) override; + STDMETHODIMP get_windowHandle(HWND* windowHandle) override; + STDMETHODIMP get_indexInParent(long* indexInParent) override; + STDMETHODIMP get_locale(IA2Locale* locale) override; + STDMETHODIMP get_attributes(BSTR* attributes) override; + + // IAccessible2_2 + STDMETHODIMP get_attribute(BSTR name, VARIANT* attribute) override; + STDMETHODIMP get_accessibleWithCaret(IUnknown** accessible, + long* caretOffset) override; + STDMETHODIMP get_relationTargetsOfType(BSTR type, long maxTargets, + IUnknown*** targets, + long* nTargets) override; + + // IAccessible2_3 + STDMETHODIMP get_selectionRanges(IA2Range** ranges, long* nRanges) override; + + // IServiceProvider + STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) override; + + // IProvideClassInfo + STDMETHODIMP GetClassInfo(ITypeInfo** aOutTypeInfo) override; + + // IAccessibleAction + STDMETHODIMP nActions(long* nActions) override; + STDMETHODIMP doAction(long actionIndex) override; + STDMETHODIMP get_description(long actionIndex, BSTR* description) override; + STDMETHODIMP get_keyBinding(long actionIndex, long nMaxBindings, + BSTR** keyBindings, long* nBindings) override; + STDMETHODIMP get_name(long actionIndex, BSTR* name) override; + STDMETHODIMP get_localizedName(long actionIndex, + BSTR* localizedName) override; + + // IAccessibleHyperlink + STDMETHODIMP get_anchor(long index, VARIANT* anchor) override; + STDMETHODIMP get_anchorTarget(long index, VARIANT* anchorTarget) override; + STDMETHODIMP get_startIndex(long* index) override; + STDMETHODIMP get_endIndex(long* index) override; + STDMETHODIMP get_valid(boolean* valid) override; + + // IAccessibleTableCell + STDMETHODIMP get_columnExtent(long* nColumnsSpanned) override; + STDMETHODIMP get_columnHeaderCells(IUnknown*** cellAccessibles, + long* nColumnHeaderCells) override; + STDMETHODIMP get_columnIndex(long* columnIndex) override; + STDMETHODIMP get_rowExtent(long* nRowsSpanned) override; + STDMETHODIMP get_rowHeaderCells(IUnknown*** cellAccessibles, + long* nRowHeaderCells) override; + STDMETHODIMP get_rowIndex(long* rowIndex) override; + STDMETHODIMP get_isSelected(boolean* isSelected) override; + STDMETHODIMP get_rowColumnExtents(long* row, long* column, long* rowExtents, + long* columnExtents, + boolean* isSelected) override; + STDMETHODIMP get_table(IUnknown** table) override; + + // IAccessibleText + STDMETHODIMP addSelection(long startOffset, long endOffset) override; + STDMETHODIMP get_attributes(long offset, long* startOffset, long* endOffset, + BSTR* textAttributes) override; + STDMETHODIMP get_caretOffset(long* offset) override; + STDMETHODIMP get_characterExtents(long offset, + enum IA2CoordinateType coordType, long* x, + long* y, long* width, + long* height) override; + STDMETHODIMP get_nSelections(long* nSelections) override; + STDMETHODIMP get_offsetAtPoint(long x, long y, + enum IA2CoordinateType coordType, + long* offset) override; + STDMETHODIMP get_selection(long selectionIndex, long* startOffset, + long* endOffset) override; + STDMETHODIMP get_text(long startOffset, long endOffset, BSTR* text) override; + STDMETHODIMP get_textBeforeOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP get_textAfterOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP get_textAtOffset(long offset, + enum IA2TextBoundaryType boundaryType, + long* startOffset, long* endOffset, + BSTR* text) override; + STDMETHODIMP removeSelection(long selectionIndex) override; + STDMETHODIMP setCaretOffset(long offset) override; + STDMETHODIMP setSelection(long selectionIndex, long startOffset, + long endOffset) override; + STDMETHODIMP get_nCharacters(long* nCharacters) override; + STDMETHODIMP scrollSubstringTo(long startIndex, long endIndex, + enum IA2ScrollType scrollType) override; + STDMETHODIMP scrollSubstringToPoint(long startIndex, long endIndex, + enum IA2CoordinateType coordinateType, + long x, long y) override; + STDMETHODIMP get_newText(IA2TextSegment* newText) override; + STDMETHODIMP get_oldText(IA2TextSegment* oldText) override; + + // IAccessibleHypertext + STDMETHODIMP get_nHyperlinks(long* hyperlinkCount) override; + STDMETHODIMP get_hyperlink(long index, + IAccessibleHyperlink** hyperlink) override; + STDMETHODIMP get_hyperlinkIndex(long charIndex, + long* hyperlinkIndex) override; + + // IAccessibleHypertext2 + STDMETHODIMP get_hyperlinks(IAccessibleHyperlink*** hyperlinks, + long* nHyperlinks) override; + + private: + AccessibleHandler(IUnknown* aOuter, HRESULT* aResult); + virtual ~AccessibleHandler(); + + HRESULT ResolveIA2(); + HRESULT ResolveIDispatch(); + HRESULT ResolveIAHyperlink(); + HRESULT ResolveIAHypertext(); + HRESULT ResolveIATableCell(); + HRESULT MaybeUpdateCachedData(); + HRESULT GetAllTextInfo(BSTR* aText); + void ClearTextCache(); + HRESULT GetRelationsInfo(); + void ClearRelationCache(); + + RefPtr<IUnknown> mDispatchUnk; + /** + * Handlers aggregate their proxies. This means that their proxies delegate + * their IUnknown implementation to us. + * + * mDispatchUnk and the result of Handler::GetProxy() are both strong + * references to the aggregated objects. OTOH, any interfaces that are QI'd + * from those aggregated objects have delegated unknowns. + * + * AddRef'ing an interface with a delegated unknown ends up incrementing the + * refcount of the *aggregator*. Since we are the aggregator of mDispatchUnk + * and of the wrapped proxy, holding a strong reference to any interfaces + * QI'd off of those objects would create a reference cycle. + * + * We may hold onto pointers to those references, but when we query them we + * must immediately Release() them to prevent these cycles. + * + * It is safe for us to use these raw pointers because the aggregated + * objects's lifetimes are proper subsets of our own lifetime. + */ + IDispatch* mDispatch; // weak + NEWEST_IA2_INTERFACE* mIA2PassThru; // weak + IServiceProvider* mServProvPassThru; // weak + IAccessibleHyperlink* mIAHyperlinkPassThru; // weak + IAccessibleTableCell* mIATableCellPassThru; // weak + IAccessibleHypertext2* mIAHypertextPassThru; // weak + IA2Payload mCachedData; + bool mCachedDynamicDataMarshaledByCom; + UniquePtr<mscom::StructToStream> mSerializer; + uint32_t mCacheGen; + IAccessibleHyperlink** mCachedHyperlinks; + long mCachedNHyperlinks; + IA2TextSegment* mCachedTextAttribRuns; + long mCachedNTextAttribRuns; + IARelationData* mCachedRelations; + long mCachedNRelations; + bool mIsEmulatedWindow; +}; + +inline static BSTR CopyBSTR(BSTR aSrc) { + if (!aSrc) { + return nullptr; + } + + return ::SysAllocStringLen(aSrc, ::SysStringLen(aSrc)); +} + +} // namespace a11y +} // namespace mozilla + +# endif // !defined(MOZILLA_INTERNAL_API) + +#endif // defined(__midl) + +#endif // mozilla_a11y_AccessibleHandler_h diff --git a/accessible/ipc/win/handler/AccessibleHandler.rc b/accessible/ipc/win/handler/AccessibleHandler.rc new file mode 100644 index 0000000000..3dac2f89d5 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandler.rc @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +1 typelib HandlerData.tlb diff --git a/accessible/ipc/win/handler/AccessibleHandlerControl.cpp b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp new file mode 100644 index 0000000000..0d0bd4d71d --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandlerControl.cpp @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "AccessibleHandlerControl.h" + +#include <utility> + +#include "AccessibleEventId.h" +#include "AccessibleHandler.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory; + +namespace detail { + +TextChange::TextChange() : mIA2UniqueId(0), mIsInsert(false), mText() {} + +TextChange::TextChange(long aIA2UniqueId, bool aIsInsert, + NotNull<IA2TextSegment*> aText) + : mIA2UniqueId(aIA2UniqueId), + mIsInsert(aIsInsert), + mText{BSTRCopy(aText->text), aText->start, aText->end} {} + +TextChange::TextChange(TextChange&& aOther) : mText() { + *this = std::move(aOther); +} + +TextChange::TextChange(const TextChange& aOther) : mText() { *this = aOther; } + +TextChange& TextChange::operator=(TextChange&& aOther) { + mIA2UniqueId = aOther.mIA2UniqueId; + mIsInsert = aOther.mIsInsert; + aOther.mIA2UniqueId = 0; + ::SysFreeString(mText.text); + mText = aOther.mText; + aOther.mText.text = nullptr; + return *this; +} + +TextChange& TextChange::operator=(const TextChange& aOther) { + mIA2UniqueId = aOther.mIA2UniqueId; + mIsInsert = aOther.mIsInsert; + ::SysFreeString(mText.text); + mText = {BSTRCopy(aOther.mText.text), aOther.mText.start, aOther.mText.end}; + return *this; +} + +TextChange::~TextChange() { ::SysFreeString(mText.text); } + +HRESULT +TextChange::GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment) { + if (mIsInsert || aIA2UniqueId != mIA2UniqueId) { + return S_OK; + } + + return SegCopy(*aOutOldSegment, mText); +} + +HRESULT +TextChange::GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment) { + if (!mIsInsert || aIA2UniqueId != mIA2UniqueId) { + return S_OK; + } + + return SegCopy(*aOutNewSegment, mText); +} + +/* static */ +BSTR TextChange::BSTRCopy(const BSTR& aIn) { + return ::SysAllocStringLen(aIn, ::SysStringLen(aIn)); +} + +/* static */ +HRESULT TextChange::SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc) { + aDest = {BSTRCopy(aSrc.text), aSrc.start, aSrc.end}; + if (aSrc.text && !aDest.text) { + return E_OUTOFMEMORY; + } + if (!::SysStringLen(aDest.text)) { + return S_FALSE; + } + return S_OK; +} + +} // namespace detail + +HRESULT +AccessibleHandlerControl::Create(AccessibleHandlerControl** aOutObject) { + if (!aOutObject) { + return E_INVALIDARG; + } + + RefPtr<AccessibleHandlerControl> ctl(new AccessibleHandlerControl()); + ctl.forget(aOutObject); + return S_OK; +} + +AccessibleHandlerControl::AccessibleHandlerControl() + : mIsRegistered(false), + mCacheGen(0), + mIA2Proxy(mscom::RegisterProxy(L"ia2marshal.dll")), + mHandlerProxy(mscom::RegisterProxy()) { + MOZ_ASSERT(mIA2Proxy); +} + +IMPL_IUNKNOWN1(AccessibleHandlerControl, IHandlerControl) + +HRESULT +AccessibleHandlerControl::Invalidate() { + ++mCacheGen; + // We can't just call mAccessibleCache.clear() because doing so would release + // remote objects, making remote COM calls. Since this is an STA, an incoming + // COM call might be handled which might marshal an AccessibleHandler, + // which in turn might add itself to mAccessibleCache. Since we'd be in the + // middle of mutating mAccessibleCache, that might cause a crash. Instead, + // swap mAccessibleCache into a temporary map first, which will empty + // mAccessibleCache without releasing remote objects. Once mAccessibleCache + // is empty, it's safe to let the temporary map be destroyed when it goes + // out of scope. Remote calls will be made, but nothing will re-enter + // the temporary map while it's being destroyed. + AccessibleCache oldCache; + mAccessibleCache.swap(oldCache); + return S_OK; +} + +HRESULT +AccessibleHandlerControl::OnTextChange(long aHwnd, long aIA2UniqueId, + VARIANT_BOOL aIsInsert, + IA2TextSegment* aText) { + if (!aText) { + return E_INVALIDARG; + } + + mTextChange = detail::TextChange(aIA2UniqueId, aIsInsert, WrapNotNull(aText)); + NotifyWinEvent(aIsInsert ? IA2_EVENT_TEXT_INSERTED : IA2_EVENT_TEXT_REMOVED, + reinterpret_cast<HWND>(static_cast<uintptr_t>(aHwnd)), + OBJID_CLIENT, aIA2UniqueId); + return S_OK; +} + +HRESULT +AccessibleHandlerControl::GetNewText(long aIA2UniqueId, + NotNull<IA2TextSegment*> aOutNewText) { + return mTextChange.GetNew(aIA2UniqueId, aOutNewText); +} + +HRESULT +AccessibleHandlerControl::GetOldText(long aIA2UniqueId, + NotNull<IA2TextSegment*> aOutOldText) { + return mTextChange.GetOld(aIA2UniqueId, aOutOldText); +} + +HRESULT +AccessibleHandlerControl::GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo) { + if (!mHandlerProxy) { + return E_UNEXPECTED; + } + + return mHandlerProxy->GetTypeInfoForGuid(CLSID_AccessibleHandler, + aOutTypeInfo); +} + +HRESULT +AccessibleHandlerControl::Register(NotNull<IGeckoBackChannel*> aGecko) { + if (mIsRegistered) { + return S_OK; + } + + long pid = static_cast<long>(::GetCurrentProcessId()); + HRESULT hr = aGecko->put_HandlerControl(pid, this); + mIsRegistered = SUCCEEDED(hr); + MOZ_ASSERT(mIsRegistered); + return hr; +} + +void AccessibleHandlerControl::CacheAccessible(long aUniqueId, + AccessibleHandler* aAccessible) { + MOZ_ASSERT(aUniqueId && aAccessible); + mAccessibleCache[aUniqueId] = aAccessible; +} + +HRESULT AccessibleHandlerControl::GetCachedAccessible( + long aUniqueId, AccessibleHandler** aAccessible) { + MOZ_ASSERT(aUniqueId && aAccessible); + auto it = mAccessibleCache.find(aUniqueId); + if (it == mAccessibleCache.end()) { + return E_INVALIDARG; + } + RefPtr<AccessibleHandler> ref = it->second; + ref.forget(aAccessible); + return S_OK; +} + +HRESULT AccessibleHandlerControl::SuppressA11yForClipboardCopy() { + mA11yClipboardCopySuppressionStartTime = ::GetTickCount(); + return S_OK; +} + +bool AccessibleHandlerControl::IsA11ySuppressedForClipboardCopy() { + // Must be kept in sync with kSuppressTimeout in + // accessible/windows/msaa/Compatibility.cpp. + constexpr DWORD kSuppressTimeout = 1500; // ms + if (!mA11yClipboardCopySuppressionStartTime) { + return false; + } + return ::GetTickCount() - mA11yClipboardCopySuppressionStartTime < + kSuppressTimeout; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/AccessibleHandlerControl.h b/accessible/ipc/win/handler/AccessibleHandlerControl.h new file mode 100644 index 0000000000..9a94ff9fe8 --- /dev/null +++ b/accessible/ipc/win/handler/AccessibleHandlerControl.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_AccessibleHandlerControl_h +# define mozilla_a11y_AccessibleHandlerControl_h + +# include <unordered_map> +# include "Factory.h" +# include "HandlerData.h" +# include "IUnknownImpl.h" +# include "mozilla/mscom/Registration.h" +# include "mozilla/NotNull.h" + +namespace mozilla { +namespace a11y { + +namespace detail { + +class TextChange final { + public: + TextChange(); + TextChange(long aIA2UniqueId, bool aIsInsert, NotNull<IA2TextSegment*> aText); + TextChange(TextChange&& aOther); + TextChange(const TextChange& aOther); + + TextChange& operator=(TextChange&& aOther); + TextChange& operator=(const TextChange& aOther); + + ~TextChange(); + + HRESULT GetOld(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldSegment); + HRESULT GetNew(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewSegment); + + private: + static BSTR BSTRCopy(const BSTR& aIn); + static HRESULT SegCopy(IA2TextSegment& aDest, const IA2TextSegment& aSrc); + + long mIA2UniqueId; + bool mIsInsert; + IA2TextSegment mText; +}; + +} // namespace detail + +class AccessibleHandler; + +class AccessibleHandlerControl final : public IHandlerControl { + public: + static HRESULT Create(AccessibleHandlerControl** aOutObject); + + DECL_IUNKNOWN + + // IHandlerControl + STDMETHODIMP Invalidate() override; + STDMETHODIMP OnTextChange(long aHwnd, long aIA2UniqueId, + VARIANT_BOOL aIsInsert, + IA2TextSegment* aText) override; + STDMETHODIMP SuppressA11yForClipboardCopy() override; + + uint32_t GetCacheGen() const { return mCacheGen; } + + HRESULT GetNewText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutNewText); + HRESULT GetOldText(long aIA2UniqueId, NotNull<IA2TextSegment*> aOutOldText); + + HRESULT GetHandlerTypeInfo(ITypeInfo** aOutTypeInfo); + + HRESULT Register(NotNull<IGeckoBackChannel*> aGecko); + + void CacheAccessible(long aUniqueId, AccessibleHandler* aAccessible); + HRESULT GetCachedAccessible(long aUniqueId, AccessibleHandler** aAccessible); + + bool IsA11ySuppressedForClipboardCopy(); + + private: + AccessibleHandlerControl(); + ~AccessibleHandlerControl() = default; + + bool mIsRegistered; + uint32_t mCacheGen; + detail::TextChange mTextChange; + UniquePtr<mscom::RegisteredProxy> mIA2Proxy; + UniquePtr<mscom::RegisteredProxy> mHandlerProxy; + // We can't use Gecko APIs in this dll, hence the use of std::unordered_map. + typedef std::unordered_map<long, RefPtr<AccessibleHandler>> AccessibleCache; + AccessibleCache mAccessibleCache; + // Time when SuppressA11yForClipboardCopy() was called, as returned by + // ::GetTickCount(). + DWORD mA11yClipboardCopySuppressionStartTime = 0; +}; + +extern mscom::SingletonFactory<AccessibleHandlerControl> gControlFactory; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_AccessibleHandlerControl_h diff --git a/accessible/ipc/win/handler/HandlerChildEnumerator.cpp b/accessible/ipc/win/handler/HandlerChildEnumerator.cpp new file mode 100644 index 0000000000..2062387bf6 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerChildEnumerator.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerChildEnumerator.h" +#include "HandlerTextLeaf.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace a11y { + +HandlerChildEnumerator::HandlerChildEnumerator( + AccessibleHandler* aHandler, IGeckoBackChannel* aGeckoBackChannel) + : mHandler(aHandler), + mGeckoBackChannel(aGeckoBackChannel), + mChildCount(0), + mNextChild(0) { + MOZ_ASSERT(aHandler); + MOZ_ASSERT(aGeckoBackChannel); +} + +HandlerChildEnumerator::HandlerChildEnumerator( + const HandlerChildEnumerator& aEnumerator) + : mHandler(aEnumerator.mHandler), + mGeckoBackChannel(aEnumerator.mGeckoBackChannel), + mChildCount(aEnumerator.mChildCount), + mNextChild(aEnumerator.mNextChild) { + if (mChildCount == 0) { + return; + } + mChildren = MakeUnique<VARIANT[]>(mChildCount); + CopyMemory(mChildren.get(), aEnumerator.mChildren.get(), + sizeof(VARIANT) * mChildCount); + for (ULONG index = 0; index < mChildCount; ++index) { + mChildren[index].pdispVal->AddRef(); + } +} + +HandlerChildEnumerator::~HandlerChildEnumerator() { ClearCache(); } + +void HandlerChildEnumerator::ClearCache() { + if (!mChildren) { + return; + } + + for (ULONG index = 0; index < mChildCount; ++index) { + mChildren[index].pdispVal->Release(); + } + + mChildren = nullptr; + mChildCount = 0; +} + +HRESULT +HandlerChildEnumerator::MaybeCacheChildren() { + if (mChildren) { + // Already cached. + return S_OK; + } + + AccChildData* children; + HRESULT hr = mGeckoBackChannel->get_AllChildren(&children, &mChildCount); + if (FAILED(hr)) { + mChildCount = 0; + ClearCache(); + return hr; + } + + HWND hwnd = nullptr; + hr = mHandler->get_windowHandle(&hwnd); + MOZ_ASSERT(SUCCEEDED(hr)); + + RefPtr<IDispatch> parent; + hr = mHandler->QueryInterface(IID_IDispatch, getter_AddRefs(parent)); + MOZ_ASSERT(SUCCEEDED(hr)); + + mChildren = MakeUnique<VARIANT[]>(mChildCount); + for (ULONG index = 0; index < mChildCount; ++index) { + AccChildData& data = children[index]; + VARIANT& child = mChildren[index]; + if (data.mAccessible) { + RefPtr<IDispatch> disp; + hr = + data.mAccessible->QueryInterface(IID_IDispatch, getter_AddRefs(disp)); + data.mAccessible->Release(); + MOZ_ASSERT(SUCCEEDED(hr)); + if (FAILED(hr)) { + child.vt = VT_EMPTY; + continue; + } + child.vt = VT_DISPATCH; + disp.forget(&child.pdispVal); + } else { + // Text leaf. + RefPtr<IDispatch> leaf(new HandlerTextLeaf(parent, index, hwnd, data)); + child.vt = VT_DISPATCH; + leaf.forget(&child.pdispVal); + } + } + + ::CoTaskMemFree(children); + return S_OK; +} + +IMPL_IUNKNOWN_QUERY_HEAD(HandlerChildEnumerator) +IMPL_IUNKNOWN_QUERY_IFACE(IEnumVARIANT) +IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mHandler) + +/*** IEnumVARIANT ***/ + +HRESULT +HandlerChildEnumerator::Clone(IEnumVARIANT** aPpEnum) { + RefPtr<HandlerChildEnumerator> newEnum(new HandlerChildEnumerator(*this)); + newEnum.forget(aPpEnum); + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Next(ULONG aCelt, VARIANT* aRgVar, + ULONG* aPCeltFetched) { + if (!aRgVar || aCelt == 0) { + return E_INVALIDARG; + } + + HRESULT hr = MaybeCacheChildren(); + if (FAILED(hr)) { + return hr; + } + + for (ULONG index = 0; index < aCelt; ++index) { + if (mNextChild >= mChildCount) { + // Less elements remaining than were requested. + if (aPCeltFetched) { + *aPCeltFetched = index; + } + return S_FALSE; + } + aRgVar[index] = mChildren[mNextChild]; + aRgVar[index].pdispVal->AddRef(); + ++mNextChild; + } + *aPCeltFetched = aCelt; + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Reset() { + mNextChild = 0; + ClearCache(); + return S_OK; +} + +HRESULT +HandlerChildEnumerator::Skip(ULONG aCelt) { + mNextChild += aCelt; + if (mNextChild > mChildCount) { + // Less elements remaining than the client requested to skip. + return S_FALSE; + } + return S_OK; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerChildEnumerator.h b/accessible/ipc/win/handler/HandlerChildEnumerator.h new file mode 100644 index 0000000000..df0218c6ed --- /dev/null +++ b/accessible/ipc/win/handler/HandlerChildEnumerator.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerChildEnumerator_h +# define mozilla_a11y_HandlerChildEnumerator_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerChildEnumerator final : public IEnumVARIANT { + public: + explicit HandlerChildEnumerator(AccessibleHandler* aHandler, + IGeckoBackChannel* aGeckoBackChannel); + + DECL_IUNKNOWN + + // IEnumVARIANT + STDMETHODIMP Clone(IEnumVARIANT** aPpEnum) override; + STDMETHODIMP Next(ULONG aCelt, VARIANT* aRgVar, + ULONG* aPCeltFetched) override; + STDMETHODIMP Reset() override; + STDMETHODIMP Skip(ULONG aCelt) override; + + private: + explicit HandlerChildEnumerator(const HandlerChildEnumerator& aEnumerator); + ~HandlerChildEnumerator(); + void ClearCache(); + HRESULT MaybeCacheChildren(); + + RefPtr<AccessibleHandler> mHandler; + RefPtr<IGeckoBackChannel> mGeckoBackChannel; + UniquePtr<VARIANT[]> mChildren; + ULONG mChildCount; + ULONG mNextChild; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerChildEnumerator_h diff --git a/accessible/ipc/win/handler/HandlerData.acf b/accessible/ipc/win/handler/HandlerData.acf new file mode 100644 index 0000000000..993240b063 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerData.acf @@ -0,0 +1,11 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +[explicit_handle] +interface HandlerData +{ + typedef [encode,decode] IA2Payload; +} diff --git a/accessible/ipc/win/handler/HandlerData.idl b/accessible/ipc/win/handler/HandlerData.idl new file mode 100644 index 0000000000..e449a88263 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerData.idl @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla-config.h" +#include "AccessibleHandler.h" +#include "HandlerDataUUID.h" + +import "ocidl.idl"; +import "servprov.idl"; + +import "Accessible2_3.idl"; +import "AccessibleHypertext2.idl"; +import "AccessibleHyperlink.idl"; +import "AccessibleTable.idl"; +import "AccessibleTable2.idl"; +import "AccessibleTableCell.idl"; + +typedef struct _StaticIA2Data +{ + NEWEST_IA2_INTERFACE* mIA2; + IAccessibleHypertext2* mIAHypertext; + IAccessibleHyperlink* mIAHyperlink; + IAccessibleTable* mIATable; + IAccessibleTable2* mIATable2; + IAccessibleTableCell* mIATableCell; +} StaticIA2Data; + +typedef struct _DynamicIA2Data +{ + // From IAccessible/IAccessible2 + VARIANT mRole; + long mState; + long mChildCount; + long mIA2Role; + AccessibleStates mIA2States; + long mLeft; + long mTop; + long mWidth; + long mHeight; + long mHwnd; + BSTR mKeyboardShortcut; + BSTR mName; + BSTR mDescription; + BSTR mDefaultAction; + BSTR mValue; + BSTR mAttributes; + IA2Locale mIA2Locale; + // From IAccessibleAction + long mNActions; + // From IAccessibleTableCell + long mRowIndex; + long mColumnIndex; + long mRowExtent; + long mColumnExtent; + boolean mCellIsSelected; + long mNRowHeaderCells; + [size_is(mNRowHeaderCells)] long* mRowHeaderCellIds; + long mNColumnHeaderCells; + [size_is(mNColumnHeaderCells)] long* mColumnHeaderCellIds; + // From IAccessible2 + long mUniqueId; +} DynamicIA2Data; + +interface IGeckoBackChannel; + +[uuid(2b0e83b3-fd1a-443f-9ed6-c00d39055b58)] +interface HandlerData +{ + typedef struct _IA2Payload + { + StaticIA2Data mStaticData; + DynamicIA2Data mDynamicData; + IGeckoBackChannel* mGeckoBackChannel; + } IA2Payload; +} + +[object, + uuid(IHANDLERCONTROL_IID), + async_uuid(ASYNCIHANDLERCONTROL_IID), + pointer_default(unique)] +interface IHandlerControl : IUnknown +{ + HRESULT Invalidate(); + HRESULT OnTextChange([in] long aHwnd, [in] long aIA2UniqueId, + [in] VARIANT_BOOL aIsInsert, + [in] IA2TextSegment* aText); + HRESULT SuppressA11yForClipboardCopy(); +} + +typedef struct _IARelationData +{ + BSTR mType; + long mNTargets; +} IARelationData; + +typedef struct _AccChildData +{ + NEWEST_IA2_INTERFACE* mAccessible; + BSTR mText; + long mTextRole; + long mTextId; + long mTextState; + long mTextLeft; + long mTextTop; + long mTextWidth; + long mTextHeight; +} AccChildData; + +[object, + uuid(IGECKOBACKCHANNEL_IID), + pointer_default(unique)] +interface IGeckoBackChannel : IUnknown +{ + [propput] HRESULT HandlerControl([in] long aPid, [in] IHandlerControl* aCtrl); + HRESULT Refresh([out] DynamicIA2Data* aOutData); + [propget] HRESULT AllTextInfo([out] BSTR* aText, + [out, size_is(,*aNHyperlinks)] IAccessibleHyperlink*** aHyperlinks, + [out] long* aNHyperlinks, + [out, size_is(,*aNAttribRuns)] IA2TextSegment** aAttribRuns, + [out] long* aNAttribRuns); + [propget] HRESULT RelationsInfo( + [out, size_is(,*aNRelations)] IARelationData** aRelations, + [out] long* aNRelations); + [propget] HRESULT AllChildren( + [out, size_is(,*aNChildren)] AccChildData** aChildren, + [out] ULONG* aNChildren); +} + +[uuid(1e545f07-f108-4912-9471-546827a80983)] +library AccessibleHandlerTypeLib +{ + importlib("stdole2.tlb"); + + /** + * This definition is required in order for the handler implementation to + * support IDispatch (aka Automation). This is used by interpreted language + * FFIs to discover which interfaces may be controlled via IDispatch. + * (In particular, the python FFI used by NVDA needs this). + * + * In reality, the only a11y interface that is Automation compliant is + * IAccessible; our remaining interfaces are not. + * + * Once the FFI knows that IAccessible is supported, the FFI queries for + * IAccessible and is then able to resolve non-automation interfaces from + * there. + */ + [uuid(HANDLER_CLSID)] + coclass AccessibleHandler + { + [default] interface IAccessible; + }; +}; diff --git a/accessible/ipc/win/handler/HandlerDataCleanup.h b/accessible/ipc/win/handler/HandlerDataCleanup.h new file mode 100644 index 0000000000..14d9995c19 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerDataCleanup.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_a11y_HandlerDataCleanup_h +#define mozilla_a11y_HandlerDataCleanup_h + +#include <oleauto.h> + +namespace mozilla { +namespace a11y { + +inline void ReleaseStaticIA2DataInterfaces(StaticIA2Data& aData) { + // Only interfaces of the proxied object wrapped by this handler should be + // released here, never other objects! + // For example, if StaticIA2Data were to include accParent in future, + // that must not be released here. + if (aData.mIA2) { + aData.mIA2->Release(); + } + if (aData.mIAHypertext) { + aData.mIAHypertext->Release(); + } + if (aData.mIAHyperlink) { + aData.mIAHyperlink->Release(); + } + if (aData.mIATable) { + aData.mIATable->Release(); + } + if (aData.mIATable2) { + aData.mIATable2->Release(); + } + if (aData.mIATableCell) { + aData.mIATableCell->Release(); + } +} + +/** + * Pass true for aMarshaledByCom if this struct was directly marshaled as an + * out parameter of a COM method, currently only IGeckoBackChannel::Refresh. + */ +inline void CleanupDynamicIA2Data(DynamicIA2Data& aData, + bool aMarshaledByCom = false) { + // If freeing generic memory returned to the client, you *must* use freeMem, + // not CoTaskMemFree! + auto freeMem = [aMarshaledByCom](void* aMem) { + if (aMarshaledByCom) { + ::CoTaskMemFree(aMem); + } else { + ::midl_user_free(aMem); + } + }; + + ::VariantClear(&aData.mRole); + if (aData.mKeyboardShortcut) { + ::SysFreeString(aData.mKeyboardShortcut); + } + if (aData.mName) { + ::SysFreeString(aData.mName); + } + if (aData.mDescription) { + ::SysFreeString(aData.mDescription); + } + if (aData.mDefaultAction) { + ::SysFreeString(aData.mDefaultAction); + } + if (aData.mValue) { + ::SysFreeString(aData.mValue); + } + if (aData.mAttributes) { + ::SysFreeString(aData.mAttributes); + } + if (aData.mIA2Locale.language) { + ::SysFreeString(aData.mIA2Locale.language); + } + if (aData.mIA2Locale.country) { + ::SysFreeString(aData.mIA2Locale.country); + } + if (aData.mIA2Locale.variant) { + ::SysFreeString(aData.mIA2Locale.variant); + } + if (aData.mRowHeaderCellIds) { + freeMem(aData.mRowHeaderCellIds); + } + if (aData.mColumnHeaderCellIds) { + freeMem(aData.mColumnHeaderCellIds); + } +} + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerDataCleanup_h diff --git a/accessible/ipc/win/handler/HandlerDataUUID.h.in b/accessible/ipc/win/handler/HandlerDataUUID.h.in new file mode 100644 index 0000000000..234c3d6114 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerDataUUID.h.in @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// We use different CLSIDs and IIDs depending on channel and officiality. +// This prevents handlers from installing overtop one another when multiple +// channels are present. Note that we only do this for the UUIDs that are +// written to the registry. +// The specific UUIDs are defined in branding configuration. + +#define HANDLER_CLSID @MOZ_HANDLER_CLSID@ +#define IHANDLERCONTROL_IID @MOZ_IHANDLERCONTROL_IID@ +#define ASYNCIHANDLERCONTROL_IID @MOZ_ASYNCIHANDLERCONTROL_IID@ +#define IGECKOBACKCHANNEL_IID @MOZ_IGECKOBACKCHANNEL_IID@ diff --git a/accessible/ipc/win/handler/HandlerRelation.cpp b/accessible/ipc/win/handler/HandlerRelation.cpp new file mode 100644 index 0000000000..8e03da5360 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerRelation.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerRelation.h" +#include "mozilla/Assertions.h" + +#include "AccessibleRelation_i.c" + +namespace mozilla { +namespace a11y { + +HandlerRelation::HandlerRelation(AccessibleHandler* aHandler, + IARelationData& aData) + : mHandler(aHandler), mData(aData), mTargets(nullptr) { + // This instance now owns any pointers, so ensure no one else can + // manipulate them. + aData.mType = nullptr; +} + +HandlerRelation::~HandlerRelation() { + if (mData.mType) { + ::SysFreeString(mData.mType); + } + + if (mTargets) { + for (long index = 0; index < mData.mNTargets; ++index) { + mTargets[index]->Release(); + } + ::CoTaskMemFree(mTargets); + mTargets = nullptr; + } +} + +HRESULT +HandlerRelation::GetTargets() { + if (mTargets) { + // Already cached. + return S_OK; + } + + // Marshaling all of the IAccessibleRelation objects across processes is + // slow, and the client probably only wants targets for a few of them. + // Therefore, we just use IAccessible2_2::relationTargetsOfType, passing + // the type we have cached. This is a bit inefficient because Gecko has + // to look up the relation twice, but it's significantly faster than + // marshaling the relation objects regardless. + return mHandler->get_relationTargetsOfType(mData.mType, 0, &mTargets, + &mData.mNTargets); +} + +IMPL_IUNKNOWN1(HandlerRelation, IAccessibleRelation) + +/*** IAccessibleRelation ***/ + +HRESULT +HandlerRelation::get_relationType(BSTR* aType) { + if (!aType) { + return E_INVALIDARG; + } + if (!mData.mType) { + return E_FAIL; + } + *aType = CopyBSTR(mData.mType); + return S_OK; +} + +HRESULT +HandlerRelation::get_localizedRelationType(BSTR* aLocalizedType) { + // This is not implemented as per ia2AccessibleRelation. + return E_NOTIMPL; +} + +HRESULT +HandlerRelation::get_nTargets(long* aNTargets) { + if (!aNTargets) { + return E_INVALIDARG; + } + if (mData.mNTargets == -1) { + return E_FAIL; + } + *aNTargets = mData.mNTargets; + return S_OK; +} + +HRESULT +HandlerRelation::get_target(long aIndex, IUnknown** aTarget) { + if (!aTarget) { + return E_INVALIDARG; + } + + HRESULT hr = GetTargets(); + if (FAILED(hr)) { + return hr; + } + if (aIndex >= mData.mNTargets) { + return E_INVALIDARG; + } + + *aTarget = mTargets[aIndex]; + (*aTarget)->AddRef(); + return S_OK; +} + +HRESULT +HandlerRelation::get_targets(long aMaxTargets, IUnknown** aTargets, + long* aNTargets) { + if (aMaxTargets == 0 || !aTargets || !aNTargets) { + return E_INVALIDARG; + } + + HRESULT hr = GetTargets(); + if (FAILED(hr)) { + return hr; + } + + if (mData.mNTargets > aMaxTargets) { + // Don't give back more targets than were requested. + *aNTargets = aMaxTargets; + } else { + *aNTargets = mData.mNTargets; + } + + for (long index = 0; index < *aNTargets; ++index) { + aTargets[index] = mTargets[index]; + aTargets[index]->AddRef(); + } + return S_OK; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerRelation.h b/accessible/ipc/win/handler/HandlerRelation.h new file mode 100644 index 0000000000..e25b4ee189 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerRelation.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerRelation_h +# define mozilla_a11y_HandlerRelation_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerRelation final : public IAccessibleRelation { + public: + explicit HandlerRelation(AccessibleHandler* aHandler, IARelationData& aData); + + DECL_IUNKNOWN + + // IAccessibleRelation + STDMETHODIMP get_relationType(BSTR* aType) override; + STDMETHODIMP get_localizedRelationType(BSTR* aLocalizedType) override; + STDMETHODIMP get_nTargets(long* aNTargets) override; + STDMETHODIMP get_target(long aIndex, IUnknown** aTarget) override; + STDMETHODIMP get_targets(long aMaxTargets, IUnknown** aTargets, + long* aNTargets) override; + + private: + virtual ~HandlerRelation(); + HRESULT GetTargets(); + RefPtr<AccessibleHandler> mHandler; + IARelationData mData; + IUnknown** mTargets; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerRelation_h diff --git a/accessible/ipc/win/handler/HandlerTextLeaf.cpp b/accessible/ipc/win/handler/HandlerTextLeaf.cpp new file mode 100644 index 0000000000..730aa88df9 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerTextLeaf.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#include "HandlerTextLeaf.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace a11y { + +HandlerTextLeaf::HandlerTextLeaf(IDispatch* aParent, long aIndexInParent, + HWND aHwnd, AccChildData& aData) + : mParent(aParent), + mIndexInParent(aIndexInParent), + mHwnd(aHwnd), + mData(aData) { + MOZ_ASSERT(aParent); +} + +HandlerTextLeaf::~HandlerTextLeaf() { + if (mData.mText) { + ::SysFreeString(mData.mText); + } +} + +IMPL_IUNKNOWN_QUERY_HEAD(HandlerTextLeaf) +IMPL_IUNKNOWN_QUERY_IFACE(IDispatch) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessible) +IMPL_IUNKNOWN_QUERY_IFACE(IAccessible2) +IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) +IMPL_IUNKNOWN_QUERY_TAIL + +/*** IDispatch ***/ + +HRESULT +HandlerTextLeaf::GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, + WORD wFlags, DISPPARAMS* pDispParams, + VARIANT* pVarResult, EXCEPINFO* pExcepInfo, + UINT* puArgErr) { + return E_NOTIMPL; +} + +/*** IAccessible ***/ + +HRESULT +HandlerTextLeaf::get_accParent(IDispatch** ppdispParent) { + if (!ppdispParent) { + return E_INVALIDARG; + } + + RefPtr<IDispatch> parent(mParent); + parent.forget(ppdispParent); + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accChildCount(long* pcountChildren) { + if (!pcountChildren) { + return E_INVALIDARG; + } + + *pcountChildren = 0; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accChild(VARIANT varChild, IDispatch** ppdispChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accName(VARIANT varChild, BSTR* pszName) { + if (varChild.lVal != CHILDID_SELF || !pszName) { + return E_INVALIDARG; + } + + *pszName = CopyBSTR(mData.mText); + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accValue(VARIANT varChild, BSTR* pszValue) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accDescription(VARIANT varChild, BSTR* pszDescription) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accRole(VARIANT varChild, VARIANT* pvarRole) { + if (varChild.lVal != CHILDID_SELF || !pvarRole) { + return E_INVALIDARG; + } + + pvarRole->vt = VT_I4; + pvarRole->lVal = mData.mTextRole; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accState(VARIANT varChild, VARIANT* pvarState) { + if (varChild.lVal != CHILDID_SELF || !pvarState) { + return E_INVALIDARG; + } + + pvarState->vt = VT_I4; + pvarState->lVal = mData.mTextState; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_accHelp(VARIANT varChild, BSTR* pszHelp) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_accFocus(VARIANT* pvarChild) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_accSelection(VARIANT* pvarChildren) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accSelect(long flagsSelect, VARIANT varChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) { + if (varChild.lVal != CHILDID_SELF || !pxLeft || !pyTop || !pcxWidth || + !pcyHeight) { + return E_INVALIDARG; + } + + *pxLeft = mData.mTextLeft; + *pyTop = mData.mTextTop; + *pcxWidth = mData.mTextWidth; + *pcyHeight = mData.mTextHeight; + return S_OK; +} + +HRESULT +HandlerTextLeaf::accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accHitTest(long xLeft, long yTop, VARIANT* pvarChild) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::accDoDefaultAction(VARIANT varChild) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::put_accName(VARIANT varChild, BSTR szName) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::put_accValue(VARIANT varChild, BSTR szValue) { + return E_NOTIMPL; +} + +/*** IAccessible2 ***/ + +HRESULT +HandlerTextLeaf::get_nRelations(long* nRelations) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_relation(long relationIndex, + IAccessibleRelation** relation) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_relations(long maxRelations, + IAccessibleRelation** relations, + long* nRelations) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::role(long* role) { + if (!role) { + return E_INVALIDARG; + } + + *role = mData.mTextRole; + return S_OK; +} + +HRESULT +HandlerTextLeaf::scrollTo(IA2ScrollType scrollType) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_states(AccessibleStates* states) { + if (!states) { + return E_INVALIDARG; + } + + *states = 0; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_extendedRole(BSTR* extendedRole) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_localizedExtendedRole(BSTR* localizedExtendedRole) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_nExtendedStates(long* nExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_extendedStates(long maxExtendedStates, + BSTR** extendedStates, + long* nExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_localizedExtendedStates(long maxLocalizedExtendedStates, + BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) { + return E_NOTIMPL; +} + +HRESULT +HandlerTextLeaf::get_uniqueID(long* uniqueID) { + if (!uniqueID) { + return E_INVALIDARG; + } + + *uniqueID = mData.mTextId; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_windowHandle(HWND* windowHandle) { + if (!windowHandle) { + return E_INVALIDARG; + } + + *windowHandle = mHwnd; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_indexInParent(long* indexInParent) { + if (!indexInParent) { + return E_INVALIDARG; + } + + *indexInParent = mIndexInParent; + return S_OK; +} + +HRESULT +HandlerTextLeaf::get_locale(IA2Locale* locale) { return E_NOTIMPL; } + +HRESULT +HandlerTextLeaf::get_attributes(BSTR* attributes) { return E_NOTIMPL; } + +/*** IServiceProvider ***/ + +HRESULT +HandlerTextLeaf::QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) { + if (aIid == IID_IAccessible2) { + RefPtr<IAccessible2> ia2(this); + ia2.forget(aOutInterface); + return S_OK; + } + + return E_INVALIDARG; +} + +} // namespace a11y +} // namespace mozilla diff --git a/accessible/ipc/win/handler/HandlerTextLeaf.h b/accessible/ipc/win/handler/HandlerTextLeaf.h new file mode 100644 index 0000000000..f1a31b9219 --- /dev/null +++ b/accessible/ipc/win/handler/HandlerTextLeaf.h @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if defined(MOZILLA_INTERNAL_API) +# error This code is NOT for internal Gecko use! +#endif // defined(MOZILLA_INTERNAL_API) + +#ifndef mozilla_a11y_HandlerTextLeaf_h +# define mozilla_a11y_HandlerTextLeaf_h + +# include "AccessibleHandler.h" +# include "IUnknownImpl.h" +# include "mozilla/RefPtr.h" + +namespace mozilla { +namespace a11y { + +class HandlerTextLeaf final : public IAccessible2, public IServiceProvider { + public: + explicit HandlerTextLeaf(IDispatch* aParent, long aIndexInParent, HWND aHwnd, + AccChildData& aData); + + DECL_IUNKNOWN + + // IDispatch + STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override; + STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, + ITypeInfo** ppTInfo) override; + STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, + LCID lcid, DISPID* rgDispId) override; + STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS* pDispParams, VARIANT* pVarResult, + EXCEPINFO* pExcepInfo, UINT* puArgErr) override; + + // IAccessible + STDMETHODIMP get_accParent(IDispatch** ppdispParent) override; + STDMETHODIMP get_accChildCount(long* pcountChildren) override; + STDMETHODIMP get_accChild(VARIANT varChild, IDispatch** ppdispChild) override; + STDMETHODIMP get_accName(VARIANT varChild, BSTR* pszName) override; + STDMETHODIMP get_accValue(VARIANT varChild, BSTR* pszValue) override; + STDMETHODIMP get_accDescription(VARIANT varChild, + BSTR* pszDescription) override; + STDMETHODIMP get_accRole(VARIANT varChild, VARIANT* pvarRole) override; + STDMETHODIMP get_accState(VARIANT varChild, VARIANT* pvarState) override; + STDMETHODIMP get_accHelp(VARIANT varChild, BSTR* pszHelp) override; + STDMETHODIMP get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, + long* pidTopic) override; + STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, + BSTR* pszKeyboardShortcut) override; + STDMETHODIMP get_accFocus(VARIANT* pvarChild) override; + STDMETHODIMP get_accSelection(VARIANT* pvarChildren) override; + STDMETHODIMP get_accDefaultAction(VARIANT varChild, + BSTR* pszDefaultAction) override; + STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override; + STDMETHODIMP accLocation(long* pxLeft, long* pyTop, long* pcxWidth, + long* pcyHeight, VARIANT varChild) override; + STDMETHODIMP accNavigate(long navDir, VARIANT varStart, + VARIANT* pvarEndUpAt) override; + STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT* pvarChild) override; + STDMETHODIMP accDoDefaultAction(VARIANT varChild) override; + STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override; + STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override; + + // IAccessible2 + STDMETHODIMP get_nRelations(long* nRelations) override; + STDMETHODIMP get_relation(long relationIndex, + IAccessibleRelation** relation) override; + STDMETHODIMP get_relations(long maxRelations, IAccessibleRelation** relations, + long* nRelations) override; + STDMETHODIMP role(long* role) override; + STDMETHODIMP scrollTo(IA2ScrollType scrollType) override; + STDMETHODIMP scrollToPoint(IA2CoordinateType coordinateType, long x, + long y) override; + STDMETHODIMP get_groupPosition(long* groupLevel, long* similarItemsInGroup, + long* positionInGroup) override; + STDMETHODIMP get_states(AccessibleStates* states) override; + STDMETHODIMP get_extendedRole(BSTR* extendedRole) override; + STDMETHODIMP get_localizedExtendedRole(BSTR* localizedExtendedRole) override; + STDMETHODIMP get_nExtendedStates(long* nExtendedStates) override; + STDMETHODIMP get_extendedStates(long maxExtendedStates, BSTR** extendedStates, + long* nExtendedStates) override; + STDMETHODIMP get_localizedExtendedStates( + long maxLocalizedExtendedStates, BSTR** localizedExtendedStates, + long* nLocalizedExtendedStates) override; + STDMETHODIMP get_uniqueID(long* uniqueID) override; + STDMETHODIMP get_windowHandle(HWND* windowHandle) override; + STDMETHODIMP get_indexInParent(long* indexInParent) override; + STDMETHODIMP get_locale(IA2Locale* locale) override; + STDMETHODIMP get_attributes(BSTR* attributes) override; + + // IServiceProvider + STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aIid, + void** aOutInterface) override; + + private: + ~HandlerTextLeaf(); + + RefPtr<IDispatch> mParent; + long mIndexInParent; + HWND mHwnd; + AccChildData mData; +}; + +} // namespace a11y +} // namespace mozilla + +#endif // mozilla_a11y_HandlerTextLeaf_h diff --git a/accessible/ipc/win/handler/moz.build b/accessible/ipc/win/handler/moz.build new file mode 100644 index 0000000000..1242bb6b8c --- /dev/null +++ b/accessible/ipc/win/handler/moz.build @@ -0,0 +1,132 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("AccessibleHandler") + +EXPORTS.mozilla.a11y += [ + "AccessibleHandler.h", + "HandlerDataCleanup.h", +] + +LOCAL_INCLUDES += [ + "/accessible/interfaces/ia2", + "/ipc/mscom/oop", +] + +# We want to generate distinct UUIDs on a per-channel basis, so we need +# finer granularity than the standard preprocessor definitions offer. +# This generated include allow us to separate local builds from automated +# builds, separate beta from release, and use different UUIDs in downstream +# projects such as Thunderbird. +CONFIGURE_SUBST_FILES += ["HandlerDataUUID.h"] +flags = [] + +GeneratedFile( + "HandlerData.h", + "HandlerData_p.c", + "HandlerData_i.c", + "HandlerData_c.c", + "HandlerData_dlldata.c", + "HandlerData.tlb", + inputs=["HandlerData.idl"], + script="/build/midl.py", + entry_point="midl", + flags=flags + + [ + "-I", + TOPOBJDIR, + "-I", + TOPOBJDIR + "/dist/include", + "-I", + TOPSRCDIR + "/other-licenses/ia2", + "-I", + SRCDIR, + "-I", + OBJDIR, + "-acf", + SRCDIR + "/HandlerData.acf", + "-dlldata", + OBJDIR + "/HandlerData_dlldata.c", + ], +) + +SOURCES += [ + "!HandlerData_c.c", + "!HandlerData_dlldata.c", + "!HandlerData_i.c", + "!HandlerData_p.c", + "AccessibleHandler.cpp", + "AccessibleHandlerControl.cpp", + "HandlerChildEnumerator.cpp", + "HandlerRelation.cpp", + "HandlerTextLeaf.cpp", +] + +EXPORTS += [ + "!HandlerData.h", + "!HandlerData_i.c", + "!HandlerDataUUID.h", +] + +# Give some symbols a unique name in each translation unit, to avoid +# collisions caused by https://llvm.org/pr41817. +if CONFIG["CC_TYPE"] == "clang-cl": + SOURCES["!HandlerData_p.c"].flags += [ + "-DHandlerData__MIDL_ProcFormatString=HandlerData__MIDL_ProcFormatString__HandlerData_p" + ] + SOURCES["!HandlerData_p.c"].flags += [ + "-DHandlerData__MIDL_TypeFormatString=HandlerData__MIDL_TypeFormatString__HandlerData_p" + ] + for p in ("dlldata", "c", "i", "p"): + SOURCES["!HandlerData_%s.c" % p].flags += [ + "-DUserMarshalRoutines=UserMarshalRoutines__HandlerData_%s" % p + ] + +DEFFILE = "AccessibleHandler.def" + +USE_LIBS += [ + "mscom_oop", +] + +OS_LIBS += [ + "advapi32", + "uuid", + "rpcrt4", + "oleacc", + "user32", +] + +RCINCLUDE = "AccessibleHandler.rc" + +# Suppress warnings from the MIDL generated code. +if CONFIG["CC_TYPE"] == "clang-cl": + CFLAGS += [ + "-Wno-extern-initializer", + "-Wno-incompatible-pointer-types", + "-Wno-missing-braces", + "-Wno-unused-const-variable", + ] + +# Since we are defining our own COM entry points (DllRegisterServer et al), +# but we still want to be able to delegate some work to the generated code, +# we add the prefix "Proxy" to all of the generated counterparts. +DEFINES["ENTRY_PREFIX"] = "Proxy" +DEFINES["REGISTER_PROXY_DLL"] = True +LIBRARY_DEFINES["MOZ_MSCOM_REMARSHAL_NO_HANDLER"] = True + +# This DLL may be loaded into other processes, so we need static libs for +# Windows 7 and Windows 8. +USE_STATIC_LIBS = True + +LIBRARY_DEFINES["UNICODE"] = True +LIBRARY_DEFINES["_UNICODE"] = True +LIBRARY_DEFINES["MOZ_NO_MOZALLOC"] = True +DisableStlWrapping() + +DELAYLOAD_DLLS += [ + "advapi32.dll", + "ktmw32.dll", +] |