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 "AccessibleWrap.h"
8 
9 #include "mozilla/a11y/DocAccessibleParent.h"
10 #include "AccEvent.h"
11 #include "GeckoCustom.h"
12 #include "nsAccUtils.h"
13 #include "nsIAccessibleEvent.h"
14 #include "nsIWidget.h"
15 #include "nsWindowsHelpers.h"
16 #include "mozilla/a11y/RemoteAccessible.h"
17 #include "ProxyWrappers.h"
18 #include "ServiceProvider.h"
19 #include "sdnAccessible.h"
20 
21 #include "mozilla/mscom/AsyncInvoker.h"
22 
23 using namespace mozilla;
24 using namespace mozilla::a11y;
25 
26 /* For documentation of the accessibility architecture,
27  * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
28  */
29 
30 StaticAutoPtr<nsTArray<AccessibleWrap::HandlerControllerData>>
31     AccessibleWrap::sHandlerControllers;
32 
33 ////////////////////////////////////////////////////////////////////////////////
34 // AccessibleWrap
35 ////////////////////////////////////////////////////////////////////////////////
AccessibleWrap(nsIContent * aContent,DocAccessible * aDoc)36 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
37     : LocalAccessible(aContent, aDoc) {}
38 
NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap,LocalAccessible)39 NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, LocalAccessible)
40 
41 void AccessibleWrap::Shutdown() {
42   if (mMsaa) {
43     mMsaa->MsaaShutdown();
44     // Don't release mMsaa here because this will cause its id to be released
45     // immediately, which will result in immediate reuse, causing problems
46     // for clients. Instead, we release it in the destructor.
47   }
48   LocalAccessible::Shutdown();
49 }
50 
51 //-----------------------------------------------------
52 // IUnknown interface methods - see iunknown.h for documentation
53 //-----------------------------------------------------
54 
GetMsaa()55 MsaaAccessible* AccessibleWrap::GetMsaa() {
56   if (!mMsaa) {
57     mMsaa = MsaaAccessible::Create(this);
58   }
59   return mMsaa;
60 }
61 
GetNativeInterface(void ** aOutAccessible)62 void AccessibleWrap::GetNativeInterface(void** aOutAccessible) {
63   RefPtr<IAccessible> result = GetMsaa();
64   return result.forget(aOutAccessible);
65 }
66 
67 ////////////////////////////////////////////////////////////////////////////////
68 // LocalAccessible
69 
HandleAccEvent(AccEvent * aEvent)70 nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
71   nsresult rv = LocalAccessible::HandleAccEvent(aEvent);
72   NS_ENSURE_SUCCESS(rv, rv);
73 
74   if (IPCAccessibilityActive()) {
75     return NS_OK;
76   }
77 
78   uint32_t eventType = aEvent->GetEventType();
79 
80   // Means we're not active.
81   NS_ENSURE_TRUE(!IsDefunct(), NS_ERROR_FAILURE);
82 
83   LocalAccessible* accessible = aEvent->GetAccessible();
84   if (!accessible) return NS_OK;
85 
86   if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED ||
87       eventType == nsIAccessibleEvent::EVENT_FOCUS) {
88     UpdateSystemCaretFor(accessible);
89   }
90 
91   MsaaAccessible::FireWinEvent(accessible, eventType);
92 
93   return NS_OK;
94 }
95 
96 ////////////////////////////////////////////////////////////////////////////////
97 // AccessibleWrap
98 
99 //------- Helper methods ---------
100 
IsRootForHWND()101 bool AccessibleWrap::IsRootForHWND() {
102   if (IsRoot()) {
103     return true;
104   }
105   HWND thisHwnd = MsaaAccessible::GetHWNDFor(this);
106   AccessibleWrap* parent = static_cast<AccessibleWrap*>(LocalParent());
107   MOZ_ASSERT(parent);
108   HWND parentHwnd = MsaaAccessible::GetHWNDFor(parent);
109   return thisHwnd != parentHwnd;
110 }
111 
UpdateSystemCaretFor(LocalAccessible * aAccessible)112 void AccessibleWrap::UpdateSystemCaretFor(LocalAccessible* aAccessible) {
113   // Move the system caret so that Windows Tablet Edition and tradional ATs with
114   // off-screen model can follow the caret
115   ::DestroyCaret();
116 
117   HyperTextAccessible* text = aAccessible->AsHyperText();
118   if (!text) return;
119 
120   nsIWidget* widget = nullptr;
121   LayoutDeviceIntRect caretRect = text->GetCaretRect(&widget);
122 
123   if (!widget) {
124     return;
125   }
126 
127   HWND caretWnd =
128       reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
129   UpdateSystemCaretFor(caretWnd, caretRect);
130 }
131 
132 /* static */
UpdateSystemCaretFor(RemoteAccessible * aProxy,const LayoutDeviceIntRect & aCaretRect)133 void AccessibleWrap::UpdateSystemCaretFor(
134     RemoteAccessible* aProxy, const LayoutDeviceIntRect& aCaretRect) {
135   ::DestroyCaret();
136 
137   // The HWND should be the real widget HWND, not an emulated HWND.
138   // We get the HWND from the proxy's outer doc to bypass window emulation.
139   LocalAccessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
140   UpdateSystemCaretFor(MsaaAccessible::GetHWNDFor(outerDoc), aCaretRect);
141 }
142 
143 /* static */
UpdateSystemCaretFor(HWND aCaretWnd,const LayoutDeviceIntRect & aCaretRect)144 void AccessibleWrap::UpdateSystemCaretFor(
145     HWND aCaretWnd, const LayoutDeviceIntRect& aCaretRect) {
146   if (!aCaretWnd || aCaretRect.IsEmpty()) {
147     return;
148   }
149 
150   // Create invisible bitmap for caret, otherwise its appearance interferes
151   // with Gecko caret
152   nsAutoBitmap caretBitMap(CreateBitmap(1, aCaretRect.Height(), 1, 1, nullptr));
153   if (::CreateCaret(aCaretWnd, caretBitMap, 1,
154                     aCaretRect.Height())) {  // Also destroys the last caret
155     ::ShowCaret(aCaretWnd);
156     RECT windowRect;
157     ::GetWindowRect(aCaretWnd, &windowRect);
158     ::SetCaretPos(aCaretRect.X() - windowRect.left,
159                   aCaretRect.Y() - windowRect.top);
160   }
161 }
162 
163 /* static */
SetHandlerControl(DWORD aPid,RefPtr<IHandlerControl> aCtrl)164 void AccessibleWrap::SetHandlerControl(DWORD aPid,
165                                        RefPtr<IHandlerControl> aCtrl) {
166   MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
167 
168   if (!sHandlerControllers) {
169     sHandlerControllers = new nsTArray<HandlerControllerData>();
170     ClearOnShutdown(&sHandlerControllers);
171   }
172 
173   HandlerControllerData ctrlData(aPid, std::move(aCtrl));
174   if (sHandlerControllers->Contains(ctrlData)) {
175     return;
176   }
177 
178   sHandlerControllers->AppendElement(std::move(ctrlData));
179 }
180 
181 /* static */
InvalidateHandlers()182 void AccessibleWrap::InvalidateHandlers() {
183   static const HRESULT kErrorServerDied =
184       HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE);
185 
186   MOZ_ASSERT(XRE_IsParentProcess());
187   MOZ_ASSERT(NS_IsMainThread());
188 
189   if (!sHandlerControllers || sHandlerControllers->IsEmpty()) {
190     return;
191   }
192 
193   // We iterate in reverse so that we may safely remove defunct elements while
194   // executing the loop.
195   for (auto& controller : Reversed(*sHandlerControllers)) {
196     MOZ_ASSERT(controller.mPid);
197     MOZ_ASSERT(controller.mCtrl);
198 
199     ASYNC_INVOKER_FOR(IHandlerControl)
200     invoker(controller.mCtrl, Some(controller.mIsProxy));
201 
202     HRESULT hr = ASYNC_INVOKE(invoker, Invalidate);
203 
204     if (hr == CO_E_OBJNOTCONNECTED || hr == kErrorServerDied) {
205       sHandlerControllers->RemoveElement(controller);
206     } else {
207       Unused << NS_WARN_IF(FAILED(hr));
208     }
209   }
210 }
211 
DispatchTextChangeToHandler(bool aIsInsert,const nsString & aText,int32_t aStart,uint32_t aLen)212 bool AccessibleWrap::DispatchTextChangeToHandler(bool aIsInsert,
213                                                  const nsString& aText,
214                                                  int32_t aStart,
215                                                  uint32_t aLen) {
216   MOZ_ASSERT(XRE_IsParentProcess());
217   MOZ_ASSERT(NS_IsMainThread());
218 
219   if (!sHandlerControllers || sHandlerControllers->IsEmpty()) {
220     return false;
221   }
222 
223   HWND hwnd = MsaaAccessible::GetHWNDFor(this);
224   MOZ_ASSERT(hwnd);
225   if (!hwnd) {
226     return false;
227   }
228 
229   long msaaId = MsaaAccessible::GetChildIDFor(this);
230 
231   DWORD ourPid = ::GetCurrentProcessId();
232 
233   // The handler ends up calling NotifyWinEvent, which should only be done once
234   // since it broadcasts the same event to every process who is subscribed.
235   // OTOH, if our chrome process contains a handler, we should prefer to
236   // broadcast the event from that process, as we want any DLLs injected by ATs
237   // to receive the event synchronously. Otherwise we simply choose the first
238   // handler in the list, for the lack of a better heuristic.
239 
240   nsTArray<HandlerControllerData>::index_type ctrlIndex =
241       sHandlerControllers->IndexOf(ourPid);
242 
243   if (ctrlIndex == nsTArray<HandlerControllerData>::NoIndex) {
244     ctrlIndex = 0;
245   }
246 
247   HandlerControllerData& controller = sHandlerControllers->ElementAt(ctrlIndex);
248   MOZ_ASSERT(controller.mPid);
249   MOZ_ASSERT(controller.mCtrl);
250 
251   VARIANT_BOOL isInsert = aIsInsert ? VARIANT_TRUE : VARIANT_FALSE;
252 
253   IA2TextSegment textSegment{::SysAllocStringLen(aText.get(), aText.Length()),
254                              aStart, aStart + static_cast<long>(aLen)};
255 
256   ASYNC_INVOKER_FOR(IHandlerControl)
257   invoker(controller.mCtrl, Some(controller.mIsProxy));
258 
259   HRESULT hr = ASYNC_INVOKE(invoker, OnTextChange, PtrToLong(hwnd), msaaId,
260                             isInsert, &textSegment);
261 
262   ::SysFreeString(textSegment.text);
263 
264   return SUCCEEDED(hr);
265 }
266