Compatibility.cpp (7294B)
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 "Compatibility.h" 8 9 #include "mozilla/WindowsVersion.h" 10 #include "mozilla/WinHeaderOnlyUtils.h" 11 #include "mozilla/StaticPrefs_accessibility.h" 12 #include "nsExceptionHandler.h" 13 #include "nsIXULRuntime.h" 14 #include "nsPrintfCString.h" 15 #include "nsUnicharUtils.h" 16 #include "nsWinUtils.h" 17 #include "Statistics.h" 18 #include "AccessibleWrap.h" 19 20 #include "mozilla/Preferences.h" 21 22 #include <shlobj.h> 23 24 using namespace mozilla; 25 using namespace mozilla::a11y; 26 27 /** 28 * String versions of consumer flags. See GetHumanReadableConsumersStr. 29 */ 30 static const wchar_t* ConsumerStringMap[CONSUMERS_ENUM_LEN + 1] = { 31 L"NVDA", L"JAWS", L"OLDJAWS", L"WE", L"DOLPHIN", 32 L"SEROTEK", L"COBRA", L"ZOOMTEXT", L"KAZAGURU", L"YOUDAO", 33 L"UNKNOWN", L"UIAUTOMATION", L"VISPEROSHARED", L"\0"}; 34 35 bool Compatibility::IsModuleVersionLessThan(HMODULE aModuleHandle, 36 unsigned long long aVersion) { 37 LauncherResult<ModuleVersion> version = GetModuleVersion(aModuleHandle); 38 if (version.isErr()) { 39 return true; 40 } 41 42 return version.unwrap() < aVersion; 43 } 44 45 //////////////////////////////////////////////////////////////////////////////// 46 // Compatibility 47 //////////////////////////////////////////////////////////////////////////////// 48 49 uint32_t Compatibility::sConsumers = Compatibility::UNKNOWN; 50 51 /** 52 * This function is safe to call multiple times. 53 */ 54 /* static */ 55 void Compatibility::InitConsumers() { 56 HMODULE jawsHandle = ::GetModuleHandleW(L"jhook"); 57 if (jawsHandle) { 58 sConsumers |= 59 IsModuleVersionLessThan(jawsHandle, MAKE_FILE_VERSION(19, 0, 0, 0)) 60 ? OLDJAWS 61 : JAWS; 62 } 63 64 if (::GetModuleHandleW(L"gwm32inc")) sConsumers |= WE; 65 66 if (::GetModuleHandleW(L"dolwinhk")) sConsumers |= DOLPHIN; 67 68 if (::GetModuleHandleW(L"STSA32")) sConsumers |= SEROTEK; 69 70 if (::GetModuleHandleW(L"nvdaHelperRemote")) sConsumers |= NVDA; 71 72 if (::GetModuleHandleW(L"OsmHooks") || ::GetModuleHandleW(L"OsmHks64")) 73 sConsumers |= COBRA; 74 75 if (::GetModuleHandleW(L"WebFinderRemote")) sConsumers |= ZOOMTEXT; 76 77 if (::GetModuleHandleW(L"Kazahook")) sConsumers |= KAZAGURU; 78 79 if (::GetModuleHandleW(L"TextExtractorImpl32") || 80 ::GetModuleHandleW(L"TextExtractorImpl64")) 81 sConsumers |= YOUDAO; 82 83 if (::GetModuleHandleW(L"uiautomation") || 84 ::GetModuleHandleW(L"uiautomationcore")) 85 sConsumers |= UIAUTOMATION; 86 87 if (::GetModuleHandleW(L"AccEventCache")) { 88 sConsumers |= VISPEROSHARED; 89 } 90 91 // If we have a known consumer remove the unknown bit. 92 if (sConsumers != Compatibility::UNKNOWN) 93 sConsumers &= ~Compatibility::UNKNOWN; 94 } 95 96 /* static */ 97 bool Compatibility::HasKnownNonUiaConsumer() { 98 InitConsumers(); 99 return sConsumers & ~(Compatibility::UNKNOWN | UIAUTOMATION); 100 } 101 102 void Compatibility::Init() { 103 // Note we collect some AT statistics/telemetry here for convenience. 104 InitConsumers(); 105 106 CrashReporter::RecordAnnotationNSCString( 107 CrashReporter::Annotation::AccessibilityInProcClient, 108 nsPrintfCString("0x%X", sConsumers)); 109 110 // Gather telemetry 111 uint32_t temp = sConsumers; 112 for (int i = 0; temp; i++) { 113 if (temp & 0x1) statistics::A11yConsumers(i); 114 115 temp >>= 1; 116 } 117 118 // Turn off new tab switching for Jaws and WE. 119 if (sConsumers & (JAWS | OLDJAWS | WE)) { 120 // Check to see if the pref for disallowing CtrlTab is already set. If so, 121 // bail out (respect the user settings). If not, set it. 122 if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders")) 123 Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true); 124 } 125 } 126 127 // static 128 void Compatibility::GetHumanReadableConsumersStr(nsAString& aResult) { 129 bool appened = false; 130 uint32_t index = 0; 131 for (uint32_t consumers = sConsumers; consumers; consumers = consumers >> 1) { 132 if (consumers & 0x1) { 133 if (appened) { 134 aResult.AppendLiteral(","); 135 } 136 aResult.Append(ConsumerStringMap[index]); 137 appened = true; 138 } 139 if (++index > CONSUMERS_ENUM_LEN) { 140 break; 141 } 142 } 143 } 144 145 struct SuppressionTimer { 146 constexpr SuppressionTimer() = default; 147 void Start() { mStart = ::GetTickCount(); } 148 bool IsActive(DWORD aTickCount) const { 149 return mStart && aTickCount - mStart < kSuppressTimeout; 150 } 151 // Last time Start() was called, as returned by ::GetTickCount(). 152 DWORD mStart = 0; 153 154 static constexpr DWORD kSuppressTimeout = 1500; // ms 155 }; 156 157 static SuppressionTimer sClipboardSuppressionTimer; 158 static SuppressionTimer sSnapLayoutsSuppressionTimer; 159 160 /* static */ 161 void Compatibility::SuppressA11yForClipboardCopy() { 162 // Bug 1774285: Windows Suggested Actions (introduced in Windows 11 22H2) 163 // might walk the a11y tree using UIA whenever anything is copied to the 164 // clipboard. This causes an unacceptable hang, particularly when the cache 165 // is disabled. 166 bool doSuppress = [&] { 167 switch ( 168 StaticPrefs::accessibility_windows_suppress_after_clipboard_copy()) { 169 case 0: 170 return false; 171 case 1: 172 return true; 173 default: 174 // Our workaround for Suggested Actions is needed from Windows 11 22H2 175 return IsWin1122H2OrLater(); 176 } 177 }(); 178 179 if (doSuppress) { 180 sClipboardSuppressionTimer.Start(); 181 } 182 } 183 184 /* static */ 185 void Compatibility::SuppressA11yForSnapLayouts() { 186 // Bug 1883132: Snap Layouts might enable a11y to find the maximize button 187 // position. 188 bool doSuppress = [&] { 189 switch (StaticPrefs::accessibility_windows_suppress_for_snap_layout()) { 190 case 0: 191 return false; 192 case 1: 193 return true; 194 default: 195 // Our workaround for Snap Layouts is needed from Windows 11 22H2 196 return IsWin1122H2OrLater(); 197 } 198 }(); 199 200 if (doSuppress) { 201 sSnapLayoutsSuppressionTimer.Start(); 202 } 203 } 204 205 /* static */ 206 SuppressionReasons Compatibility::A11ySuppressionReasons() { 207 const auto now = ::GetTickCount(); 208 auto reasons = SuppressionReasons::None; 209 if (sClipboardSuppressionTimer.IsActive(now)) { 210 reasons |= SuppressionReasons::Clipboard; 211 } 212 if (sSnapLayoutsSuppressionTimer.IsActive(now)) { 213 reasons |= SuppressionReasons::SnapLayouts; 214 } 215 return reasons; 216 } 217 218 /* static */ 219 bool Compatibility::IsUiaEnabled() { 220 // This is the only function which should call the UIA pref function directly. 221 const uint32_t pref = 222 StaticPrefs::accessibility_uia_enable_DoNotUseDirectly(); 223 if (pref == 0) { 224 return false; // Never enable. 225 } 226 if (pref == 1) { 227 return true; // Always enable. 228 } 229 // Bug 1956415: Some screen readers break when native UIA is enabled, so don't 230 // enable it when they're detected. 231 // Call InitConsumers() if we haven't already. It's safe to call this multiple 232 // times, but it still does a small amount of work and we can easily avoid 233 // unnecessary calls here. 234 if (!(sConsumers & UIAUTOMATION)) { 235 InitConsumers(); 236 } 237 return !IsJAWS() && !IsOldJAWS() && !IsVisperoShared() && 238 !(sConsumers & NVDA); 239 }