1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "MsaaDocAccessible.h"
8 
9 #include "DocAccessibleChild.h"
10 #include "mozilla/StaticPrefs_accessibility.h"
11 #include "nsWinUtils.h"
12 #include "Role.h"
13 
14 using namespace mozilla;
15 using namespace mozilla::a11y;
16 
DocAcc()17 DocAccessible* MsaaDocAccessible::DocAcc() {
18   return static_cast<DocAccessible*>(LocalAcc());
19 }
20 
21 /* static */
GetFrom(DocAccessible * aDoc)22 MsaaDocAccessible* MsaaDocAccessible::GetFrom(DocAccessible* aDoc) {
23   return static_cast<MsaaDocAccessible*>(MsaaAccessible::GetFrom(aDoc));
24 }
25 
26 /* static */
GetFrom(DocAccessibleParent * aDoc)27 MsaaDocAccessible* MsaaDocAccessible::GetFrom(DocAccessibleParent* aDoc) {
28   MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup());
29   return static_cast<MsaaDocAccessible*>(
30       reinterpret_cast<MsaaAccessible*>(aDoc->GetWrapper()));
31 }
32 
33 /* static */
GetFromOwned(Accessible * aAcc)34 MsaaDocAccessible* MsaaDocAccessible::GetFromOwned(Accessible* aAcc) {
35   if (RemoteAccessible* remoteAcc = aAcc->AsRemote()) {
36     DocAccessibleParent* doc = remoteAcc->Document();
37     if (!doc) {
38       return nullptr;
39     }
40     return MsaaDocAccessible::GetFrom(doc);
41   }
42   DocAccessible* doc = aAcc->AsLocal()->Document();
43   if (!doc) {
44     return nullptr;
45   }
46   return MsaaDocAccessible::GetFrom(doc);
47 }
48 
49 // IUnknown
50 IMPL_IUNKNOWN_QUERY_HEAD(MsaaDocAccessible)
51 if (aIID == IID_ISimpleDOMDocument && LocalAcc()) {
52   statistics::ISimpleDOMUsed();
53   *aInstancePtr = static_cast<ISimpleDOMDocument*>(new sdnDocAccessible(this));
54   static_cast<IUnknown*>(*aInstancePtr)->AddRef();
55   return S_OK;
56 }
IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext)57 IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(ia2AccessibleHypertext)
58 
59 STDMETHODIMP
60 MsaaDocAccessible::get_accParent(
61     /* [retval][out] */ IDispatch __RPC_FAR* __RPC_FAR* ppdispParent) {
62   if (!mAcc) {
63     return CO_E_OBJNOTCONNECTED;
64   }
65 
66   if (mAcc->IsRemote()) {
67     MOZ_ASSERT(StaticPrefs::accessibility_cache_enabled_AtStartup());
68     DocAccessibleParent* remoteDoc = mAcc->AsRemote()->AsDoc();
69     if (nsWinUtils::IsWindowEmulationStarted() && remoteDoc->IsTopLevel()) {
70       // Window emulation is enabled and this is a top level document. Return
71       // window system accessible object.
72       HWND hwnd = remoteDoc->GetEmulatedWindowHandle();
73       MOZ_ASSERT(hwnd);
74       if (hwnd &&
75           SUCCEEDED(::AccessibleObjectFromWindow(
76               hwnd, OBJID_WINDOW, IID_IAccessible, (void**)ppdispParent))) {
77         return S_OK;
78       }
79     }
80     return MsaaAccessible::get_accParent(ppdispParent);
81   }
82 
83   DocAccessible* docAcc = DocAcc();
84   MOZ_ASSERT(docAcc);
85 
86   // We might be a top-level document in a content process.
87   DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
88   if (ipcDoc && static_cast<dom::BrowserChild*>(ipcDoc->Manager())
89                         ->GetTopLevelDocAccessibleChild() == ipcDoc) {
90     MOZ_ASSERT(!StaticPrefs::accessibility_cache_enabled_AtStartup());
91     // Emulated window proxy is only set for the top level content document when
92     // emulation is enabled.
93     RefPtr<IDispatch> dispParent = ipcDoc->GetEmulatedWindowIAccessible();
94     if (!dispParent) {
95       dispParent = ipcDoc->GetParentIAccessible();
96     }
97 
98     if (!dispParent) {
99       return S_FALSE;
100     }
101 
102     dispParent.forget(ppdispParent);
103     return S_OK;
104   }
105 
106   // In the parent process, return window system accessible object for root
107   // document accessibles, as well as tab document accessibles if window
108   // emulation is enabled.
109   if (XRE_IsParentProcess() &&
110       (!docAcc->ParentDocument() ||
111        (nsWinUtils::IsWindowEmulationStarted() &&
112         nsCoreUtils::IsTopLevelContentDocInProcess(docAcc->DocumentNode())))) {
113     HWND hwnd = static_cast<HWND>(docAcc->GetNativeWindow());
114     if (hwnd && !docAcc->ParentDocument()) {
115       nsIFrame* frame = docAcc->GetFrame();
116       if (frame) {
117         nsIWidget* widget = frame->GetNearestWidget();
118         if (widget->WindowType() == eWindowType_child && !widget->GetParent()) {
119           // Bug 1427304: Windows opened with popup=yes (such as the WebRTC
120           // sharing indicator) get two HWNDs. The root widget is associated
121           // with the inner HWND, but the outer HWND still answers to
122           // WM_GETOBJECT queries. This means that getting the parent of the
123           // oleacc window accessible for the inner HWND returns this
124           // root accessible. Thus, to avoid a loop, we must never return the
125           // oleacc window accessible for the inner HWND. Instead, we use the
126           // outer HWND here.
127           HWND parentHwnd = ::GetParent(hwnd);
128           if (parentHwnd) {
129             MOZ_ASSERT(::GetWindowLongW(parentHwnd, GWL_STYLE) & WS_POPUP,
130                        "Parent HWND should be a popup!");
131             hwnd = parentHwnd;
132           }
133         }
134       }
135     }
136     if (hwnd &&
137         SUCCEEDED(::AccessibleObjectFromWindow(
138             hwnd, OBJID_WINDOW, IID_IAccessible, (void**)ppdispParent))) {
139       return S_OK;
140     }
141   }
142 
143   return MsaaAccessible::get_accParent(ppdispParent);
144 }
145 
146 STDMETHODIMP
get_accValue(VARIANT aVarChild,BSTR __RPC_FAR * aValue)147 MsaaDocAccessible::get_accValue(VARIANT aVarChild, BSTR __RPC_FAR* aValue) {
148   if (!aValue) return E_INVALIDARG;
149   *aValue = nullptr;
150 
151   // For backwards-compat, we still support old MSAA hack to provide URL in
152   // accValue Check for real value first
153   HRESULT hr = MsaaAccessible::get_accValue(aVarChild, aValue);
154   if (FAILED(hr) || *aValue || aVarChild.lVal != CHILDID_SELF) return hr;
155 
156   // MsaaAccessible::get_accValue should have failed (and thus we should have
157   // returned early) if the Accessible is dead.
158   MOZ_ASSERT(mAcc);
159   if (mAcc->IsRemote()) {
160     return E_NOTIMPL;
161   }
162   DocAccessible* docAcc = DocAcc();
163   // This is not remote, so it must be a local DocAccessible.
164   MOZ_ASSERT(docAcc);
165   // If document is being used to create a widget, don't use the URL hack
166   roles::Role role = docAcc->Role();
167   if (role != roles::DOCUMENT && role != roles::APPLICATION &&
168       role != roles::DIALOG && role != roles::ALERT &&
169       role != roles::NON_NATIVE_DOCUMENT)
170     return hr;
171 
172   nsAutoString url;
173   docAcc->URL(url);
174   if (url.IsEmpty()) return S_FALSE;
175 
176   *aValue = ::SysAllocStringLen(url.get(), url.Length());
177   return *aValue ? S_OK : E_OUTOFMEMORY;
178 }
179