summaryrefslogtreecommitdiffstats
path: root/accessible/ipc/win/handler
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /accessible/ipc/win/handler
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/ipc/win/handler')
-rw-r--r--accessible/ipc/win/handler/AccessibleHandler.cpp2189
-rw-r--r--accessible/ipc/win/handler/AccessibleHandler.def12
-rw-r--r--accessible/ipc/win/handler/AccessibleHandler.h336
-rw-r--r--accessible/ipc/win/handler/AccessibleHandler.rc5
-rw-r--r--accessible/ipc/win/handler/AccessibleHandlerControl.cpp221
-rw-r--r--accessible/ipc/win/handler/AccessibleHandlerControl.h103
-rw-r--r--accessible/ipc/win/handler/HandlerChildEnumerator.cpp170
-rw-r--r--accessible/ipc/win/handler/HandlerChildEnumerator.h51
-rw-r--r--accessible/ipc/win/handler/HandlerData.acf11
-rw-r--r--accessible/ipc/win/handler/HandlerData.idl155
-rw-r--r--accessible/ipc/win/handler/HandlerDataCleanup.h95
-rw-r--r--accessible/ipc/win/handler/HandlerDataUUID.h.in16
-rw-r--r--accessible/ipc/win/handler/HandlerRelation.cpp138
-rw-r--r--accessible/ipc/win/handler/HandlerRelation.h46
-rw-r--r--accessible/ipc/win/handler/HandlerTextLeaf.cpp337
-rw-r--r--accessible/ipc/win/handler/HandlerTextLeaf.h110
-rw-r--r--accessible/ipc/win/handler/moz.build132
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",
+]