Bug 1272146: Add thunk for IAccessible property accesses that pass non-self child IDs; r?tbsaunde draft
authorAaron Klotz <aklotz@mozilla.com>
Fri, 22 Jul 2016 13:24:06 -0600
changeset 392062 e2fbb0ebc3bddd45066bcd9a9fa380ff2899da89
parent 392061 630a0c3daef55f118f7ad9f3c5f51dbe1f807491
child 392063 e74982866e033e4aa23954d4cf64d5c4905559db
push id23925
push useraklotz@mozilla.com
push dateFri, 22 Jul 2016 21:35:17 +0000
reviewerstbsaunde
bugs1272146
milestone50.0a1
Bug 1272146: Add thunk for IAccessible property accesses that pass non-self child IDs; r?tbsaunde MozReview-Commit-ID: Kx8UVGP2q7h
accessible/windows/msaa/ChildIDThunk.cpp
accessible/windows/msaa/ChildIDThunk.h
accessible/windows/msaa/RootAccessibleWrap.cpp
accessible/windows/msaa/RootAccessibleWrap.h
accessible/windows/msaa/moz.build
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, &paramValue);
+  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, &paramInfo);
+  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',