Bug 1769547 - Do not MOZ_CRASH() on missing process r=nika
[gecko.git] / accessible / windows / msaa / Platform.cpp
blob1309974699637dbb93b425422bc99ac559cbc7eb
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/. */
7 #include "Platform.h"
9 #include <olectl.h>
11 #include "AccEvent.h"
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"
30 #include "WinUtils.h"
32 #include <tuple>
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));
60 if (NS_FAILED(rv)) {
61 return false;
64 rv = handlerPath->Append(u"AccessibleHandler.dll"_ns);
65 if (NS_FAILED(rv)) {
66 return false;
68 nsAutoString path;
69 rv = handlerPath->GetPath(path);
70 if (NS_FAILED(rv)) {
71 return false;
74 nsModuleHandle handlerDll(LoadLibrary(path.get()));
75 if (!handlerDll.get()) {
76 return false;
78 auto RegisterMsix = reinterpret_cast<decltype(&DllRegisterServer)>(
79 GetProcAddress(handlerDll, "RegisterMsix"));
80 if (!RegisterMsix) {
81 return false;
83 if (FAILED(RegisterMsix())) {
84 return false;
86 return true;
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
108 // this.
109 RegisterHandlerMsix();
113 void a11y::PlatformShutdown() {
114 ::DestroyCaret();
116 nsWinUtils::ShutdownWindowEmulation();
117 gRegCustomProxy = nullptr;
118 gRegProxy = nullptr;
119 gRegAccTlb = nullptr;
120 gRegMiscTlb = nullptr;
122 if (gInstantiator) {
123 gInstantiator = nullptr;
127 void a11y::ProxyCreated(RemoteAccessible* aProxy) {
128 MsaaAccessible* msaa = MsaaAccessible::Create(aProxy);
129 msaa->AddRef();
130 aProxy->SetWrapper(reinterpret_cast<uintptr_t>(msaa));
133 void a11y::ProxyDestroyed(RemoteAccessible* aProxy) {
134 MsaaAccessible* msaa =
135 reinterpret_cast<MsaaAccessible*>(aProxy->GetWrapper());
136 if (!msaa) {
137 return;
139 msaa->MsaaShutdown();
140 aProxy->SetWrapper(0);
141 msaa->Release();
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
166 // able to trigger.
167 return;
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,
184 bool) {
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();
191 if (useHandler) {
192 AccessibleWrap::DispatchTextChangeToHandler(aText, aInsert, aStr, aStart,
193 aLen);
194 return;
197 // XXX Call ia2AccessibleText::UpdateTextChangeData once that works for
198 // RemoteAccessible.
199 MsaaAccessible::FireWinEvent(aText, eventType);
202 void a11y::ProxyShowHideEvent(RemoteAccessible* aTarget, RemoteAccessible*,
203 bool aInsert, bool) {
204 uint32_t event =
205 aInsert ? nsIAccessibleEvent::EVENT_SHOW : nsIAccessibleEvent::EVENT_HIDE;
206 MsaaAccessible::FireWinEvent(aTarget, event);
209 void a11y::ProxySelectionEvent(RemoteAccessible* aTarget, RemoteAccessible*,
210 uint32_t aType) {
211 MsaaAccessible::FireWinEvent(aTarget, aType);
214 bool a11y::IsHandlerRegistered() {
215 nsresult rv;
216 nsCOMPtr<nsIWindowsRegKey> regKey =
217 do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
218 if (NS_FAILED(rv)) {
219 return false;
222 nsAutoString clsid;
223 GUIDToString(CLSID_AccessibleHandler, clsid);
225 nsAutoString subKey;
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
231 // there.
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);
236 if (NS_FAILED(rv)) {
237 return false;
240 nsAutoString handlerPath;
241 rv = regKey->ReadStringValue(nsAutoString(), handlerPath);
242 if (NS_FAILED(rv)) {
243 return false;
246 nsCOMPtr<nsIFile> actualHandler;
247 rv = NS_NewLocalFile(handlerPath, false, getter_AddRefs(actualHandler));
248 if (NS_FAILED(rv)) {
249 return false;
252 nsCOMPtr<nsIFile> expectedHandler;
253 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(expectedHandler));
254 if (NS_FAILED(rv)) {
255 return false;
258 rv = expectedHandler->Append(u"AccessibleHandler.dll"_ns);
259 if (NS_FAILED(rv)) {
260 return false;
263 bool equal;
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) {
273 return false;
276 DWORD bufLen = MAX_PATH;
277 UniquePtr<wchar_t[]> buf;
279 while (true) {
280 buf = MakeUnique<wchar_t[]>(bufLen);
281 if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) {
282 break;
285 DWORD lastError = ::GetLastError();
286 MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER);
287 if (lastError != ERROR_INSUFFICIENT_BUFFER) {
288 return false;
291 bufLen *= 2;
294 nsCOMPtr<nsIFile> file;
295 nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false,
296 getter_AddRefs(file));
297 if (NS_FAILED(rv)) {
298 return false;
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()) {
314 return;
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());
348 nsString value;
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);
358 }));
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);
370 return;
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
376 // threads.
377 if (gInstantiator) {
378 bool equal;
379 nsresult rv = gInstantiator->Equals(clientExe, &equal);
380 if (NS_SUCCEEDED(rv) && equal) {
381 return;
385 gInstantiator = clientExe;
387 nsCOMPtr<nsIRunnable> runnable(
388 NS_NewRunnableFunction("a11y::GatherInstantiatorTelemetry",
389 [clientExe = std::move(clientExe)]() -> void {
390 GatherInstantiatorTelemetry(clientExe);
391 }));
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) {
400 return false;
403 return NS_SUCCEEDED(gInstantiator->Clone(aOutInstantiator));