tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }