diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/ipc/win/HandlerProvider.cpp | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/accessible/ipc/win/HandlerProvider.cpp b/accessible/ipc/win/HandlerProvider.cpp new file mode 100644 index 0000000000..6e8e5e52c3 --- /dev/null +++ b/accessible/ipc/win/HandlerProvider.cpp @@ -0,0 +1,974 @@ +/* -*- 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/a11y/HandlerProvider.h" + +#include <memory.h> + +#include <utility> + +#include "Accessible2_3.h" +#include "AccessibleApplication.h" +#include "AccessibleDocument.h" +#include "AccessibleEditableText.h" +#include "AccessibleImage.h" +#include "AccessibleRelation.h" +#include "AccessibleTable.h" +#include "AccessibleTable2.h" +#include "AccessibleTableCell.h" +#include "HandlerData.h" +#include "HandlerData_i.c" +#include "ISimpleDOM.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/a11y/AccessibleWrap.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/mscom/AgileReference.h" +#include "mozilla/mscom/FastMarshaler.h" +#include "mozilla/mscom/Interceptor.h" +#include "mozilla/mscom/MainThreadHandoff.h" +#include "mozilla/mscom/MainThreadInvoker.h" +#include "mozilla/mscom/Ptr.h" +#include "mozilla/mscom/StructStream.h" +#include "mozilla/mscom/Utils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "uiautomation.h" + +namespace mozilla { +namespace a11y { + +HandlerProvider::HandlerProvider(REFIID aIid, + mscom::InterceptorTargetPtr<IUnknown> aTarget) + : mRefCnt(0), + mMutex("mozilla::a11y::HandlerProvider::mMutex"), + mTargetUnkIid(aIid), + mTargetUnk(std::move(aTarget)), + mPayloadMutex("mozilla::a11y::HandlerProvider::mPayloadMutex") {} + +HRESULT +HandlerProvider::QueryInterface(REFIID riid, void** ppv) { + if (!ppv) { + return E_INVALIDARG; + } + + if (riid == IID_IUnknown || riid == IID_IGeckoBackChannel) { + RefPtr<IUnknown> punk(static_cast<IGeckoBackChannel*>(this)); + punk.forget(ppv); + return S_OK; + } + + if (riid == IID_IMarshal) { + if (!mFastMarshalUnk) { + HRESULT hr = + mscom::FastMarshaler::Create(static_cast<IGeckoBackChannel*>(this), + getter_AddRefs(mFastMarshalUnk)); + if (FAILED(hr)) { + return hr; + } + } + + return mFastMarshalUnk->QueryInterface(riid, ppv); + } + + return E_NOINTERFACE; +} + +ULONG +HandlerProvider::AddRef() { return ++mRefCnt; } + +ULONG +HandlerProvider::Release() { + ULONG result = --mRefCnt; + if (!result) { + delete this; + } + return result; +} + +HRESULT +HandlerProvider::GetHandler(NotNull<CLSID*> aHandlerClsid) { + if (!IsTargetInterfaceCacheable()) { + return E_NOINTERFACE; + } + + *aHandlerClsid = CLSID_AccessibleHandler; + return S_OK; +} + +void HandlerProvider::GetAndSerializePayload( + const MutexAutoLock&, NotNull<mscom::IInterceptor*> aInterceptor) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (mSerializer) { + return; + } + + IA2PayloadPtr payload; + { // Scope for lock + MutexAutoLock lock(mPayloadMutex); + if (mPayload) { + // The payload was already built by prebuildPayload() called during a + // bulk fetch operation. + payload = std::move(mPayload); + } + } + + if (!payload) { + // We don't have a pre-built payload, so build it now. + payload.reset(new IA2Payload()); + if (!mscom::InvokeOnMainThread( + "HandlerProvider::BuildInitialIA2Data", this, + &HandlerProvider::BuildInitialIA2Data, + std::forward<NotNull<mscom::IInterceptor*>>(aInterceptor), + std::forward<StaticIA2Data*>(&payload->mStaticData), + std::forward<DynamicIA2Data*>(&payload->mDynamicData)) || + !payload->mDynamicData.mUniqueId) { + return; + } + } + + // But we set mGeckoBackChannel on the current thread which resides in the + // MTA. This is important to ensure that COM always invokes + // IGeckoBackChannel methods in an MTA background thread. + RefPtr<IGeckoBackChannel> payloadRef(this); + // AddRef/Release pair for this reference is handled by payloadRef + payload->mGeckoBackChannel = this; + + mSerializer = MakeUnique<mscom::StructToStream>(*payload, &IA2Payload_Encode); +} + +HRESULT +HandlerProvider::GetHandlerPayloadSize( + NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<DWORD*> aOutPayloadSize) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!IsTargetInterfaceCacheable()) { + // No handler, so no payload for this instance. + return E_NOTIMPL; + } + + MutexAutoLock lock(mMutex); + + GetAndSerializePayload(lock, aInterceptor); + + if (!mSerializer || !(*mSerializer)) { + // Failed payload serialization is non-fatal + *aOutPayloadSize = mscom::StructToStream::GetEmptySize(); + return S_OK; + } + + *aOutPayloadSize = mSerializer->GetSize(); + return S_OK; +} + +void HandlerProvider::BuildStaticIA2Data( + NotNull<mscom::IInterceptor*> aInterceptor, StaticIA2Data* aOutData) { + MOZ_ASSERT(aOutData); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTargetUnk); + MOZ_ASSERT(IsTargetInterfaceCacheable()); + + // Include interfaces the client is likely to request. + // This is cheap here and saves multiple cross-process calls later. + // These interfaces must be released in ReleaseStaticIA2DataInterfaces! + + // If the target is already an IAccessible2, this pointer is redundant. + // However, the target might be an IAccessibleHyperlink, etc., in which + // case the client will almost certainly QI for IAccessible2. + HRESULT hr = aInterceptor->GetInterceptorForIID(NEWEST_IA2_IID, + (void**)&aOutData->mIA2); + if (FAILED(hr)) { + // IA2 should always be present, so something has + // gone very wrong if this fails. + aOutData->mIA2 = nullptr; + return; + } + + // Some of these interfaces aren't present on all accessibles, + // so it's not a failure if these interfaces can't be fetched. + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleHypertext2, + (void**)&aOutData->mIAHypertext); + if (FAILED(hr)) { + aOutData->mIAHypertext = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleHyperlink, + (void**)&aOutData->mIAHyperlink); + if (FAILED(hr)) { + aOutData->mIAHyperlink = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTable, + (void**)&aOutData->mIATable); + if (FAILED(hr)) { + aOutData->mIATable = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTable2, + (void**)&aOutData->mIATable2); + if (FAILED(hr)) { + aOutData->mIATable2 = nullptr; + } + + hr = aInterceptor->GetInterceptorForIID(IID_IAccessibleTableCell, + (void**)&aOutData->mIATableCell); + if (FAILED(hr)) { + aOutData->mIATableCell = nullptr; + } +} + +void HandlerProvider::BuildDynamicIA2Data(DynamicIA2Data* aOutIA2Data, + bool aMarshaledByCom) { + MOZ_ASSERT(aOutIA2Data); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsTargetInterfaceCacheable()); + + if (!mTargetUnk) { + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> target; + HRESULT hr = + mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(target)); + if (FAILED(hr)) { + return; + } + + hr = E_UNEXPECTED; + + auto hasFailed = [&hr]() -> bool { return FAILED(hr); }; + + auto cleanup = [aOutIA2Data, aMarshaledByCom]() -> void { + CleanupDynamicIA2Data(*aOutIA2Data, aMarshaledByCom); + }; + + mscom::ExecuteWhen<decltype(hasFailed), decltype(cleanup)> onFail(hasFailed, + cleanup); + + // When allocating memory to be returned to the client, you *must* use + // allocMem, not CoTaskMemAlloc! + auto allocMem = [aMarshaledByCom](size_t aSize) { + if (aMarshaledByCom) { + return ::CoTaskMemAlloc(aSize); + } + // We use midl_user_allocate rather than CoTaskMemAlloc because this + // struct is being marshaled by RPC, not COM. + return ::midl_user_allocate(aSize); + }; + + const VARIANT kChildIdSelf = {VT_I4}; + VARIANT varVal; + + hr = target->accLocation(&aOutIA2Data->mLeft, &aOutIA2Data->mTop, + &aOutIA2Data->mWidth, &aOutIA2Data->mHeight, + kChildIdSelf); + if (FAILED(hr)) { + return; + } + + hr = target->get_accRole(kChildIdSelf, &aOutIA2Data->mRole); + if (FAILED(hr)) { + return; + } + + hr = target->get_accState(kChildIdSelf, &varVal); + if (FAILED(hr)) { + return; + } + + aOutIA2Data->mState = varVal.lVal; + + hr = target->get_accKeyboardShortcut(kChildIdSelf, + &aOutIA2Data->mKeyboardShortcut); + if (FAILED(hr)) { + return; + } + + hr = target->get_accName(kChildIdSelf, &aOutIA2Data->mName); + if (FAILED(hr)) { + return; + } + + hr = target->get_accDescription(kChildIdSelf, &aOutIA2Data->mDescription); + if (FAILED(hr)) { + return; + } + + hr = target->get_accDefaultAction(kChildIdSelf, &aOutIA2Data->mDefaultAction); + if (FAILED(hr)) { + return; + } + + hr = target->get_accChildCount(&aOutIA2Data->mChildCount); + if (FAILED(hr)) { + return; + } + + hr = target->get_accValue(kChildIdSelf, &aOutIA2Data->mValue); + if (FAILED(hr)) { + return; + } + + hr = target->get_states(&aOutIA2Data->mIA2States); + if (FAILED(hr)) { + return; + } + + hr = target->get_attributes(&aOutIA2Data->mAttributes); + if (FAILED(hr)) { + return; + } + + HWND hwnd; + hr = target->get_windowHandle(&hwnd); + if (FAILED(hr)) { + return; + } + + aOutIA2Data->mHwnd = PtrToLong(hwnd); + + hr = target->get_locale(&aOutIA2Data->mIA2Locale); + if (FAILED(hr)) { + return; + } + + hr = target->role(&aOutIA2Data->mIA2Role); + if (FAILED(hr)) { + return; + } + + RefPtr<IAccessibleAction> action; + // It is not an error if this fails. + hr = mTargetUnk.get()->QueryInterface(IID_IAccessibleAction, + getter_AddRefs(action)); + if (SUCCEEDED(hr)) { + hr = action->nActions(&aOutIA2Data->mNActions); + if (FAILED(hr)) { + return; + } + } + + RefPtr<IAccessibleTableCell> cell; + // It is not an error if this fails. + hr = mTargetUnk.get()->QueryInterface(IID_IAccessibleTableCell, + getter_AddRefs(cell)); + if (SUCCEEDED(hr)) { + hr = cell->get_rowColumnExtents( + &aOutIA2Data->mRowIndex, &aOutIA2Data->mColumnIndex, + &aOutIA2Data->mRowExtent, &aOutIA2Data->mColumnExtent, + &aOutIA2Data->mCellIsSelected); + if (FAILED(hr)) { + return; + } + + // Because the same headers can apply to many cells, include the ids of + // header cells, rather than the actual objects. Otherwise, we might + // end up marshaling the same objects (and their payloads) many times. + IUnknown** headers = nullptr; + hr = cell->get_rowHeaderCells(&headers, &aOutIA2Data->mNRowHeaderCells); + if (FAILED(hr)) { + return; + } + if (aOutIA2Data->mNRowHeaderCells > 0) { + aOutIA2Data->mRowHeaderCellIds = static_cast<long*>( + allocMem(sizeof(long) * aOutIA2Data->mNRowHeaderCells)); + for (long i = 0; i < aOutIA2Data->mNRowHeaderCells; ++i) { + RefPtr<IAccessible2> headerAcc; + hr = headers[i]->QueryInterface(IID_IAccessible2, + getter_AddRefs(headerAcc)); + MOZ_ASSERT(SUCCEEDED(hr)); + headers[i]->Release(); + hr = headerAcc->get_uniqueID(&aOutIA2Data->mRowHeaderCellIds[i]); + MOZ_ASSERT(SUCCEEDED(hr)); + } + } + ::CoTaskMemFree(headers); + + hr = cell->get_columnHeaderCells(&headers, + &aOutIA2Data->mNColumnHeaderCells); + if (FAILED(hr)) { + return; + } + if (aOutIA2Data->mNColumnHeaderCells > 0) { + aOutIA2Data->mColumnHeaderCellIds = static_cast<long*>( + allocMem(sizeof(long) * aOutIA2Data->mNColumnHeaderCells)); + for (long i = 0; i < aOutIA2Data->mNColumnHeaderCells; ++i) { + RefPtr<IAccessible2> headerAcc; + hr = headers[i]->QueryInterface(IID_IAccessible2, + getter_AddRefs(headerAcc)); + MOZ_ASSERT(SUCCEEDED(hr)); + headers[i]->Release(); + hr = headerAcc->get_uniqueID(&aOutIA2Data->mColumnHeaderCellIds[i]); + MOZ_ASSERT(SUCCEEDED(hr)); + } + } + ::CoTaskMemFree(headers); + } + + // NB: get_uniqueID should be the final property retrieved in this method, + // as its presence is used to determine whether the rest of this data + // retrieval was successful. + hr = target->get_uniqueID(&aOutIA2Data->mUniqueId); +} + +void HandlerProvider::BuildInitialIA2Data( + NotNull<mscom::IInterceptor*> aInterceptor, StaticIA2Data* aOutStaticData, + DynamicIA2Data* aOutDynamicData) { + BuildStaticIA2Data(aInterceptor, aOutStaticData); + if (!aOutStaticData->mIA2) { + return; + } + BuildDynamicIA2Data(aOutDynamicData); +} + +bool HandlerProvider::IsTargetInterfaceCacheable() { + return MarshalAs(mTargetUnkIid) == NEWEST_IA2_IID || + mTargetUnkIid == IID_IAccessibleHyperlink; +} + +HRESULT +HandlerProvider::WriteHandlerPayload(NotNull<mscom::IInterceptor*> aInterceptor, + NotNull<IStream*> aStream) { + if (!IsTargetInterfaceCacheable()) { + // No handler, so no payload for this instance. + return E_NOTIMPL; + } + + MutexAutoLock lock(mMutex); + + if (!mSerializer || !(*mSerializer)) { + // Failed payload serialization is non-fatal + mscom::StructToStream emptyStruct; + return emptyStruct.Write(aStream); + } + + HRESULT hr = mSerializer->Write(aStream); + + mSerializer.reset(); + + return hr; +} + +REFIID +HandlerProvider::MarshalAs(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IDispatch || aIid == IID_IAccessible || + aIid == IID_IAccessible2 || aIid == IID_IAccessible2_2 || + aIid == IID_IAccessible2_3) { + // This should always be the newest IA2 interface ID + return NEWEST_IA2_IID; + } + // Otherwise we juse return the identity. + return aIid; +} + +HRESULT +HandlerProvider::DisconnectHandlerRemotes() { + // If a handlerProvider call is pending on another thread, + // CoDisconnectObject won't release this HandlerProvider immediately. + // However, the interceptor and its target (mTargetUnk) might be destroyed. + mTargetUnk = nullptr; + + IUnknown* unk = static_cast<IGeckoBackChannel*>(this); + return ::CoDisconnectObject(unk, 0); +} + +HRESULT +HandlerProvider::IsInterfaceMaybeSupported(REFIID aIid) { + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if (aIid == IID_IUnknown || aIid == IID_IDispatch || + aIid == IID_IAccessible || aIid == IID_IServiceProvider || + aIid == IID_IEnumVARIANT || aIid == IID_IAccessible2 || + aIid == IID_IAccessible2_2 || aIid == IID_IAccessible2_3 || + aIid == IID_IAccessibleAction || aIid == IID_IAccessibleApplication || + aIid == IID_IAccessibleComponent || aIid == IID_IAccessibleDocument || + aIid == IID_IAccessibleEditableText || aIid == IID_IAccessibleHyperlink || + aIid == IID_IAccessibleHypertext || aIid == IID_IAccessibleHypertext2 || + aIid == IID_IAccessibleImage || aIid == IID_IAccessibleRelation || + aIid == IID_IAccessibleTable || aIid == IID_IAccessibleTable2 || + aIid == IID_IAccessibleTableCell || aIid == IID_IAccessibleText || + aIid == IID_IAccessibleValue || aIid == IID_ISimpleDOMNode || + aIid == IID_ISimpleDOMDocument || aIid == IID_ISimpleDOMText || + aIid == IID_IAccessibleEx || aIid == IID_IRawElementProviderSimple) { + return S_OK; + } + return E_NOINTERFACE; +} + +REFIID +HandlerProvider::GetEffectiveOutParamIid(REFIID aCallIid, ULONG aCallMethod) { + if (aCallIid == IID_IAccessibleTable || aCallIid == IID_IAccessibleTable2 || + aCallIid == IID_IAccessibleDocument || + aCallIid == IID_IAccessibleTableCell || + aCallIid == IID_IAccessibleRelation) { + return NEWEST_IA2_IID; + } + + // IAccessible2_2::accessibleWithCaret + static_assert(&NEWEST_IA2_IID == &IID_IAccessible2_3, + "You have modified NEWEST_IA2_IID. This code needs updating."); + if ((aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) && + aCallMethod == 47) { + return NEWEST_IA2_IID; + } + + // IAccessible::get_accSelection + if ((aCallIid == IID_IAccessible || aCallIid == IID_IAccessible2 || + aCallIid == IID_IAccessible2_2 || aCallIid == IID_IAccessible2_3) && + aCallMethod == 19) { + return IID_IEnumVARIANT; + } + + MOZ_ASSERT(false); + return IID_IUnknown; +} + +HRESULT +HandlerProvider::NewInstance( + REFIID aIid, mscom::InterceptorTargetPtr<IUnknown> aTarget, + NotNull<mscom::IHandlerProvider**> aOutNewPayload) { + RefPtr<IHandlerProvider> newPayload( + new HandlerProvider(aIid, std::move(aTarget))); + newPayload.forget(aOutNewPayload.get()); + return S_OK; +} + +void HandlerProvider::SetHandlerControlOnMainThread( + DWORD aPid, mscom::ProxyUniquePtr<IHandlerControl> aCtrl) { + MOZ_ASSERT(NS_IsMainThread()); + + auto content = dom::ContentChild::GetSingleton(); + MOZ_ASSERT(content); + + IHandlerControlHolder holder( + CreateHolderFromHandlerControl(std::move(aCtrl))); + Unused << content->SendA11yHandlerControl(aPid, holder); +} + +HRESULT +HandlerProvider::put_HandlerControl(long aPid, IHandlerControl* aCtrl) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!aCtrl) { + return E_INVALIDARG; + } + + auto ptrProxy = mscom::ToProxyUniquePtr(aCtrl); + + if (!mscom::InvokeOnMainThread( + "HandlerProvider::SetHandlerControlOnMainThread", this, + &HandlerProvider::SetHandlerControlOnMainThread, + static_cast<DWORD>(aPid), std::move(ptrProxy))) { + return E_FAIL; + } + + return S_OK; +} + +HRESULT +HandlerProvider::Refresh(DynamicIA2Data* aOutData) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + if (!mscom::InvokeOnMainThread("HandlerProvider::BuildDynamicIA2Data", this, + &HandlerProvider::BuildDynamicIA2Data, + std::forward<DynamicIA2Data*>(aOutData), + /* aMarshaledByCom */ true)) { + return E_FAIL; + } + + if (!aOutData->mUniqueId) { + // BuildDynamicIA2Data failed. + if (!mTargetUnk) { + // Even though we checked this before, the accessible can be shut down + // before BuildDynamicIA2Data executes on the main thread. + return CO_E_OBJNOTCONNECTED; + } + return E_FAIL; + } + + return S_OK; +} + +void HandlerProvider::PrebuildPayload( + NotNull<mscom::IInterceptor*> aInterceptor) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mPayloadMutex); + mPayload.reset(new IA2Payload()); + BuildInitialIA2Data(aInterceptor, &mPayload->mStaticData, + &mPayload->mDynamicData); + if (!mPayload->mDynamicData.mUniqueId) { + // Building the payload failed. + mPayload.reset(); + } +} + +template <typename Interface> +HRESULT HandlerProvider::ToWrappedObject(Interface** aObj) { + MOZ_ASSERT(NS_IsMainThread()); + mscom::STAUniquePtr<Interface> inObj(*aObj); + RefPtr<HandlerProvider> hprov = new HandlerProvider( + __uuidof(Interface), mscom::ToInterceptorTargetPtr(inObj)); + HRESULT hr = + mscom::MainThreadHandoff::WrapInterface(std::move(inObj), hprov, aObj); + if (FAILED(hr)) { + *aObj = nullptr; + return hr; + } + // Build the payload for this object now to avoid a cross-thread call when + // marshaling it later. + RefPtr<mscom::IInterceptor> interceptor; + hr = (*aObj)->QueryInterface(mscom::IID_IInterceptor, + getter_AddRefs(interceptor)); + MOZ_ASSERT(SUCCEEDED(hr)); + // Even though we created a new HandlerProvider, that won't be used if + // there's an existing Interceptor. Therefore, we must get the + // HandlerProvider from the Interceptor. + RefPtr<mscom::IInterceptorSink> interceptorSink; + interceptor->GetEventSink(getter_AddRefs(interceptorSink)); + MOZ_ASSERT(interceptorSink); + RefPtr<mscom::IMainThreadHandoff> handoff; + hr = interceptorSink->QueryInterface(mscom::IID_IMainThreadHandoff, + getter_AddRefs(handoff)); + // If a11y Interceptors stop using MainThreadHandoff as their event sink, we + // *really* want to know about it ASAP. + MOZ_DIAGNOSTIC_ASSERT(SUCCEEDED(hr), + "A11y Interceptor isn't using MainThreadHandoff"); + RefPtr<mscom::IHandlerProvider> usedIHprov; + handoff->GetHandlerProvider(getter_AddRefs(usedIHprov)); + MOZ_ASSERT(usedIHprov); + auto usedHprov = static_cast<HandlerProvider*>(usedIHprov.get()); + usedHprov->PrebuildPayload(WrapNotNull(interceptor)); + return hr; +} + +void HandlerProvider::GetAllTextInfoMainThread( + BSTR* aText, IAccessibleHyperlink*** aHyperlinks, long* aNHyperlinks, + IA2TextSegment** aAttribRuns, long* aNAttribRuns, HRESULT* result) { + MOZ_ASSERT(aText); + MOZ_ASSERT(aHyperlinks); + MOZ_ASSERT(aNHyperlinks); + MOZ_ASSERT(aAttribRuns); + MOZ_ASSERT(aNAttribRuns); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *result = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<IAccessibleHypertext2> ht; + HRESULT hr = + mTargetUnk->QueryInterface(IID_IAccessibleHypertext2, getter_AddRefs(ht)); + if (FAILED(hr)) { + *result = hr; + return; + } + + hr = ht->get_text(0, IA2_TEXT_OFFSET_LENGTH, aText); + if (FAILED(hr)) { + *result = hr; + return; + } + + if (hr == S_FALSE) { + // No text. + *aHyperlinks = nullptr; + *aNHyperlinks = 0; + *aAttribRuns = nullptr; + *aNAttribRuns = 0; + *result = S_FALSE; + return; + } + + hr = ht->get_hyperlinks(aHyperlinks, aNHyperlinks); + if (FAILED(hr)) { + *aHyperlinks = nullptr; + // -1 signals to the handler that it should call hyperlinks itself. + *aNHyperlinks = -1; + } + // We must wrap these hyperlinks in an interceptor. + for (long index = 0; index < *aNHyperlinks; ++index) { + ToWrappedObject(&(*aHyperlinks)[index]); + } + + // Fetch all attribute runs. + nsTArray<IA2TextSegment> attribRuns; + long end = 0; + long length = ::SysStringLen(*aText); + while (end < length) { + long offset = end; + long start; + BSTR attribs; + // The (exclusive) end of the last run is the start of the next run. + hr = ht->get_attributes(offset, &start, &end, &attribs); + // Bug 1421873: Gecko can return end <= offset in some rare cases, which + // isn't valid. This is perhaps because the text mutated during the loop + // for some reason, making this offset invalid. + if (FAILED(hr) || end <= offset) { + break; + } + attribRuns.AppendElement(IA2TextSegment({attribs, start, end})); + } + + // Put the attribute runs in a COM array. + *aNAttribRuns = attribRuns.Length(); + *aAttribRuns = static_cast<IA2TextSegment*>( + ::CoTaskMemAlloc(sizeof(IA2TextSegment) * *aNAttribRuns)); + for (long index = 0; index < *aNAttribRuns; ++index) { + (*aAttribRuns)[index] = attribRuns[index]; + } + + *result = S_OK; +} + +HRESULT +HandlerProvider::get_AllTextInfo(BSTR* aText, + IAccessibleHyperlink*** aHyperlinks, + long* aNHyperlinks, + IA2TextSegment** aAttribRuns, + long* aNAttribRuns) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetAllTextInfoMainThread", this, + &HandlerProvider::GetAllTextInfoMainThread, + std::forward<BSTR*>(aText), + std::forward<IAccessibleHyperlink***>(aHyperlinks), + std::forward<long*>(aNHyperlinks), + std::forward<IA2TextSegment**>(aAttribRuns), + std::forward<long*>(aNAttribRuns), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +void HandlerProvider::GetRelationsInfoMainThread(IARelationData** aRelations, + long* aNRelations, + HRESULT* hr) { + MOZ_ASSERT(aRelations); + MOZ_ASSERT(aNRelations); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *hr = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> acc; + *hr = mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(acc)); + if (FAILED(*hr)) { + return; + } + + *hr = acc->get_nRelations(aNRelations); + if (FAILED(*hr)) { + return; + } + + auto rawRels = MakeUnique<IAccessibleRelation*[]>(*aNRelations); + *hr = acc->get_relations(*aNRelations, rawRels.get(), aNRelations); + if (FAILED(*hr)) { + return; + } + + *aRelations = static_cast<IARelationData*>( + ::CoTaskMemAlloc(sizeof(IARelationData) * *aNRelations)); + for (long index = 0; index < *aNRelations; ++index) { + IAccessibleRelation* rawRel = rawRels[index]; + IARelationData& relData = (*aRelations)[index]; + *hr = rawRel->get_relationType(&relData.mType); + if (FAILED(*hr)) { + relData.mType = nullptr; + } + *hr = rawRel->get_nTargets(&relData.mNTargets); + if (FAILED(*hr)) { + relData.mNTargets = -1; + } + rawRel->Release(); + } + + *hr = S_OK; +} + +HRESULT +HandlerProvider::get_RelationsInfo(IARelationData** aRelations, + long* aNRelations) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + if (!mTargetUnk) { + return CO_E_OBJNOTCONNECTED; + } + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetRelationsInfoMainThread", this, + &HandlerProvider::GetRelationsInfoMainThread, + std::forward<IARelationData**>(aRelations), + std::forward<long*>(aNRelations), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +// Helper function for GetAllChildrenMainThread. +static bool SetChildDataForTextLeaf(NEWEST_IA2_INTERFACE* acc, + AccChildData& data) { + const VARIANT kChildIdSelf = {VT_I4}; + VARIANT varVal; + + // 1. Check whether this is a text leaf. + + // 1.1. A text leaf always has ROLE_SYSTEM_TEXT or ROLE_SYSTEM_WHITESPACE. + HRESULT hr = acc->get_accRole(kChildIdSelf, &varVal); + if (FAILED(hr)) { + return false; + } + if (varVal.vt != VT_I4) { + return false; + } + long role = varVal.lVal; + if (role != ROLE_SYSTEM_TEXT && role != ROLE_SYSTEM_WHITESPACE) { + return false; + } + + // 1.2. A text leaf doesn't support IAccessibleText. + RefPtr<IAccessibleText> iaText; + hr = acc->QueryInterface(IID_IAccessibleText, getter_AddRefs(iaText)); + if (SUCCEEDED(hr)) { + return false; + } + + // 1.3. A text leaf doesn't have children. + long count; + hr = acc->get_accChildCount(&count); + if (FAILED(hr) || count != 0) { + return false; + } + + // 2. Update |data| with the data for this text leaf. + // Because marshaling objects is more expensive than marshaling other data, + // we just marshal the data we need for text leaf children, rather than + // marshaling the full accessible object. + + // |data| has already been zeroed, so we don't need to do anything if these + // calls fail. + acc->get_accName(kChildIdSelf, &data.mText); + data.mTextRole = role; + acc->get_uniqueID(&data.mTextId); + acc->get_accState(kChildIdSelf, &varVal); + data.mTextState = varVal.lVal; + acc->accLocation(&data.mTextLeft, &data.mTextTop, &data.mTextWidth, + &data.mTextHeight, kChildIdSelf); + + return true; +} + +void HandlerProvider::GetAllChildrenMainThread(AccChildData** aChildren, + ULONG* aNChildren, HRESULT* hr) { + MOZ_ASSERT(aChildren); + MOZ_ASSERT(aNChildren); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mTargetUnk) { + *hr = CO_E_OBJNOTCONNECTED; + return; + } + + RefPtr<NEWEST_IA2_INTERFACE> acc; + *hr = mTargetUnk.get()->QueryInterface(NEWEST_IA2_IID, getter_AddRefs(acc)); + if (FAILED(*hr)) { + return; + } + + long count; + *hr = acc->get_accChildCount(&count); + if (FAILED(*hr)) { + return; + } + MOZ_ASSERT(count >= 0); + + if (count == 0) { + *aChildren = nullptr; + *aNChildren = 0; + return; + } + + RefPtr<IEnumVARIANT> enumVar; + *hr = mTargetUnk.get()->QueryInterface(IID_IEnumVARIANT, + getter_AddRefs(enumVar)); + if (FAILED(*hr)) { + return; + } + + auto rawChildren = MakeUnique<VARIANT[]>(count); + *hr = enumVar->Next((ULONG)count, rawChildren.get(), aNChildren); + if (FAILED(*hr)) { + *aChildren = nullptr; + *aNChildren = 0; + return; + } + + *aChildren = static_cast<AccChildData*>( + ::CoTaskMemAlloc(sizeof(AccChildData) * *aNChildren)); + for (ULONG index = 0; index < *aNChildren; ++index) { + (*aChildren)[index] = {}; + AccChildData& child = (*aChildren)[index]; + + MOZ_ASSERT(rawChildren[index].vt == VT_DISPATCH); + MOZ_ASSERT(rawChildren[index].pdispVal); + RefPtr<NEWEST_IA2_INTERFACE> childAcc; + *hr = rawChildren[index].pdispVal->QueryInterface(NEWEST_IA2_IID, + getter_AddRefs(childAcc)); + rawChildren[index].pdispVal->Release(); + MOZ_ASSERT(SUCCEEDED(*hr)); + if (FAILED(*hr)) { + continue; + } + + if (!SetChildDataForTextLeaf(childAcc, child)) { + // This isn't a text leaf. Marshal the accessible. + childAcc.forget(&child.mAccessible); + // We must wrap this accessible in an Interceptor. + ToWrappedObject(&child.mAccessible); + } + } + + *hr = S_OK; +} + +HRESULT +HandlerProvider::get_AllChildren(AccChildData** aChildren, ULONG* aNChildren) { + MOZ_ASSERT(mscom::IsCurrentThreadMTA()); + + HRESULT hr; + if (!mscom::InvokeOnMainThread( + "HandlerProvider::GetAllChildrenMainThread", this, + &HandlerProvider::GetAllChildrenMainThread, + std::forward<AccChildData**>(aChildren), + std::forward<ULONG*>(aNChildren), std::forward<HRESULT*>(&hr))) { + return E_FAIL; + } + + return hr; +} + +} // namespace a11y +} // namespace mozilla |