Bug 1272146: Add thunk for IAccessible property accesses that pass non-self child IDs; r?tbsaunde
MozReview-Commit-ID: Kx8UVGP2q7h
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/ChildIDThunk.cpp
@@ -0,0 +1,316 @@
+/* -*- 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 "ChildIDThunk.h"
+#include "MainThreadUtils.h"
+#include "mozilla/mscom/InterceptorLog.h"
+
+using namespace mozilla::mscom;
+
+namespace mozilla {
+namespace a11y {
+
+/* static */ HRESULT
+ChildIDThunk::Create(IInterceptorSink** aOutput)
+{
+ MOZ_ASSERT(aOutput);
+ *aOutput = nullptr;
+ ChildIDThunk* thunk = new ChildIDThunk();
+ HRESULT hr = thunk->QueryInterface(IID_IInterceptorSink, (void**)aOutput);
+ thunk->Release();
+ return hr;
+}
+
+ChildIDThunk::ChildIDThunk()
+ : mRefCnt(1)
+{
+}
+
+ChildIDThunk::~ChildIDThunk()
+{
+}
+
+HRESULT
+ChildIDThunk::QueryInterface(REFIID riid, void** ppv)
+{
+ IUnknown* punk = nullptr;
+ if (!ppv) {
+ return E_INVALIDARG;
+ }
+
+ if (riid == IID_IUnknown || riid == IID_ICallFrameEvents ||
+ riid == IID_IInterceptorSink) {
+ punk = static_cast<IInterceptorSink*>(this);
+ }
+
+ *ppv = punk;
+ if (!punk) {
+ return E_NOINTERFACE;
+ }
+
+ punk->AddRef();
+ return S_OK;
+}
+
+ULONG
+ChildIDThunk::AddRef()
+{
+ return (ULONG) InterlockedIncrement((LONG*)&mRefCnt);
+}
+
+ULONG
+ChildIDThunk::Release()
+{
+ ULONG newRefCnt = (ULONG) InterlockedDecrement((LONG*)&mRefCnt);
+ if (newRefCnt == 0) {
+ MOZ_ASSERT(NS_IsMainThread());
+ delete this;
+ }
+ return newRefCnt;
+}
+
+enum IAccessibleVTableIndex
+{
+ eget_accName = 10,
+ eget_accValue = 11,
+ eget_accDescription = 12,
+ eget_accRole = 13,
+ eget_accState = 14,
+ eget_accHelp = 15,
+ eget_accHelpTopic = 16,
+ eget_accKeyboardShortcut = 17,
+ eget_accDefaultAction = 20,
+ eaccLocation = 22,
+ eaccDoDefaultAction = 25,
+ eput_accName = 26,
+ eput_accValue = 27
+};
+
+bool
+ChildIDThunk::IsMethodInteresting(const ULONG aMethodIdx, ULONG& aChildIDIdx)
+{
+ switch (aMethodIdx) {
+ case eget_accName:
+ case eget_accValue:
+ case eget_accDescription:
+ case eget_accRole:
+ case eget_accState:
+ case eget_accHelp:
+ case eget_accKeyboardShortcut:
+ case eget_accDefaultAction:
+ case eaccDoDefaultAction:
+ case eput_accName:
+ case eput_accValue:
+ aChildIDIdx = 0;
+ return true;
+ case eget_accHelpTopic:
+ aChildIDIdx = 1;
+ return true;
+ case eaccLocation:
+ aChildIDIdx = 4;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+ChildIDThunk::IsChildIDSelf(ICallFrame* aFrame, const ULONG aChildIDIdx,
+ LONG& aOutChildID, VARTYPE& aOutVarType)
+{
+ VARIANT paramValue;
+ HRESULT hr = aFrame->GetParam(aChildIDIdx, ¶mValue);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ const bool isVariant = paramValue.vt & VT_VARIANT;
+ MOZ_ASSERT(isVariant);
+ if (!isVariant) {
+ return false;
+ }
+
+ VARIANT& childID = isVariant ? *(paramValue.pvarVal) : paramValue;
+ if (childID.vt != VT_I4) {
+ return false;
+ }
+
+ aOutChildID = childID.lVal;
+ aOutVarType = childID.vt;
+ return childID.lVal == CHILDID_SELF;
+}
+
+/* static */ bool
+ChildIDThunk::ValidateChildIDParamSize(const VARTYPE aVarType,
+ const ULONG aParamSize)
+{
+ if (aVarType != VT_VARIANT) {
+ return false;
+ }
+ return aParamSize == sizeof(VARIANT);
+}
+
+/**
+ * ICallFrame::SetParam always returns E_NOTIMPL, so we'll just have to work
+ * around this by manually poking at the parameter's location on the stack.
+ */
+/* static */ HRESULT
+ChildIDThunk::SetChildIDParam(ICallFrame* aFrame, const ULONG aParamIdx,
+ const VARTYPE aVarType, const LONG aChildID)
+{
+ PVOID stackBase = aFrame->GetStackLocation();
+ MOZ_ASSERT(stackBase);
+ if (!stackBase) {
+ return E_UNEXPECTED;
+ }
+
+ CALLFRAMEPARAMINFO paramInfo;
+ HRESULT hr = aFrame->GetParamInfo(aParamIdx, ¶mInfo);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // We expect this childID to be a VARIANT in-parameter
+ MOZ_ASSERT(paramInfo.fIn);
+ MOZ_ASSERT(ValidateChildIDParamSize(aVarType, paramInfo.cbParam));
+ if (!paramInfo.fIn || !ValidateChildIDParamSize(aVarType, paramInfo.cbParam)) {
+ return E_UNEXPECTED;
+ }
+
+ // Given the stack base and param offset, calculate the address of the param
+ // and replace its value
+ switch (aVarType) {
+ case VT_VARIANT: {
+ VARIANT* pInParam = reinterpret_cast<VARIANT*>(
+ reinterpret_cast<PBYTE>(stackBase) + paramInfo.stackOffset);
+ MOZ_ASSERT(pInParam->vt == VT_I4);
+ if (pInParam->vt != VT_I4) {
+ return E_UNEXPECTED;
+ }
+ pInParam->lVal = aChildID;
+ return S_OK;
+ }
+ default: {
+ return E_UNEXPECTED;
+ }
+ }
+}
+
+HRESULT
+ChildIDThunk::ResolveTargetInterface(REFIID aIid, IUnknown** aOut)
+{
+ MOZ_ASSERT(aOut);
+ if (!aOut) {
+ return E_INVALIDARG;
+ }
+ *aOut = nullptr;
+
+ RefPtr<IInterceptor> interceptor;
+ HRESULT hr = mInterceptor->Resolve(IID_IInterceptor,
+ (void**)getter_AddRefs(interceptor));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ InterceptorTargetPtr target;
+ hr = interceptor->GetTargetForIID(aIid, target);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ RefPtr<IUnknown> refTarget = target.get();
+ refTarget.forget(aOut);
+ return S_OK;
+}
+
+HRESULT
+ChildIDThunk::OnCall(ICallFrame* aFrame)
+{
+#if defined(MOZILLA_INTERNAL_API)
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+#endif
+
+ IID iid;
+ ULONG method;
+ HRESULT hr = aFrame->GetIIDAndMethod(&iid, &method);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ RefPtr<IUnknown> target;
+ hr = ResolveTargetInterface(iid, getter_AddRefs(target));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ VARTYPE vt;
+ ULONG childIDIdx;
+ LONG childID;
+ if (iid != IID_IAccessible || !IsMethodInteresting(method, childIDIdx) ||
+ IsChildIDSelf(aFrame, childIDIdx, childID, vt)) {
+ // Just pass this call through to the root
+ hr = aFrame->Invoke(target);
+ if (SUCCEEDED(hr)) {
+ InterceptorLog::Event(aFrame, target);
+ }
+ return hr;
+ }
+
+ // Otherwise we need to resolve the child node...
+ RefPtr<IAccessible> acc;
+ hr = target->QueryInterface(iid, (void**)getter_AddRefs(acc));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ VARIANT vChildID;
+ VariantInit(&vChildID);
+ vChildID.vt = VT_I4;
+ vChildID.lVal = childID;
+
+ RefPtr<IDispatch> disp;
+ hr = acc->get_accChild(vChildID, getter_AddRefs(disp));
+ if (FAILED(hr)) {
+ aFrame->SetReturnValue(hr);
+ return S_OK;
+ }
+
+ RefPtr<IAccessible> directAccessible;
+ // Yes, given our implementation we could just downcast, but that's not very COMy
+ hr = disp->QueryInterface(IID_IAccessible,
+ (void**)getter_AddRefs(directAccessible));
+ if (FAILED(hr)) {
+ aFrame->SetReturnValue(hr);
+ return S_OK;
+ }
+
+ // Now that we have the IAccessible for the child ID we need to replace it
+ // in the activation record with a self-referant child ID
+ hr = SetChildIDParam(aFrame, childIDIdx, vt, CHILDID_SELF);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = aFrame->Invoke(directAccessible);
+ if (SUCCEEDED(hr)) {
+ InterceptorLog::Event(aFrame, directAccessible);
+ }
+ return hr;
+}
+
+HRESULT
+ChildIDThunk::SetInterceptor(IWeakReference* aInterceptor)
+{
+ mInterceptor = aInterceptor;
+ return S_OK;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/ChildIDThunk.h
@@ -0,0 +1,57 @@
+/* -*- 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_ChildIDThunk_h
+#define mozilla_a11y_ChildIDThunk_h
+
+#include "mozilla/mscom/Interceptor.h"
+#include "mozilla/RefPtr.h"
+
+#include <oleacc.h>
+#include <callobj.h>
+
+namespace mozilla {
+namespace a11y {
+
+class ChildIDThunk : public mozilla::mscom::IInterceptorSink
+{
+public:
+ static HRESULT Create(mozilla::mscom::IInterceptorSink** aOutput);
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
+ STDMETHODIMP_(ULONG) AddRef() override;
+ STDMETHODIMP_(ULONG) Release() override;
+
+ // ICallFrameEvents
+ STDMETHODIMP OnCall(ICallFrame* aFrame) override;
+
+ // IInterceptorSink
+ STDMETHODIMP SetInterceptor(mozilla::mscom::IWeakReference* aInterceptor) override;
+
+private:
+ ChildIDThunk();
+ ~ChildIDThunk();
+ HRESULT ResolveTargetInterface(REFIID aIid, IUnknown** aOut);
+
+ static bool IsMethodInteresting(const ULONG aMethodIdx, ULONG& aChildIdIdx);
+ static bool IsChildIDSelf(ICallFrame* aFrame, const ULONG aChildIdIdx,
+ LONG& aOutChildID, VARTYPE& aOutVarType);
+ static HRESULT SetChildIDParam(ICallFrame* aFrame, const ULONG aParamIdx,
+ const VARTYPE aVarType, const LONG aChildID);
+ static bool ValidateChildIDParamSize(const VARTYPE aVarType,
+ const ULONG aParamSize);
+
+private:
+ ULONG mRefCnt;
+ RefPtr<mozilla::mscom::IWeakReference> mInterceptor;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_ChildIDThunk_h
+
--- a/accessible/windows/msaa/RootAccessibleWrap.cpp
+++ b/accessible/windows/msaa/RootAccessibleWrap.cpp
@@ -1,35 +1,98 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "RootAccessibleWrap.h"
+#include "ChildIDThunk.h"
#include "Compatibility.h"
+#include "mozilla/mscom/interceptor.h"
#include "nsCoreUtils.h"
+#include "nsIXULRuntime.h"
#include "nsWinUtils.h"
using namespace mozilla::a11y;
+using namespace mozilla::mscom;
////////////////////////////////////////////////////////////////////////////////
-// Constructor/desctructor
+// Constructor/destructor
RootAccessibleWrap::
RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
RootAccessible(aDocument, aPresShell)
{
}
RootAccessibleWrap::~RootAccessibleWrap()
{
}
////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+RootAccessibleWrap::GetNativeInterface(void** aOutAccessible)
+{
+ MOZ_ASSERT(aOutAccessible);
+ if (!aOutAccessible) {
+ return;
+ }
+
+ if (mInterceptor &&
+ SUCCEEDED(mInterceptor->Resolve(IID_IAccessible, aOutAccessible))) {
+ return;
+ }
+
+ *aOutAccessible = nullptr;
+
+ IAccessible* rawRootAccessible = nullptr;
+ RootAccessible::GetNativeInterface((void**)&rawRootAccessible);
+
+ STAUniquePtr<IAccessible> rootAccessible(rawRootAccessible);
+ if (!mozilla::BrowserTabsRemoteAutostart() || XRE_IsContentProcess()) {
+ // We only need to wrap this interface if our process is non-content e10s
+ if (aOutAccessible) {
+ *aOutAccessible = rootAccessible.release();
+ }
+ return;
+ }
+
+ // Otherwise, we need to wrap that IAccessible with an interceptor
+ RefPtr<IInterceptorSink> eventSink;
+ HRESULT hr = ChildIDThunk::Create(getter_AddRefs(eventSink));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RefPtr<IAccessible> interceptor;
+ hr = CreateInterceptor<IAccessible>(rootAccessible, eventSink,
+ getter_AddRefs(interceptor));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ RefPtr<IWeakReferenceSource> weakRefSrc;
+ hr = interceptor->QueryInterface(IID_IWeakReferenceSource,
+ (void**)getter_AddRefs(weakRefSrc));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ hr = weakRefSrc->GetWeakReference(getter_AddRefs(mInterceptor));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ *aOutAccessible = interceptor.forget().take();
+}
+
+////////////////////////////////////////////////////////////////////////////////
// RootAccessible
void
RootAccessibleWrap::DocumentActivated(DocAccessible* aDocument)
{
if (Compatibility::IsDolphin() &&
nsCoreUtils::IsTabDocument(aDocument->DocumentNode())) {
uint32_t count = mChildDocuments.Length();
--- a/accessible/windows/msaa/RootAccessibleWrap.h
+++ b/accessible/windows/msaa/RootAccessibleWrap.h
@@ -4,24 +4,36 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_a11y_RootAccessibleWrap_h__
#define mozilla_a11y_RootAccessibleWrap_h__
#include "RootAccessible.h"
namespace mozilla {
+namespace mscom {
+
+struct IWeakReference;
+
+} // namespace mscom
+
namespace a11y {
class RootAccessibleWrap : public RootAccessible
{
public:
RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
virtual ~RootAccessibleWrap();
+ // Accessible
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
// RootAccessible
virtual void DocumentActivated(DocAccessible* aDocument);
+
+private:
+ RefPtr<mscom::IWeakReference> mInterceptor;
};
} // namespace a11y
} // namespace mozilla
#endif
--- a/accessible/windows/msaa/moz.build
+++ b/accessible/windows/msaa/moz.build
@@ -14,16 +14,17 @@ EXPORTS.mozilla.a11y += [
'HyperTextAccessibleWrap.h',
'IDSet.h',
]
UNIFIED_SOURCES += [
'AccessibleWrap.cpp',
'ApplicationAccessibleWrap.cpp',
'ARIAGridAccessibleWrap.cpp',
+ 'ChildIDThunk.cpp',
'Compatibility.cpp',
'DocAccessibleWrap.cpp',
'EnumVariant.cpp',
'HTMLTableAccessibleWrap.cpp',
'HTMLWin32ObjectAccessible.cpp',
'HyperTextAccessibleWrap.cpp',
'ImageAccessibleWrap.cpp',
'IUnknownImpl.cpp',
@@ -44,16 +45,17 @@ if CONFIG['MOZ_XUL']:
'XULMenuAccessibleWrap.cpp',
'XULTreeGridAccessibleWrap.cpp',
]
LOCAL_INCLUDES += [
'/accessible/base',
'/accessible/generic',
'/accessible/html',
+ '/accessible/ipc/win',
'/accessible/windows',
'/accessible/windows/ia2',
'/accessible/windows/sdn',
'/accessible/windows/uia',
'/accessible/xpcom',
'/accessible/xul',
'/dom/base',
'/layout/style',