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/. */
12 #include "Compatibility.h"
13 #include "HyperTextAccessibleWrap.h"
14 #include "nsIWindowsRegKey.h"
15 #include "nsWinUtils.h"
16 #include "mozilla/a11y/DocAccessibleParent.h"
17 #include "mozilla/a11y/RemoteAccessible.h"
18 #include "mozilla/mscom/ActivationContext.h"
19 #include "mozilla/mscom/InterceptorLog.h"
20 #include "mozilla/mscom/Registration.h"
21 #include "mozilla/mscom/Utils.h"
22 #include "mozilla/StaticPrefs_accessibility.h"
23 #include "mozilla/StaticPtr.h"
24 #include "mozilla/WindowsVersion.h"
25 #include "mozilla/WinHeaderOnlyUtils.h"
26 #include "nsAccessibilityService.h"
27 #include "nsComponentManagerUtils.h"
28 #include "nsDirectoryServiceDefs.h"
29 #include "nsDirectoryServiceUtils.h"
34 #if defined(MOZ_TELEMETRY_REPORTING)
35 # include "mozilla/Telemetry.h"
36 #endif // defined(MOZ_TELEMETRY_REPORTING)
38 using namespace mozilla
;
39 using namespace mozilla::a11y
;
40 using namespace mozilla::mscom
;
42 static StaticAutoPtr
<RegisteredProxy
> gRegCustomProxy
;
43 static StaticAutoPtr
<RegisteredProxy
> gRegProxy
;
44 static StaticAutoPtr
<RegisteredProxy
> gRegAccTlb
;
45 static StaticAutoPtr
<RegisteredProxy
> gRegMiscTlb
;
46 static StaticRefPtr
<nsIFile
> gInstantiator
;
48 static bool RegisterHandlerMsix() {
49 // If we're running in an MSIX container, the handler isn't registered in
50 // HKLM. We could do that via registry.dat, but that file is difficult to
51 // manage. Instead, we register it in HKCU if it isn't already. This also
52 // covers the case where we registered in HKCU in a previous run, but the
53 // MSIX was updated and the dll now has a different path. In that case,
54 // IsHandlerRegistered will return false because the paths are different,
55 // RegisterHandlerMsix will get called and it will correct the HKCU entry. We
56 // don't need to unregister because we'll always need this and Windows cleans
57 // up the registry data for an MSIX app when it is uninstalled.
58 nsCOMPtr
<nsIFile
> handlerPath
;
59 nsresult rv
= NS_GetSpecialDirectory(NS_GRE_DIR
, getter_AddRefs(handlerPath
));
64 rv
= handlerPath
->Append(u
"AccessibleHandler.dll"_ns
);
69 rv
= handlerPath
->GetPath(path
);
74 nsModuleHandle
handlerDll(LoadLibrary(path
.get()));
75 if (!handlerDll
.get()) {
78 auto RegisterMsix
= reinterpret_cast<decltype(&DllRegisterServer
)>(
79 GetProcAddress(handlerDll
, "RegisterMsix"));
83 if (FAILED(RegisterMsix())) {
89 void a11y::PlatformInit() {
90 nsWinUtils::MaybeStartWindowEmulation();
91 ia2AccessibleText::InitTextChangeData();
93 mscom::InterceptorLog::Init();
94 UniquePtr
<RegisteredProxy
> regCustomProxy(mscom::RegisterProxy());
95 gRegCustomProxy
= regCustomProxy
.release();
96 UniquePtr
<RegisteredProxy
> regProxy(mscom::RegisterProxy(L
"ia2marshal.dll"));
97 gRegProxy
= regProxy
.release();
98 UniquePtr
<RegisteredProxy
> regAccTlb(mscom::RegisterTypelib(
99 L
"oleacc.dll", RegistrationFlags::eUseSystemDirectory
));
100 gRegAccTlb
= regAccTlb
.release();
101 UniquePtr
<RegisteredProxy
> regMiscTlb(
102 mscom::RegisterTypelib(L
"Accessible.tlb"));
103 gRegMiscTlb
= regMiscTlb
.release();
105 if (XRE_IsParentProcess() && widget::WinUtils::HasPackageIdentity() &&
106 !IsHandlerRegistered()) {
107 // See the comments at the top of RegisterHandlerMsix regarding why we do
109 RegisterHandlerMsix();
113 void a11y::PlatformShutdown() {
116 nsWinUtils::ShutdownWindowEmulation();
117 gRegCustomProxy
= nullptr;
119 gRegAccTlb
= nullptr;
120 gRegMiscTlb
= nullptr;
123 gInstantiator
= nullptr;
127 void a11y::ProxyCreated(RemoteAccessible
* aProxy
) {
128 MsaaAccessible
* msaa
= MsaaAccessible::Create(aProxy
);
130 aProxy
->SetWrapper(reinterpret_cast<uintptr_t>(msaa
));
133 void a11y::ProxyDestroyed(RemoteAccessible
* aProxy
) {
134 MsaaAccessible
* msaa
=
135 reinterpret_cast<MsaaAccessible
*>(aProxy
->GetWrapper());
139 msaa
->MsaaShutdown();
140 aProxy
->SetWrapper(0);
143 if (aProxy
->IsDoc() && nsWinUtils::IsWindowEmulationStarted()) {
144 aProxy
->AsDoc()->SetEmulatedWindowHandle(nullptr);
148 void a11y::ProxyEvent(RemoteAccessible
* aTarget
, uint32_t aEventType
) {
149 MsaaAccessible::FireWinEvent(aTarget
, aEventType
);
152 void a11y::ProxyStateChangeEvent(RemoteAccessible
* aTarget
, uint64_t, bool) {
153 MsaaAccessible::FireWinEvent(aTarget
, nsIAccessibleEvent::EVENT_STATE_CHANGE
);
156 void a11y::ProxyFocusEvent(RemoteAccessible
* aTarget
,
157 const LayoutDeviceIntRect
& aCaretRect
) {
158 FocusManager
* focusMgr
= FocusMgr();
159 if (focusMgr
&& focusMgr
->FocusedAccessible()) {
160 // This is a focus event from a remote document, but focus has moved out
161 // of that document into the chrome since that event was sent. For example,
162 // this can happen when choosing File menu -> New Tab. See bug 1471466.
163 // Note that this does not handle the case where a focus event is sent from
164 // one remote document, but focus moved into a second remote document
165 // since that event was sent. However, this isn't something anyone has been
170 AccessibleWrap::UpdateSystemCaretFor(aTarget
, aCaretRect
);
171 MsaaAccessible::FireWinEvent(aTarget
, nsIAccessibleEvent::EVENT_FOCUS
);
174 void a11y::ProxyCaretMoveEvent(RemoteAccessible
* aTarget
,
175 const LayoutDeviceIntRect
& aCaretRect
,
176 int32_t aGranularity
) {
177 AccessibleWrap::UpdateSystemCaretFor(aTarget
, aCaretRect
);
178 MsaaAccessible::FireWinEvent(aTarget
,
179 nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
);
182 void a11y::ProxyTextChangeEvent(RemoteAccessible
* aText
, const nsString
& aStr
,
183 int32_t aStart
, uint32_t aLen
, bool aInsert
,
185 uint32_t eventType
= aInsert
? nsIAccessibleEvent::EVENT_TEXT_INSERTED
186 : nsIAccessibleEvent::EVENT_TEXT_REMOVED
;
187 static const bool useHandler
=
188 !StaticPrefs::accessibility_cache_enabled_AtStartup() &&
189 Preferences::GetBool("accessibility.handler.enabled", false) &&
190 IsHandlerRegistered();
192 AccessibleWrap::DispatchTextChangeToHandler(aText
, aInsert
, aStr
, aStart
,
197 // XXX Call ia2AccessibleText::UpdateTextChangeData once that works for
199 MsaaAccessible::FireWinEvent(aText
, eventType
);
202 void a11y::ProxyShowHideEvent(RemoteAccessible
* aTarget
, RemoteAccessible
*,
203 bool aInsert
, bool) {
205 aInsert
? nsIAccessibleEvent::EVENT_SHOW
: nsIAccessibleEvent::EVENT_HIDE
;
206 MsaaAccessible::FireWinEvent(aTarget
, event
);
209 void a11y::ProxySelectionEvent(RemoteAccessible
* aTarget
, RemoteAccessible
*,
211 MsaaAccessible::FireWinEvent(aTarget
, aType
);
214 bool a11y::IsHandlerRegistered() {
216 nsCOMPtr
<nsIWindowsRegKey
> regKey
=
217 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv
);
223 GUIDToString(CLSID_AccessibleHandler
, clsid
);
226 subKey
.AppendLiteral(u
"SOFTWARE\\Classes\\CLSID\\");
227 subKey
.Append(clsid
);
228 subKey
.AppendLiteral(u
"\\InprocHandler32");
230 // If we're runnig in an MSIX container, we register this in HKCU, so look
232 const auto rootKey
= widget::WinUtils::HasPackageIdentity()
233 ? nsIWindowsRegKey::ROOT_KEY_CURRENT_USER
234 : nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE
;
235 rv
= regKey
->Open(rootKey
, subKey
, nsIWindowsRegKey::ACCESS_READ
);
240 nsAutoString handlerPath
;
241 rv
= regKey
->ReadStringValue(nsAutoString(), handlerPath
);
246 nsCOMPtr
<nsIFile
> actualHandler
;
247 rv
= NS_NewLocalFile(handlerPath
, false, getter_AddRefs(actualHandler
));
252 nsCOMPtr
<nsIFile
> expectedHandler
;
253 rv
= NS_GetSpecialDirectory(NS_GRE_DIR
, getter_AddRefs(expectedHandler
));
258 rv
= expectedHandler
->Append(u
"AccessibleHandler.dll"_ns
);
264 rv
= expectedHandler
->Equals(actualHandler
, &equal
);
265 return NS_SUCCEEDED(rv
) && equal
;
268 static bool GetInstantiatorExecutable(const DWORD aPid
,
269 nsIFile
** aOutClientExe
) {
270 nsAutoHandle
callingProcess(
271 ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION
, FALSE
, aPid
));
272 if (!callingProcess
) {
276 DWORD bufLen
= MAX_PATH
;
277 UniquePtr
<wchar_t[]> buf
;
280 buf
= MakeUnique
<wchar_t[]>(bufLen
);
281 if (::QueryFullProcessImageName(callingProcess
, 0, buf
.get(), &bufLen
)) {
285 DWORD lastError
= ::GetLastError();
286 MOZ_ASSERT(lastError
== ERROR_INSUFFICIENT_BUFFER
);
287 if (lastError
!= ERROR_INSUFFICIENT_BUFFER
) {
294 nsCOMPtr
<nsIFile
> file
;
295 nsresult rv
= NS_NewLocalFile(nsDependentString(buf
.get(), bufLen
), false,
296 getter_AddRefs(file
));
301 file
.forget(aOutClientExe
);
302 return NS_SUCCEEDED(rv
);
306 * Appends version information in the format "|a.b.c.d".
307 * If there is no version information, we append nothing.
309 static void AppendVersionInfo(nsIFile
* aClientExe
, nsAString
& aStrToAppend
) {
310 MOZ_ASSERT(!NS_IsMainThread());
312 LauncherResult
<ModuleVersion
> version
= GetModuleVersion(aClientExe
);
313 if (version
.isErr()) {
317 auto [major
, minor
, patch
, build
] = version
.unwrap().AsTuple();
319 aStrToAppend
.AppendLiteral(u
"|");
321 constexpr auto dot
= u
"."_ns
;
323 aStrToAppend
.AppendInt(major
);
324 aStrToAppend
.Append(dot
);
325 aStrToAppend
.AppendInt(minor
);
326 aStrToAppend
.Append(dot
);
327 aStrToAppend
.AppendInt(patch
);
328 aStrToAppend
.Append(dot
);
329 aStrToAppend
.AppendInt(build
);
332 static void AccumulateInstantiatorTelemetry(const nsAString
& aValue
) {
333 MOZ_ASSERT(NS_IsMainThread());
335 if (!aValue
.IsEmpty()) {
336 #if defined(MOZ_TELEMETRY_REPORTING)
337 Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS
, aValue
);
338 #endif // defined(MOZ_TELEMETRY_REPORTING)
339 CrashReporter::AnnotateCrashReport(
340 CrashReporter::Annotation::AccessibilityClient
,
341 NS_ConvertUTF16toUTF8(aValue
));
345 static void GatherInstantiatorTelemetry(nsIFile
* aClientExe
) {
346 MOZ_ASSERT(!NS_IsMainThread());
349 nsresult rv
= aClientExe
->GetLeafName(value
);
350 if (NS_SUCCEEDED(rv
)) {
351 AppendVersionInfo(aClientExe
, value
);
354 nsCOMPtr
<nsIRunnable
> runnable(
355 NS_NewRunnableFunction("a11y::AccumulateInstantiatorTelemetry",
356 [value
= std::move(value
)]() -> void {
357 AccumulateInstantiatorTelemetry(value
);
360 // Now that we've (possibly) obtained version info, send the resulting
361 // string back to the main thread to accumulate in telemetry.
362 NS_DispatchToMainThread(runnable
.forget());
365 void a11y::SetInstantiator(const uint32_t aPid
) {
366 nsCOMPtr
<nsIFile
> clientExe
;
367 if (!GetInstantiatorExecutable(aPid
, getter_AddRefs(clientExe
))) {
368 AccumulateInstantiatorTelemetry(
369 u
"(Failed to retrieve client image name)"_ns
);
373 // Only record the instantiator if it is the first instantiator, or if it does
374 // not match the previous one. Some blocked clients are repeatedly requesting
375 // a11y over and over so we don't want to be spawning countless telemetry
379 nsresult rv
= gInstantiator
->Equals(clientExe
, &equal
);
380 if (NS_SUCCEEDED(rv
) && equal
) {
385 gInstantiator
= clientExe
;
387 nsCOMPtr
<nsIRunnable
> runnable(
388 NS_NewRunnableFunction("a11y::GatherInstantiatorTelemetry",
389 [clientExe
= std::move(clientExe
)]() -> void {
390 GatherInstantiatorTelemetry(clientExe
);
393 DebugOnly
<nsresult
> rv
=
394 NS_DispatchBackgroundTask(runnable
.forget(), NS_DISPATCH_EVENT_MAY_BLOCK
);
395 MOZ_ASSERT(NS_SUCCEEDED(rv
));
398 bool a11y::GetInstantiator(nsIFile
** aOutInstantiator
) {
399 if (!gInstantiator
) {
403 return NS_SUCCEEDED(gInstantiator
->Clone(aOutInstantiator
));