Bug 1431264 part 3: AccessibleHandler: If a client wants to query all relations, fetch as much info as possible in a single cross-process call. r?MarcoZ
If a client calls IAccessible2::nRelations, it's likely that it will next call IAccessible2::relations to query each relation.
Furthermore, it's likely the client will call relationType and nTargets on each relation.
Therefore, fetch all of this info when nRelations is called.
The number of relations is immediately returned to the client.
The rest of the info is cached and returned to the client when the appropriate methods are called.
The info is only cached for one call; i.e. after the client calls relations once, the cache is dropped.
This makes memory management simpler and lowers the risk of cache invalidation problems.
MozReview-Commit-ID: IBoJbu42osG
--- a/accessible/ipc/win/handler/AccessibleHandler.cpp
+++ b/accessible/ipc/win/handler/AccessibleHandler.cpp
@@ -7,16 +7,17 @@
#if defined(MOZILLA_INTERNAL_API)
#error This code is NOT for internal Gecko use!
#endif // defined(MOZILLA_INTERNAL_API)
#define INITGUID
#include "AccessibleHandler.h"
#include "AccessibleHandlerControl.h"
+#include "HandlerRelation.h"
#include "Factory.h"
#include "HandlerData.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/a11y/HandlerDataCleanup.h"
#include "mozilla/mscom/Registration.h"
#include "mozilla/UniquePtr.h"
@@ -73,16 +74,18 @@ AccessibleHandler::AccessibleHandler(IUn
, mIATableCellPassThru(nullptr)
, mIAHypertextPassThru(nullptr)
, mCachedData()
, mCacheGen(0)
, mCachedHyperlinks(nullptr)
, mCachedNHyperlinks(-1)
, mCachedTextAttribRuns(nullptr)
, mCachedNTextAttribRuns(-1)
+ , mCachedRelations(nullptr)
+ , mCachedNRelations(-1)
{
RefPtr<AccessibleHandlerControl> ctl(gControlFactory.GetOrCreateSingleton());
MOZ_ASSERT(ctl);
if (!ctl) {
if (aResult) {
*aResult = E_UNEXPECTED;
}
return;
@@ -94,16 +97,17 @@ AccessibleHandler::AccessibleHandler(IUn
AccessibleHandler::~AccessibleHandler()
{
// No need to zero memory, since we're being destroyed anyway.
CleanupDynamicIA2Data(mCachedData.mDynamicData, false);
if (mCachedData.mGeckoBackChannel) {
mCachedData.mGeckoBackChannel->Release();
}
ClearTextCache();
+ ClearRelationCache();
}
HRESULT
AccessibleHandler::ResolveIA2()
{
if (mIA2PassThru) {
return S_OK;
}
@@ -250,16 +254,50 @@ AccessibleHandler::ClearTextCache()
// 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;
@@ -557,22 +595,16 @@ AccessibleHandler::Invoke(DISPID dispIdM
if (FAILED(hr)) {
return hr;
}
return mDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams,
pVarResult, pExcepInfo, puArgErr);
}
-inline static BSTR
-CopyBSTR(BSTR aSrc)
-{
- return ::SysAllocStringLen(aSrc, ::SysStringLen(aSrc));
-}
-
#define BEGIN_CACHE_ACCESS \
{ \
HRESULT hr; \
if (FAILED(hr = MaybeUpdateCachedData())) { \
return hr; \
} \
}
@@ -905,17 +937,33 @@ AccessibleHandler::put_accValue(VARIANT
return E_NOTIMPL;
}
/*** IAccessible2 ***/
HRESULT
AccessibleHandler::get_nRelations(long* nRelations)
{
- HRESULT hr = ResolveIA2();
+ 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,
@@ -928,16 +976,38 @@ AccessibleHandler::get_relation(long rel
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
--- a/accessible/ipc/win/handler/AccessibleHandler.h
+++ b/accessible/ipc/win/handler/AccessibleHandler.h
@@ -249,16 +249,18 @@ private:
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
@@ -283,18 +285,26 @@ private:
IAccessibleHypertext2* mIAHypertextPassThru; // weak
IA2Payload mCachedData;
UniquePtr<mscom::StructToStream> mSerializer;
uint32_t mCacheGen;
IAccessibleHyperlink** mCachedHyperlinks;
long mCachedNHyperlinks;
IA2TextSegment* mCachedTextAttribRuns;
long mCachedNTextAttribRuns;
+ IARelationData* mCachedRelations;
+ long mCachedNRelations;
};
+inline static BSTR
+CopyBSTR(BSTR aSrc)
+{
+ return ::SysAllocStringLen(aSrc, ::SysStringLen(aSrc));
+}
+
} // namespace a11y
} // namespace mozilla
#endif // !defined(MOZILLA_INTERNAL_API)
#endif // defined(__midl)
#endif // mozilla_a11y_AccessibleHandler_h