/* -*- 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 "LazyInstantiator.h" #include "MainThreadUtils.h" #include "mozilla/a11y/Accessible.h" #include "mozilla/a11y/Platform.h" #include "mozilla/Assertions.h" #include "mozilla/mscom/MainThreadRuntime.h" #include "mozilla/mscom/Registration.h" #include "mozilla/UniquePtr.h" #include "nsAccessibilityService.h" #include "nsWindowsHelpers.h" #include "nsCOMPtr.h" #include "nsIFile.h" #include "nsXPCOM.h" #include "RootAccessibleWrap.h" #include "WinUtils.h" #if defined(MOZ_TELEMETRY_REPORTING) #include "mozilla/Telemetry.h" #endif // defined(MOZ_TELEMETRY_REPORTING) #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif #include #if !defined(STATE_SYSTEM_NORMAL) #define STATE_SYSTEM_NORMAL (0) #endif // !defined(STATE_SYSTEM_NORMAL) /** * Because our wrapped accessible is cycle-collected, we can't safely AddRef() * or Release() ourselves off the main thread. This template specialization * forces NewRunnableMethod() to use STAUniquePtr instead of RefPtr for managing * a runnable's lifetime. Once the runnable has completed, the STAUniquePtr will * post a runnable to the main thread to release ourselves from there. */ template<> struct nsRunnableMethodReceiver { mozilla::mscom::STAUniquePtr mObj; explicit nsRunnableMethodReceiver(mozilla::a11y::LazyInstantiator* aObj) : mObj(aObj) { MOZ_ASSERT(NS_IsMainThread()); // STAUniquePtr does not implicitly AddRef(), so we must explicitly do so // here. aObj->AddRef(); } ~nsRunnableMethodReceiver() { Revoke(); } mozilla::a11y::LazyInstantiator* Get() const { return mObj.get(); } void Revoke() { mObj = nullptr; } }; namespace mozilla { namespace a11y { static const wchar_t kLazyInstantiatorProp[] = L"mozilla::a11y::LazyInstantiator"; /* static */ already_AddRefed LazyInstantiator::GetRootAccessible(HWND aHwnd) { // There must only be one LazyInstantiator per HWND. // To track this, we set the kLazyInstantiatorProp on the HWND with a pointer // to an existing instance. We only create a new LazyInstatiator if that prop // has not already been set. LazyInstantiator* existingInstantiator = reinterpret_cast(::GetProp(aHwnd, kLazyInstantiatorProp)); RefPtr result; if (existingInstantiator) { // Temporarily disable blind aggregation until we know that we have been // marshaled. See EnableBlindAggregation for more information. existingInstantiator->mAllowBlindAggregation = false; result = existingInstantiator; return result.forget(); } // At this time we only want to check whether the acc service is running; We // don't actually want to create the acc service yet. if (!GetAccService()) { // a11y is not running yet, there are no existing LazyInstantiators for this // HWND, so create a new one and return it as a surrogate for the root // accessible. result = new LazyInstantiator(aHwnd); return result.forget(); } // a11y is running, so we just resolve the real root accessible. a11y::Accessible* rootAcc = widget::WinUtils::GetRootAccessibleForHWND(aHwnd); if (!rootAcc) { return nullptr; } if (!rootAcc->IsRoot()) { // rootAcc might represent a popup as opposed to a true root accessible. // In that case we just use the regular Accessible::GetNativeInterface. rootAcc->GetNativeInterface(getter_AddRefs(result)); return result.forget(); } // Subtle: rootAcc might still be wrapped by a LazyInstantiator, but we // don't need LazyInstantiator's capabilities anymore (since a11y is already // running). We can bypass LazyInstantiator by retrieving the internal // unknown (which is not wrapped by the LazyInstantiator) and then querying // that for IID_IAccessible. a11y::RootAccessibleWrap* rootWrap = static_cast(rootAcc); RefPtr punk(rootWrap->GetInternalUnknown()); MOZ_ASSERT(punk); if (!punk) { return nullptr; } punk->QueryInterface(IID_IAccessible, getter_AddRefs(result)); return result.forget(); } /** * When marshaling an interface, COM makes a whole bunch of QueryInterface * calls to determine what kind of marshaling the interface supports. We need * to handle those queries without instantiating a11y, so we temporarily * disable passing through of QueryInterface calls to a11y. Once we know that * COM is finished marshaling, we call EnableBlindAggregation to re-enable * QueryInterface passthrough. */ /* static */ void LazyInstantiator::EnableBlindAggregation(HWND aHwnd) { LazyInstantiator* existingInstantiator = reinterpret_cast(::GetProp(aHwnd, kLazyInstantiatorProp)); if (!existingInstantiator) { return; } existingInstantiator->mAllowBlindAggregation = true; } LazyInstantiator::LazyInstantiator(HWND aHwnd) : mHwnd(aHwnd) , mAllowBlindAggregation(false) , mWeakRootAccWrap(nullptr) , mWeakAccessible(nullptr) , mWeakDispatch(nullptr) { MOZ_ASSERT(aHwnd); // Assign ourselves as the designated LazyInstantiator for aHwnd DebugOnly setPropOk = ::SetProp(aHwnd, kLazyInstantiatorProp, reinterpret_cast(this)); MOZ_ASSERT(setPropOk); } LazyInstantiator::~LazyInstantiator() { if (mRealRootUnk) { // Disconnect ourselves from the root accessible. RefPtr dummy(mWeakRootAccWrap->Aggregate(nullptr)); } ClearProp(); } void LazyInstantiator::ClearProp() { // Remove ourselves as the designated LazyInstantiator for mHwnd DebugOnly removedProp = ::RemoveProp(mHwnd, kLazyInstantiatorProp); MOZ_ASSERT(!removedProp || reinterpret_cast(removedProp.value) == this); } /** * Given the remote client's thread ID, resolve its execuatable image name. */ bool LazyInstantiator::GetClientExecutableName(const DWORD aClientTid, nsIFile** aOutClientExe) { nsAutoHandle callingThread(::OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, aClientTid)); if (!callingThread) { return false; } DWORD callingPid = ::GetProcessIdOfThread(callingThread); nsAutoHandle callingProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, callingPid)); if (!callingProcess) { return false; } DWORD bufLen = MAX_PATH; UniquePtr buf; while (true) { buf = MakeUnique(bufLen); if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) { break; } DWORD lastError = ::GetLastError(); MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER); if (lastError != ERROR_INSUFFICIENT_BUFFER) { return false; } bufLen *= 2; } nsCOMPtr file; nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false, getter_AddRefs(file)); if (NS_FAILED(rv)) { return false; } file.forget(aOutClientExe); return NS_SUCCEEDED(rv); } /** * Given a remote client's thread ID, determine whether we should proceed with * a11y instantiation. This is where telemetry should be gathered and any * potential blocking of unwanted a11y clients should occur. * * @return true if we should instantiate a11y */ bool LazyInstantiator::ShouldInstantiate(const DWORD aClientTid) { if (!aClientTid) { // aClientTid == 0 implies that this is either an in-process call, or else // we failed to retrieve information about the remote caller. // We should always default to instantiating a11y in this case. return true; } nsCOMPtr clientExe; if (!GetClientExecutableName(aClientTid, getter_AddRefs(clientExe))) { #if defined(MOZ_TELEMETRY_REPORTING) AccumulateTelemetry(NS_LITERAL_STRING("(Failed to retrieve client image name)")); #endif // defined(MOZ_TELEMETRY_REPORTING) // We should return true as a failsafe return true; } // Blocklist checks should go here. return false if we should not instantiate. /* if (ClientShouldBeBlocked(clientExe)) { return false; } */ // Store full path to executable for support purposes. nsAutoString filePath; nsresult rv = clientExe->GetPath(filePath); if (NS_SUCCEEDED(rv)) { a11y::SetInstantiator(filePath); } #if defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) if (!mTelemetryThread) { // Call GatherTelemetry on a background thread because it does I/O on // the executable file to retrieve version information. nsCOMPtr runnable( NewRunnableMethod, RefPtr>( "LazyInstantiator::GatherTelemetry", this, &LazyInstantiator::GatherTelemetry, clientExe, new AccumulateRunnable(this))); NS_NewThread(getter_AddRefs(mTelemetryThread), runnable); } #endif // defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) return true; } #if defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) /** * Appends version information in the format "|a.b.c.d". * If there is no version information, we append nothing. */ void LazyInstantiator::AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend) { MOZ_ASSERT(!NS_IsMainThread()); nsAutoString fullPath; nsresult rv = aClientExe->GetPath(fullPath); if (NS_FAILED(rv)) { return; } DWORD verInfoSize = ::GetFileVersionInfoSize(fullPath.get(), nullptr); if (!verInfoSize) { return; } auto verInfoBuf = MakeUnique(verInfoSize); if (!::GetFileVersionInfo(fullPath.get(), 0, verInfoSize, verInfoBuf.get())) { return; } VS_FIXEDFILEINFO* fixedInfo = nullptr; UINT fixedInfoLen = 0; if (!::VerQueryValue(verInfoBuf.get(), L"\\", (LPVOID*) &fixedInfo, &fixedInfoLen)) { return; } uint32_t major = HIWORD(fixedInfo->dwFileVersionMS); uint32_t minor = LOWORD(fixedInfo->dwFileVersionMS); uint32_t patch = HIWORD(fixedInfo->dwFileVersionLS); uint32_t build = LOWORD(fixedInfo->dwFileVersionLS); aStrToAppend.AppendLiteral(u"|"); NS_NAMED_LITERAL_STRING(dot, "."); aStrToAppend.AppendInt(major); aStrToAppend.Append(dot); aStrToAppend.AppendInt(minor); aStrToAppend.Append(dot); aStrToAppend.AppendInt(patch); aStrToAppend.Append(dot); aStrToAppend.AppendInt(build); } void LazyInstantiator::GatherTelemetry(nsIFile* aClientExe, AccumulateRunnable* aRunnable) { MOZ_ASSERT(!NS_IsMainThread()); nsAutoString value; nsresult rv = aClientExe->GetLeafName(value); if (NS_SUCCEEDED(rv)) { AppendVersionInfo(aClientExe, value); } aRunnable->SetData(value); // Now that we've (possibly) obtained version info, send the resulting // string back to the main thread to accumulate in telemetry. NS_DispatchToMainThread(aRunnable); } void LazyInstantiator::AccumulateTelemetry(const nsString& aValue) { MOZ_ASSERT(NS_IsMainThread()); if (!aValue.IsEmpty()) { #if defined(MOZ_TELEMETRY_REPORTING) Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS, aValue); #endif // defined(MOZ_TELEMETRY_REPORTING) #if defined(MOZ_CRASHREPORTER) CrashReporter:: AnnotateCrashReport(NS_LITERAL_CSTRING("AccessibilityClient"), NS_ConvertUTF16toUTF8(aValue)); #endif // defined(MOZ_CRASHREPORTER) } if (mTelemetryThread) { mTelemetryThread->Shutdown(); mTelemetryThread = nullptr; } } #endif // defined(MOZ_TELEMETRY_REPORTING) || defined(MOZ_CRASHREPORTER) RootAccessibleWrap* LazyInstantiator::ResolveRootAccWrap() { Accessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd); if (!acc || !acc->IsRoot()) { return nullptr; } return static_cast(acc); } /** * With COM aggregation, the aggregated inner object usually delegates its * reference counting to the outer object. In other words, we would expect * mRealRootUnk to delegate its AddRef() and Release() to this LazyInstantiator. * * This scheme will not work in our case because the RootAccessibleWrap is * cycle-collected! * * Instead, once a LazyInstantiator aggregates a RootAccessibleWrap, we transfer * our strong references into mRealRootUnk. Any future calls to AddRef or * Release now operate on mRealRootUnk instead of our intrinsic reference * count. This is a bit strange, but it is the only way for these objects to * share their reference count in a way that is safe for cycle collection. * * How do we know when it is safe to destroy ourselves? In * LazyInstantiator::Release, we examine the result of mRealRootUnk->Release(). * If mRealRootUnk's resulting refcount is 1, then we know that the only * remaining reference to mRealRootUnk is the mRealRootUnk reference itself (and * thus nobody else holds references to either this or mRealRootUnk). Therefore * we may now delete ourselves. */ void LazyInstantiator::TransplantRefCnt() { MOZ_ASSERT(mRefCnt > 0); MOZ_ASSERT(mRealRootUnk); while (mRefCnt > 0) { mRealRootUnk.get()->AddRef(); --mRefCnt; } } HRESULT LazyInstantiator::MaybeResolveRoot() { MOZ_ASSERT(NS_IsMainThread()); if (mWeakAccessible) { return S_OK; } if (GetAccService() || ShouldInstantiate(mscom::MainThreadRuntime::GetClientThreadId())) { mWeakRootAccWrap = ResolveRootAccWrap(); if (!mWeakRootAccWrap) { return E_POINTER; } // Wrap ourselves around the root accessible wrap mRealRootUnk = mWeakRootAccWrap->Aggregate(static_cast(this)); if (!mRealRootUnk) { return E_FAIL; } // Move our strong references into the root accessible (see the comments // above TransplantRefCnt for explanation). TransplantRefCnt(); // Now obtain mWeakAccessible which we use to forward our incoming calls // to the real accesssible. HRESULT hr = mRealRootUnk->QueryInterface(IID_IAccessible, (void**) &mWeakAccessible); if (FAILED(hr)) { return hr; } // mWeakAccessible is weak, so don't hold a strong ref mWeakAccessible->Release(); // Now that a11y is running, we don't need to remain registered with our // HWND anymore. ClearProp(); return S_OK; } // If we don't want a real root, let's resolve a fake one. const WPARAM flags = 0xFFFFFFFFUL; // Synthesize a WM_GETOBJECT request to obtain a system-implemented // IAccessible object from DefWindowProc LRESULT lresult = ::DefWindowProc(mHwnd, WM_GETOBJECT, flags, static_cast(OBJID_CLIENT)); HRESULT hr = ObjectFromLresult(lresult, IID_IAccessible, flags, getter_AddRefs(mRealRootUnk)); if (FAILED(hr)) { return hr; } if (!mRealRootUnk) { return E_NOTIMPL; } hr = mRealRootUnk->QueryInterface(IID_IAccessible, (void**) &mWeakAccessible); if (FAILED(hr)) { return hr; } // mWeakAccessible is weak, so don't hold a strong ref mWeakAccessible->Release(); return S_OK; } #define RESOLVE_ROOT \ { \ HRESULT hr = MaybeResolveRoot(); \ if (FAILED(hr)) { \ return hr; \ } \ } IMPL_IUNKNOWN_QUERY_HEAD(LazyInstantiator) IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible) IMPL_IUNKNOWN_QUERY_IFACE(IAccessible) IMPL_IUNKNOWN_QUERY_IFACE(IDispatch) IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider) // See EnableBlindAggregation for comments. if (!mAllowBlindAggregation) { return E_NOINTERFACE; } // If the client queries for an interface that LazyInstantiator does not // intrinsically support, then we must resolve the root accessible and pass // on the QueryInterface call to mRealRootUnk. RESOLVE_ROOT IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mRealRootUnk) ULONG LazyInstantiator::AddRef() { // Always delegate refcounting to mRealRootUnk when it exists if (mRealRootUnk) { return mRealRootUnk.get()->AddRef(); } return ++mRefCnt; } ULONG LazyInstantiator::Release() { ULONG result; // Always delegate refcounting to mRealRootUnk when it exists if (mRealRootUnk) { result = mRealRootUnk.get()->Release(); if (result == 1) { // mRealRootUnk is the only strong reference left, so nothing else holds // a strong reference to us. Drop result to zero so that we destroy // ourselves (See the comments above LazyInstantiator::TransplantRefCnt // for more info). --result; } } else { result = --mRefCnt; } if (!result) { delete this; } return result; } /** * Create a standard IDispatch implementation. mStdDispatch will translate any * IDispatch::Invoke calls into real IAccessible calls. */ HRESULT LazyInstantiator::ResolveDispatch() { if (mWeakDispatch) { return S_OK; } // The IAccessible typelib is embedded in oleacc.dll's resources. auto typelib = mscom::RegisterTypelib(L"oleacc.dll", mscom::RegistrationFlags::eUseSystemDirectory); if (!typelib) { return E_UNEXPECTED; } // Extract IAccessible's type info RefPtr accTypeInfo; HRESULT hr = typelib->GetTypeInfoForGuid(IID_IAccessible, getter_AddRefs(accTypeInfo)); if (FAILED(hr)) { return hr; } // Now create the standard IDispatch for IAccessible hr = ::CreateStdDispatch(static_cast(this), static_cast(this), accTypeInfo, getter_AddRefs(mStdDispatch)); if (FAILED(hr)) { return hr; } hr = mStdDispatch->QueryInterface(IID_IDispatch, (void**)&mWeakDispatch); if (FAILED(hr)) { return hr; } // WEAK reference mWeakDispatch->Release(); return S_OK; } #define RESOLVE_IDISPATCH \ { \ HRESULT hr = ResolveDispatch(); \ if (FAILED(hr)) { \ return hr; \ } \ } /** * The remaining methods implement IDispatch, IAccessible, and IServiceProvider, * lazily resolving the real a11y objects and passing the call through. */ HRESULT LazyInstantiator::GetTypeInfoCount(UINT* pctinfo) { RESOLVE_IDISPATCH; return mWeakDispatch->GetTypeInfoCount(pctinfo); } HRESULT LazyInstantiator::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { RESOLVE_IDISPATCH; return mWeakDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo); } HRESULT LazyInstantiator::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { RESOLVE_IDISPATCH; return mWeakDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId); } HRESULT LazyInstantiator::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { RESOLVE_IDISPATCH; return mWeakDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); } HRESULT LazyInstantiator::get_accParent(IDispatch **ppdispParent) { RESOLVE_ROOT; return mWeakAccessible->get_accParent(ppdispParent); } HRESULT LazyInstantiator::get_accChildCount(long *pcountChildren) { if (!pcountChildren) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accChildCount(pcountChildren); } HRESULT LazyInstantiator::get_accChild(VARIANT varChild, IDispatch **ppdispChild) { if (!ppdispChild) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accChild(varChild, ppdispChild); } HRESULT LazyInstantiator::get_accName(VARIANT varChild, BSTR *pszName) { if (!pszName) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accName(varChild, pszName); } HRESULT LazyInstantiator::get_accValue(VARIANT varChild, BSTR *pszValue) { if (!pszValue) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accValue(varChild, pszValue); } HRESULT LazyInstantiator::get_accDescription(VARIANT varChild, BSTR *pszDescription) { if (!pszDescription) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accDescription(varChild, pszDescription); } HRESULT LazyInstantiator::get_accRole(VARIANT varChild, VARIANT *pvarRole) { if (!pvarRole) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accRole(varChild, pvarRole); } HRESULT LazyInstantiator::get_accState(VARIANT varChild, VARIANT *pvarState) { if (!pvarState) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accState(varChild, pvarState); } HRESULT LazyInstantiator::get_accHelp(VARIANT varChild, BSTR *pszHelp) { return E_NOTIMPL; } HRESULT LazyInstantiator::get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic) { return E_NOTIMPL; } HRESULT LazyInstantiator::get_accKeyboardShortcut(VARIANT varChild, BSTR *pszKeyboardShortcut) { if (!pszKeyboardShortcut) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accKeyboardShortcut(varChild, pszKeyboardShortcut); } HRESULT LazyInstantiator::get_accFocus(VARIANT *pvarChild) { if (!pvarChild) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accFocus(pvarChild); } HRESULT LazyInstantiator::get_accSelection(VARIANT *pvarChildren) { if (!pvarChildren) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accSelection(pvarChildren); } HRESULT LazyInstantiator::get_accDefaultAction(VARIANT varChild, BSTR *pszDefaultAction) { if (!pszDefaultAction) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->get_accDefaultAction(varChild, pszDefaultAction); } HRESULT LazyInstantiator::accSelect(long flagsSelect, VARIANT varChild) { RESOLVE_ROOT; return mWeakAccessible->accSelect(flagsSelect, varChild); } HRESULT LazyInstantiator::accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) { RESOLVE_ROOT; return mWeakAccessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild); } HRESULT LazyInstantiator::accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt) { if (!pvarEndUpAt) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->accNavigate(navDir, varStart, pvarEndUpAt); } HRESULT LazyInstantiator::accHitTest(long xLeft, long yTop, VARIANT *pvarChild) { if (!pvarChild) { return E_INVALIDARG; } RESOLVE_ROOT; return mWeakAccessible->accHitTest(xLeft, yTop, pvarChild); } HRESULT LazyInstantiator::accDoDefaultAction(VARIANT varChild) { RESOLVE_ROOT; return mWeakAccessible->accDoDefaultAction(varChild); } HRESULT LazyInstantiator::put_accName(VARIANT varChild, BSTR szName) { return E_NOTIMPL; } HRESULT LazyInstantiator::put_accValue(VARIANT varChild, BSTR szValue) { return E_NOTIMPL; } HRESULT LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid, void** aOutInterface) { if (!aOutInterface) { return E_INVALIDARG; } *aOutInterface = nullptr; RESOLVE_ROOT; RefPtr servProv; HRESULT hr = mRealRootUnk->QueryInterface(IID_IServiceProvider, getter_AddRefs(servProv)); if (FAILED(hr)) { return hr; } return servProv->QueryService(aServiceId, aServiceIid, aOutInterface); } } // namespace a11y } // namespace mozilla