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