Bug 1272146: Add thunk for IAccessible property accesses that pass non-self child IDs; r=tbsaunde
authorAaron Klotz <aklotz@mozilla.com>
Thu, 18 Aug 2016 00:50:03 -0600
changeset 310456 42ac31b54b3797949ea2747898e66a7fd7f42678
parent 310455 bf20e2879a4e8e52e8af7641cf913f3f20859b10
child 310457 d867848c7e66402e7658a4103964ded227779fc8
push id30585
push userryanvm@gmail.com
push dateMon, 22 Aug 2016 13:41:00 +0000
treeherdermozilla-central@194fe275b4e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs1272146
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1272146: Add thunk for IAccessible property accesses that pass non-self child IDs; r=tbsaunde MozReview-Commit-ID: Kx8UVGP2q7h
accessible/windows/msaa/AccessibleWrap.cpp
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
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -918,32 +918,16 @@ AccessibleWrap::accNavigate(
 
   Accessible* accessible = GetXPAccessibleFor(varStart);
   if (!accessible)
     return E_INVALIDARG;
 
   if (accessible->IsDefunct())
     return CO_E_OBJNOTCONNECTED;
 
-  // Make sure that varStart != CHILDID_SELF so we don't infinitely recurse
-  if (accessible->IsProxy() && varStart.vt == VT_I4 &&
-      varStart.lVal != CHILDID_SELF) {
-    // Now that we have the starting object, delegate this request to that
-    // object as a self-relative request...
-    RefPtr<IAccessible> comProxy;
-    accessible->GetNativeInterface((void**) getter_AddRefs(comProxy));
-    if (!comProxy) {
-      return E_UNEXPECTED;
-    }
-    VARIANT selfChildId;
-    selfChildId.vt = VT_I4;
-    selfChildId.lVal = CHILDID_SELF;
-    return comProxy->accNavigate(navDir, selfChildId, pvarEndUpAt);
-  }
-
   Accessible* navAccessible = nullptr;
   Maybe<RelationType> xpRelation;
 
 #define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
   case msaaType: \
     xpRelation.emplace(RelationType::geckoType); \
     break;
 
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/ChildIDThunk.cpp
@@ -0,0 +1,282 @@
+/* -*- 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 {
+
+ChildIDThunk::ChildIDThunk()
+  : mRefCnt(0)
+{
+}
+
+HRESULT
+ChildIDThunk::QueryInterface(REFIID riid, void** ppv)
+{
+  if (!ppv) {
+    return E_INVALIDARG;
+  }
+
+  if (riid == IID_IUnknown || riid == IID_ICallFrameEvents ||
+      riid == IID_IInterceptorSink) {
+    *ppv = static_cast<IInterceptorSink*>(this);
+    AddRef();
+    return S_OK;
+  }
+
+  *ppv = nullptr;
+  return E_NOINTERFACE;
+}
+
+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,
+  eaccSelect = 21,
+  eaccLocation = 22,
+  eaccNavigate = 23,
+  eaccDoDefaultAction = 25,
+  eput_accName = 26,
+  eput_accValue = 27
+};
+
+Maybe<ULONG>
+ChildIDThunk::IsMethodInteresting(const ULONG aMethodIdx)
+{
+  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:
+      return Some(0UL);
+    case eget_accHelpTopic:
+    case eaccNavigate:
+      return Some(1UL);
+    case eaccSelect:
+      return Some(2UL);
+    case eaccLocation:
+      return Some(4UL);
+    default:
+      return Nothing();
+  }
+}
+
+bool
+ChildIDThunk::IsChildIDSelf(ICallFrame* aFrame, const ULONG aChildIDIdx,
+                            LONG& aOutChildID)
+{
+  VARIANT paramValue;
+  HRESULT hr = aFrame->GetParam(aChildIDIdx, &paramValue);
+  MOZ_ASSERT(SUCCEEDED(hr));
+  if (FAILED(hr)) {
+    return false;
+  }
+
+  const bool isVariant = paramValue.vt & VT_VARIANT;
+  MOZ_ASSERT(isVariant);
+  if (!isVariant) {
+    return false;
+  }
+
+  VARIANT& childID = *(paramValue.pvarVal);
+  if (childID.vt != VT_I4) {
+    return false;
+  }
+
+  aOutChildID = childID.lVal;
+  return childID.lVal == CHILDID_SELF;
+}
+
+/**
+ * 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 LONG aChildID)
+{
+  void* 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(paramInfo.cbParam == sizeof(VARIANT));
+  if (!paramInfo.fIn || paramInfo.cbParam != sizeof(VARIANT)) {
+    return E_UNEXPECTED;
+  }
+
+  // Given the stack base and param offset, calculate the address of the param
+  // and replace its value
+  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;
+}
+
+HRESULT
+ChildIDThunk::ResolveTargetInterface(REFIID aIid, IUnknown** aOut)
+{
+  MOZ_ASSERT(aOut);
+  *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);
+  MOZ_ASSERT(SUCCEEDED(hr));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  RefPtr<IUnknown> target;
+  hr = ResolveTargetInterface(iid, getter_AddRefs(target));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  Maybe<ULONG> childIDIdx;
+  LONG childID;
+  if (iid != IID_IAccessible ||
+      (childIDIdx = IsMethodInteresting(method)).isNothing() ||
+      IsChildIDSelf(aFrame, childIDIdx.value(), childID)) {
+    // We're only interested in methods which accept a child ID parameter which
+    // is not equal to CHILDID_SELF. Otherwise we just invoke against the
+    // original target interface.
+    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.value(), 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,54 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/RefPtr.h"
+
+#include <oleacc.h>
+#include <callobj.h>
+
+namespace mozilla {
+namespace a11y {
+
+class ChildIDThunk : public mozilla::mscom::IInterceptorSink
+{
+public:
+  ChildIDThunk();
+
+  // 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:
+  HRESULT ResolveTargetInterface(REFIID aIid, IUnknown** aOut);
+
+  static mozilla::Maybe<ULONG> IsMethodInteresting(const ULONG aMethodIdx);
+  static bool IsChildIDSelf(ICallFrame* aFrame, const ULONG aChildIdIdx,
+                            LONG& aOutChildID);
+  static HRESULT SetChildIDParam(ICallFrame* aFrame, const ULONG aParamIdx,
+                                 const LONG aChildID);
+
+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,92 @@
 /* -*- 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;
+
+  RefPtr<IAccessible> rootAccessible;
+  RootAccessible::GetNativeInterface((void**)getter_AddRefs(rootAccessible));
+
+  if (!mozilla::BrowserTabsRemoteAutostart() || XRE_IsContentProcess()) {
+    // We only need to wrap this interface if our process is non-content e10s
+    rootAccessible.forget(aOutAccessible);
+    return;
+  }
+
+  // Otherwise, we need to wrap that IAccessible with an interceptor
+  RefPtr<IInterceptorSink> eventSink(MakeAndAddRef<ChildIDThunk>());
+
+  RefPtr<IAccessible> interceptor;
+  HRESULT hr = CreateInterceptor<IAccessible>(
+      STAUniquePtr<IAccessible>(rootAccessible.forget().take()), 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;
+  }
+
+  interceptor.forget(aOutAccessible);
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // 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',