Platform.cpp (10670B)
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 "Platform.h" 8 9 #include "AccEvent.h" 10 #include "Compatibility.h" 11 #include "MsaaAccessible.h" 12 #include "nsWinUtils.h" 13 #include "mozilla/a11y/DocAccessibleParent.h" 14 #include "mozilla/a11y/HyperTextAccessibleBase.h" 15 #include "mozilla/a11y/RemoteAccessible.h" 16 #include "mozilla/StaticPtr.h" 17 #include "mozilla/WinHeaderOnlyUtils.h" 18 #include "ia2AccessibleText.h" 19 20 #if defined(MOZ_TELEMETRY_REPORTING) 21 # include "mozilla/glean/AccessibleMetrics.h" 22 #endif // defined(MOZ_TELEMETRY_REPORTING) 23 24 using namespace mozilla; 25 using namespace mozilla::a11y; 26 27 static StaticRefPtr<nsIFile> gInstantiator; 28 29 /** 30 * System caret support: update the Windows caret position. 31 * The system caret works more universally than the MSAA caret 32 * For example, Window-Eyes, JAWS, ZoomText and Windows Tablet Edition use it 33 * We will use an invisible system caret. 34 * Gecko is still responsible for drawing its own caret 35 */ 36 static void UpdateSystemCaretFor(Accessible* aAccessible) { 37 // Move the system caret so that Windows Tablet Edition and tradional ATs with 38 // off-screen model can follow the caret 39 ::DestroyCaret(); 40 HyperTextAccessibleBase* text = aAccessible->AsHyperTextBase(); 41 if (!text) { 42 return; 43 } 44 auto [caretRect, widget] = text->GetCaretRect(); 45 if (caretRect.IsEmpty() || !widget) { 46 return; 47 } 48 HWND caretWnd = 49 reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); 50 if (!caretWnd) { 51 return; 52 } 53 // Create invisible bitmap for caret, otherwise its appearance interferes 54 // with Gecko caret 55 nsAutoBitmap caretBitMap(CreateBitmap(1, caretRect.Height(), 1, 1, nullptr)); 56 if (::CreateCaret(caretWnd, caretBitMap, 1, 57 caretRect.Height())) { // Also destroys the last caret 58 ::ShowCaret(caretWnd); 59 POINT clientPoint{caretRect.X(), caretRect.Y()}; 60 ::ScreenToClient(caretWnd, &clientPoint); 61 ::SetCaretPos(clientPoint.x, clientPoint.y); 62 } 63 } 64 65 void a11y::PlatformInit() { 66 nsWinUtils::MaybeStartWindowEmulation(); 67 ia2AccessibleText::InitTextChangeData(); 68 } 69 70 void a11y::PlatformShutdown() { 71 ::DestroyCaret(); 72 73 nsWinUtils::ShutdownWindowEmulation(); 74 75 if (gInstantiator) { 76 gInstantiator = nullptr; 77 } 78 } 79 80 void a11y::ProxyCreated(RemoteAccessible* aProxy) { 81 MsaaAccessible* msaa = MsaaAccessible::Create(aProxy); 82 msaa->AddRef(); 83 aProxy->SetWrapper(reinterpret_cast<uintptr_t>(msaa)); 84 } 85 86 void a11y::ProxyDestroyed(RemoteAccessible* aProxy) { 87 MsaaAccessible* msaa = 88 reinterpret_cast<MsaaAccessible*>(aProxy->GetWrapper()); 89 if (!msaa) { 90 return; 91 } 92 msaa->MsaaShutdown(); 93 aProxy->SetWrapper(0); 94 msaa->Release(); 95 96 if (aProxy->IsDoc() && nsWinUtils::IsWindowEmulationStarted()) { 97 aProxy->AsDoc()->SetEmulatedWindowHandle(nullptr); 98 } 99 } 100 101 void a11y::PlatformEvent(Accessible* aTarget, uint32_t aEventType) { 102 Accessible* msaaTarget = aTarget; 103 if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING_START && 104 aTarget->IsTextLeaf()) { 105 // For MSAA/IA2, this event should not be fired on text leaf Accessibles. 106 msaaTarget = aTarget->Parent(); 107 } 108 if (msaaTarget) { 109 MsaaAccessible::FireWinEvent(msaaTarget, aEventType); 110 } 111 uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aEventType); 112 } 113 114 void a11y::PlatformStateChangeEvent(Accessible* aTarget, uint64_t aState, 115 bool aEnabled) { 116 MsaaAccessible::FireWinEvent(aTarget, nsIAccessibleEvent::EVENT_STATE_CHANGE); 117 uiaRawElmProvider::RaiseUiaEventForStateChange(aTarget, aState, aEnabled); 118 } 119 120 void a11y::PlatformFocusEvent(Accessible* aTarget) { 121 if (aTarget->IsRemote() && FocusMgr() && 122 FocusMgr()->FocusedLocalAccessible()) { 123 // This is a focus event from a remote document, but focus has moved out 124 // of that document into the chrome since that event was sent. For example, 125 // this can happen when choosing File menu -> New Tab. See bug 1471466. 126 // Note that this does not handle the case where a focus event is sent from 127 // one remote document, but focus moved into a second remote document 128 // since that event was sent. However, this isn't something anyone has been 129 // able to trigger. 130 return; 131 } 132 133 UpdateSystemCaretFor(aTarget); 134 MsaaAccessible::FireWinEvent(aTarget, nsIAccessibleEvent::EVENT_FOCUS); 135 uiaRawElmProvider::RaiseUiaEventForGeckoEvent( 136 aTarget, nsIAccessibleEvent::EVENT_FOCUS); 137 } 138 139 void a11y::PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset, 140 bool aIsSelectionCollapsed, 141 int32_t aGranularity, bool aFromUser) { 142 UpdateSystemCaretFor(aTarget); 143 MsaaAccessible::FireWinEvent(aTarget, 144 nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED); 145 uiaRawElmProvider::RaiseUiaEventForGeckoEvent( 146 aTarget, nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED); 147 } 148 149 void a11y::PlatformTextChangeEvent(Accessible* aText, const nsAString& aStr, 150 int32_t aStart, uint32_t aLen, bool aInsert, 151 bool) { 152 uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED 153 : nsIAccessibleEvent::EVENT_TEXT_REMOVED; 154 MOZ_ASSERT(aText->IsHyperText()); 155 ia2AccessibleText::UpdateTextChangeData(aText->AsHyperTextBase(), aInsert, 156 aStr, aStart, aLen); 157 MsaaAccessible::FireWinEvent(aText, eventType); 158 uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aText, eventType); 159 } 160 161 void a11y::PlatformShowHideEvent(Accessible* aTarget, Accessible*, bool aInsert, 162 bool) { 163 uint32_t event = 164 aInsert ? nsIAccessibleEvent::EVENT_SHOW : nsIAccessibleEvent::EVENT_HIDE; 165 MsaaAccessible::FireWinEvent(aTarget, event); 166 } 167 168 void a11y::PlatformSelectionEvent(Accessible* aTarget, Accessible*, 169 uint32_t aType) { 170 MsaaAccessible::FireWinEvent(aTarget, aType); 171 uiaRawElmProvider::RaiseUiaEventForGeckoEvent(aTarget, aType); 172 } 173 174 static bool GetInstantiatorExecutable(const DWORD aPid, 175 nsIFile** aOutClientExe) { 176 nsAutoHandle callingProcess( 177 ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, aPid)); 178 if (!callingProcess) { 179 return false; 180 } 181 182 DWORD bufLen = MAX_PATH; 183 UniquePtr<wchar_t[]> buf; 184 185 while (true) { 186 buf = MakeUnique<wchar_t[]>(bufLen); 187 if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) { 188 break; 189 } 190 191 DWORD lastError = ::GetLastError(); 192 MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER); 193 if (lastError != ERROR_INSUFFICIENT_BUFFER) { 194 return false; 195 } 196 197 bufLen *= 2; 198 } 199 200 nsCOMPtr<nsIFile> file; 201 nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), 202 getter_AddRefs(file)); 203 if (NS_FAILED(rv)) { 204 return false; 205 } 206 207 file.forget(aOutClientExe); 208 return NS_SUCCEEDED(rv); 209 } 210 211 /** 212 * Appends version information in the format "|a.b.c.d". 213 * If there is no version information, we append nothing. 214 */ 215 static void AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend) { 216 MOZ_ASSERT(!NS_IsMainThread()); 217 218 LauncherResult<ModuleVersion> version = GetModuleVersion(aClientExe); 219 if (version.isErr()) { 220 return; 221 } 222 223 auto [major, minor, patch, build] = version.unwrap().AsTuple(); 224 225 aStrToAppend.AppendLiteral(u"|"); 226 227 constexpr auto dot = u"."_ns; 228 229 aStrToAppend.AppendInt(major); 230 aStrToAppend.Append(dot); 231 aStrToAppend.AppendInt(minor); 232 aStrToAppend.Append(dot); 233 aStrToAppend.AppendInt(patch); 234 aStrToAppend.Append(dot); 235 aStrToAppend.AppendInt(build); 236 } 237 238 static void AccumulateInstantiatorTelemetry(const nsAString& aValue) { 239 MOZ_ASSERT(NS_IsMainThread()); 240 241 if (!aValue.IsEmpty()) { 242 #if defined(MOZ_TELEMETRY_REPORTING) 243 glean::a11y::instantiators.Set(NS_ConvertUTF16toUTF8(aValue)); 244 #endif // defined(MOZ_TELEMETRY_REPORTING) 245 CrashReporter::RecordAnnotationNSString( 246 CrashReporter::Annotation::AccessibilityClient, aValue); 247 } 248 } 249 250 static void GatherInstantiatorTelemetry(nsIFile* aClientExe) { 251 MOZ_ASSERT(!NS_IsMainThread()); 252 253 nsString value; 254 nsresult rv = aClientExe->GetLeafName(value); 255 if (NS_SUCCEEDED(rv)) { 256 AppendVersionInfo(aClientExe, value); 257 } 258 259 nsCOMPtr<nsIRunnable> runnable( 260 NS_NewRunnableFunction("a11y::AccumulateInstantiatorTelemetry", 261 [value = std::move(value)]() -> void { 262 AccumulateInstantiatorTelemetry(value); 263 })); 264 265 // Now that we've (possibly) obtained version info, send the resulting 266 // string back to the main thread to accumulate in telemetry. 267 NS_DispatchToMainThread(runnable.forget()); 268 } 269 270 void a11y::SetInstantiator(const uint32_t aPid) { 271 nsCOMPtr<nsIFile> clientExe; 272 if (!GetInstantiatorExecutable(aPid, getter_AddRefs(clientExe))) { 273 AccumulateInstantiatorTelemetry( 274 u"(Failed to retrieve client image name)"_ns); 275 return; 276 } 277 278 // Only record the instantiator if it is the first instantiator, or if it does 279 // not match the previous one. Some blocked clients are repeatedly requesting 280 // a11y over and over so we don't want to be spawning countless telemetry 281 // threads. 282 if (gInstantiator) { 283 bool equal; 284 nsresult rv = gInstantiator->Equals(clientExe, &equal); 285 if (NS_SUCCEEDED(rv) && equal) { 286 return; 287 } 288 } 289 290 gInstantiator = clientExe; 291 292 nsCOMPtr<nsIRunnable> runnable( 293 NS_NewRunnableFunction("a11y::GatherInstantiatorTelemetry", 294 [clientExe = std::move(clientExe)]() -> void { 295 GatherInstantiatorTelemetry(clientExe); 296 })); 297 298 DebugOnly<nsresult> rv = 299 NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); 300 MOZ_ASSERT(NS_SUCCEEDED(rv)); 301 } 302 303 bool a11y::GetInstantiator(nsIFile** aOutInstantiator) { 304 if (!gInstantiator) { 305 return false; 306 } 307 308 return NS_SUCCEEDED(gInstantiator->Clone(aOutInstantiator)); 309 } 310 311 uint64_t a11y::GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { 312 // If we're instantiating because of a screen reader, enable all cache 313 // domains. We expect that demanding ATs will need all information we have. 314 if (Compatibility::IsKnownScreenReader()) { 315 return CacheDomain::All; 316 } 317 return aCacheDomains; 318 }