tor-browser

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

DocAccessibleChild.cpp (15552B)


      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 "chrome/common/ipc_channel.h"
      8 #include "mozilla/a11y/DocAccessibleChild.h"
      9 #include "mozilla/a11y/CacheConstants.h"
     10 #include "mozilla/a11y/FocusManager.h"
     11 #include "mozilla/AppShutdown.h"
     12 #include "mozilla/PerfStats.h"
     13 #include "mozilla/ProfilerMarkers.h"
     14 #include "nsAccessibilityService.h"
     15 
     16 #include "LocalAccessible-inl.h"
     17 #ifdef A11Y_LOG
     18 #  include "Logging.h"
     19 #endif
     20 #include "TextLeafRange.h"
     21 
     22 namespace mozilla {
     23 namespace a11y {
     24 
     25 // Exceeding the IPDL maximum message size will cause a crash. Try to avoid
     26 // this by only including kMaxAccsPerMessage Accessibles in a single IPDL
     27 // call. If there are Accessibles beyond this, they will be split across
     28 // multiple calls.
     29 static constexpr uint32_t kMaxAccsPerMessage = 1000;
     30 
     31 /* static */
     32 void DocAccessibleChild::FlattenTree(LocalAccessible* aRoot,
     33                                     nsTArray<LocalAccessible*>& aTree) {
     34  MOZ_ASSERT(!aRoot->IsDoc(), "documents shouldn't be serialized");
     35 
     36  aTree.AppendElement(aRoot);
     37  // OuterDocAccessibles are special because we don't want to serialize the
     38  // child doc here, we'll call PDocAccessibleConstructor in
     39  // NotificationController.
     40  uint32_t childCount = aRoot->IsOuterDoc() ? 0 : aRoot->ChildCount();
     41 
     42  for (uint32_t i = 0; i < childCount; i++) {
     43    FlattenTree(aRoot->LocalChildAt(i), aTree);
     44  }
     45 }
     46 
     47 /* static */
     48 AccessibleData DocAccessibleChild::SerializeAcc(LocalAccessible* aAcc) {
     49  uint32_t genericTypes = aAcc->mGenericTypes;
     50  if (aAcc->ARIAHasNumericValue()) {
     51    // XXX: We need to do this because this requires a state check.
     52    genericTypes |= eNumericValue;
     53  }
     54 
     55  RefPtr<AccAttributes> fields;
     56  // Even though we send moves as a hide and a show, we don't want to
     57  // push the cache again for moves.
     58  if (!aAcc->Document()->IsAccessibleBeingMoved(aAcc)) {
     59    fields = aAcc->BundleFieldsForCache(
     60        nsAccessibilityService::GetActiveCacheDomains(),
     61        CacheUpdateType::Initial);
     62    if (fields->Count() == 0) {
     63      fields = nullptr;
     64    }
     65  }
     66 
     67  return AccessibleData(aAcc->ID(), aAcc->NativeRole(),
     68                        aAcc->LocalParent()->ID(),
     69                        static_cast<int32_t>(aAcc->IndexInParent()),
     70                        static_cast<AccType>(aAcc->mType),
     71                        static_cast<AccGenericType>(genericTypes),
     72                        aAcc->mRoleMapEntryIndex, fields);
     73 }
     74 
     75 void DocAccessibleChild::InsertIntoIpcTree(LocalAccessible* aChild,
     76                                           bool aSuppressShowEvent) {
     77  nsTArray<LocalAccessible*> shownTree;
     78  FlattenTree(aChild, shownTree);
     79  uint32_t totalAccs = shownTree.Length();
     80  nsTArray<AccessibleData> data(std::min(
     81      kMaxAccsPerMessage - mMutationEventBatcher.AccCount(), totalAccs));
     82 
     83  for (uint32_t accIndex = 0; accIndex < totalAccs; ++accIndex) {
     84    // This batch of mutation events has no more room left without exceeding our
     85    // limit. Write the show event data to the queue.
     86    if (data.Length() + mMutationEventBatcher.AccCount() ==
     87        kMaxAccsPerMessage) {
     88      if (AppShutdown::IsShutdownImpending()) {
     89        return;
     90      }
     91      // Note: std::move used on aSuppressShowEvent to force selection of the
     92      // ShowEventData constructor that takes all rvalue reference arguments.
     93      const uint32_t accCount = data.Length();
     94      PushMutationEventData(
     95          ShowEventData{std::move(data), std::move(aSuppressShowEvent), false,
     96                        false},
     97          accCount);
     98 
     99      // Reset data to avoid relying on state of moved-from object.
    100      // Preallocate an appropriate capacity to avoid resizing.
    101      data = nsTArray<AccessibleData>(
    102          std::min(kMaxAccsPerMessage, totalAccs - accIndex));
    103    }
    104    LocalAccessible* child = shownTree[accIndex];
    105    data.AppendElement(SerializeAcc(child));
    106  }
    107  if (AppShutdown::IsShutdownImpending()) {
    108    return;
    109  }
    110  if (!data.IsEmpty()) {
    111    const uint32_t accCount = data.Length();
    112    PushMutationEventData(
    113        ShowEventData{std::move(data), std::move(aSuppressShowEvent), true,
    114                      false},
    115        accCount);
    116  }
    117 }
    118 
    119 void DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent) {
    120  AUTO_PROFILER_MARKER_TEXT("DocAccessibleChild::ShowEvent", A11Y, {}, ""_ns);
    121  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ShowEvent>
    122      autoRecording;
    123  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
    124 
    125  LocalAccessible* child = aShowEvent->GetAccessible();
    126  InsertIntoIpcTree(child, /* aSuppressShowEvent */ false);
    127 }
    128 
    129 void DocAccessibleChild::PushMutationEventData(MutationEventData aData,
    130                                               uint32_t aAccCount) {
    131  mMutationEventBatcher.PushMutationEventData(std::move(aData), aAccCount,
    132                                              *this);
    133  // Once all mutation events for this tick are sent, we defer all updates
    134  // until the parent process sends us a single ACK. We set that flag here, but
    135  // we request that ACK in NotificationController::WillRefresh once all
    136  // mutation events have been sent.
    137  mHasUnackedMutationEvents = true;
    138 }
    139 
    140 void DocAccessibleChild::SendQueuedMutationEvents() {
    141  mMutationEventBatcher.SendQueuedMutationEvents(*this);
    142 }
    143 
    144 size_t DocAccessibleChild::MutationEventQueueLength() const {
    145  return mMutationEventBatcher.EventCount();
    146 }
    147 
    148 mozilla::ipc::IPCResult DocAccessibleChild::RecvTakeFocus(const uint64_t& aID) {
    149  LocalAccessible* acc = IdToAccessible(aID);
    150  if (acc) {
    151    acc->TakeFocus();
    152  }
    153  return IPC_OK();
    154 }
    155 
    156 mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollTo(
    157    const uint64_t& aID, const uint32_t& aScrollType) {
    158  LocalAccessible* acc = IdToAccessible(aID);
    159  if (acc) {
    160    RefPtr<PresShell> presShell = acc->Document()->PresShellPtr();
    161    nsCOMPtr<nsIContent> content = acc->GetContent();
    162    nsCoreUtils::ScrollTo(presShell, content, aScrollType);
    163  }
    164 
    165  return IPC_OK();
    166 }
    167 
    168 mozilla::ipc::IPCResult DocAccessibleChild::RecvTakeSelection(
    169    const uint64_t& aID) {
    170  LocalAccessible* acc = IdToAccessible(aID);
    171  if (acc) {
    172    acc->TakeSelection();
    173  }
    174 
    175  return IPC_OK();
    176 }
    177 
    178 mozilla::ipc::IPCResult DocAccessibleChild::RecvSetSelected(
    179    const uint64_t& aID, const bool& aSelect) {
    180  LocalAccessible* acc = IdToAccessible(aID);
    181  if (acc) {
    182    acc->SetSelected(aSelect);
    183  }
    184 
    185  return IPC_OK();
    186 }
    187 
    188 mozilla::ipc::IPCResult DocAccessibleChild::RecvVerifyCache(
    189    const uint64_t& aID, const uint64_t& aCacheDomain, AccAttributes* aFields) {
    190 #ifdef A11Y_LOG
    191  LocalAccessible* acc = IdToAccessible(aID);
    192  if (!acc) {
    193    return IPC_OK();
    194  }
    195 
    196  RefPtr<AccAttributes> localFields =
    197      acc->BundleFieldsForCache(aCacheDomain, CacheUpdateType::Update);
    198  bool mismatches = false;
    199 
    200  for (auto prop : *localFields) {
    201    if (prop.Value<DeleteEntry>()) {
    202      if (aFields->HasAttribute(prop.Name())) {
    203        if (!mismatches) {
    204          logging::MsgBegin("Mismatch!", "Local and remote values differ");
    205          logging::AccessibleInfo("", acc);
    206          mismatches = true;
    207        }
    208        nsAutoCString propName;
    209        prop.Name()->ToUTF8String(propName);
    210        nsAutoString val;
    211        aFields->GetAttribute(prop.Name(), val);
    212        logging::MsgEntry(
    213            "Remote value for %s should be empty, but instead it is '%s'",
    214            propName.get(), NS_ConvertUTF16toUTF8(val).get());
    215      }
    216      continue;
    217    }
    218 
    219    nsAutoString localVal;
    220    prop.ValueAsString(localVal);
    221    nsAutoString remoteVal;
    222    aFields->GetAttribute(prop.Name(), remoteVal);
    223    if (!localVal.Equals(remoteVal)) {
    224      if (!mismatches) {
    225        logging::MsgBegin("Mismatch!", "Local and remote values differ");
    226        logging::AccessibleInfo("", acc);
    227        mismatches = true;
    228      }
    229      nsAutoCString propName;
    230      prop.Name()->ToUTF8String(propName);
    231      logging::MsgEntry("Fields differ: %s '%s' != '%s'", propName.get(),
    232                        NS_ConvertUTF16toUTF8(remoteVal).get(),
    233                        NS_ConvertUTF16toUTF8(localVal).get());
    234    }
    235  }
    236  if (mismatches) {
    237    logging::MsgEnd();
    238  }
    239 #endif  // A11Y_LOG
    240 
    241  return IPC_OK();
    242 }
    243 
    244 mozilla::ipc::IPCResult DocAccessibleChild::RecvDoActionAsync(
    245    const uint64_t& aID, const uint8_t& aIndex) {
    246  if (LocalAccessible* acc = IdToAccessible(aID)) {
    247    (void)acc->DoAction(aIndex);
    248  }
    249 
    250  return IPC_OK();
    251 }
    252 
    253 mozilla::ipc::IPCResult DocAccessibleChild::RecvSetTextSelection(
    254    const uint64_t& aStartID, const int32_t& aStartOffset,
    255    const uint64_t& aEndID, const int32_t& aEndOffset,
    256    const int32_t& aSelectionNum, const bool& aSetFocus) {
    257  TextLeafRange range(TextLeafPoint(IdToAccessible(aStartID), aStartOffset),
    258                      TextLeafPoint(IdToAccessible(aEndID), aEndOffset));
    259  if (range) {
    260    range.SetSelection(aSelectionNum, aSetFocus);
    261  }
    262 
    263  return IPC_OK();
    264 }
    265 
    266 mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollTextLeafRangeIntoView(
    267    const uint64_t& aStartID, const int32_t& aStartOffset,
    268    const uint64_t& aEndID, const int32_t& aEndOffset,
    269    const uint32_t& aScrollType) {
    270  TextLeafRange range(TextLeafPoint(IdToAccessible(aStartID), aStartOffset),
    271                      TextLeafPoint(IdToAccessible(aEndID), aEndOffset));
    272  if (range) {
    273    range.ScrollIntoView(aScrollType);
    274  }
    275 
    276  return IPC_OK();
    277 }
    278 
    279 mozilla::ipc::IPCResult DocAccessibleChild::RecvRemoveTextSelection(
    280    const uint64_t& aID, const int32_t& aSelectionNum) {
    281  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    282  if (acc && acc->IsTextRole()) {
    283    acc->RemoveFromSelection(aSelectionNum);
    284  }
    285 
    286  return IPC_OK();
    287 }
    288 
    289 mozilla::ipc::IPCResult DocAccessibleChild::RecvSetCurValue(
    290    const uint64_t& aID, const double& aValue) {
    291  LocalAccessible* acc = IdToAccessible(aID);
    292  if (acc) {
    293    acc->SetCurValue(aValue);
    294  }
    295 
    296  return IPC_OK();
    297 }
    298 
    299 mozilla::ipc::IPCResult DocAccessibleChild::RecvReplaceText(
    300    const uint64_t& aID, const nsAString& aText) {
    301  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    302  if (acc && acc->IsTextRole()) {
    303    acc->ReplaceText(aText);
    304  }
    305 
    306  return IPC_OK();
    307 }
    308 
    309 mozilla::ipc::IPCResult DocAccessibleChild::RecvInsertText(
    310    const uint64_t& aID, const nsAString& aText, const int32_t& aPosition) {
    311  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    312  if (acc && acc->IsTextRole()) {
    313    acc->InsertText(aText, aPosition);
    314  }
    315 
    316  return IPC_OK();
    317 }
    318 
    319 mozilla::ipc::IPCResult DocAccessibleChild::RecvCopyText(
    320    const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
    321  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    322  if (acc && acc->IsTextRole()) {
    323    acc->CopyText(aStartPos, aEndPos);
    324  }
    325 
    326  return IPC_OK();
    327 }
    328 
    329 mozilla::ipc::IPCResult DocAccessibleChild::RecvCutText(
    330    const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
    331  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    332  if (acc && acc->IsTextRole()) {
    333    acc->CutText(aStartPos, aEndPos);
    334  }
    335 
    336  return IPC_OK();
    337 }
    338 
    339 mozilla::ipc::IPCResult DocAccessibleChild::RecvDeleteText(
    340    const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
    341  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    342  if (acc && acc->IsTextRole()) {
    343    acc->DeleteText(aStartPos, aEndPos);
    344  }
    345 
    346  return IPC_OK();
    347 }
    348 
    349 mozilla::ipc::IPCResult DocAccessibleChild::RecvPasteText(
    350    const uint64_t& aID, const int32_t& aPosition) {
    351  RefPtr<HyperTextAccessible> acc = IdToHyperTextAccessible(aID);
    352  if (acc && acc->IsTextRole()) {
    353    acc->PasteText(aPosition);
    354  }
    355 
    356  return IPC_OK();
    357 }
    358 
    359 ipc::IPCResult DocAccessibleChild::RecvRestoreFocus() {
    360  if (FocusManager* focusMgr = FocusMgr()) {
    361    focusMgr->ForceFocusEvent();
    362  }
    363  return IPC_OK();
    364 }
    365 
    366 mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollToPoint(
    367    const uint64_t& aID, const uint32_t& aScrollType, const int32_t& aX,
    368    const int32_t& aY) {
    369  LocalAccessible* acc = IdToAccessible(aID);
    370  if (acc) {
    371    acc->ScrollToPoint(aScrollType, aX, aY);
    372  }
    373 
    374  return IPC_OK();
    375 }
    376 
    377 #if !defined(XP_WIN)
    378 mozilla::ipc::IPCResult DocAccessibleChild::RecvAnnounce(
    379    const uint64_t& aID, const nsAString& aAnnouncement,
    380    const uint16_t& aPriority) {
    381  LocalAccessible* acc = IdToAccessible(aID);
    382  if (acc) {
    383    acc->Announce(aAnnouncement, aPriority);
    384  }
    385 
    386  return IPC_OK();
    387 }
    388 #endif  // !defined(XP_WIN)
    389 
    390 mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollSubstringToPoint(
    391    const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset,
    392    const uint32_t& aCoordinateType, const int32_t& aX, const int32_t& aY) {
    393  HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
    394  if (acc) {
    395    acc->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType, aX,
    396                                aY);
    397  }
    398 
    399  return IPC_OK();
    400 }
    401 
    402 mozilla::ipc::IPCResult DocAccessibleChild::RecvAckMutationEvents() {
    403  mHasUnackedMutationEvents = false;
    404  return IPC_OK();
    405 }
    406 
    407 LocalAccessible* DocAccessibleChild::IdToAccessible(const uint64_t& aID) const {
    408  if (!aID) return mDoc;
    409 
    410  if (!mDoc) return nullptr;
    411 
    412  return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
    413 }
    414 
    415 HyperTextAccessible* DocAccessibleChild::IdToHyperTextAccessible(
    416    const uint64_t& aID) const {
    417  LocalAccessible* acc = IdToAccessible(aID);
    418  return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
    419 }
    420 
    421 void DocAccessibleChild::MutationEventBatcher::PushMutationEventData(
    422    MutationEventData aData, uint32_t aAccCount, DocAccessibleChild& aDocAcc) {
    423  // We want to send the mutation events in batches. The number of events in a
    424  // batch is unscientific. The goal is to avoid sending more data than would
    425  // overwhelm the IPC mechanism (see IPC::Channel::kMaximumMessageSize), but we
    426  // stop short of measuring actual message size here. We also don't want to
    427  // send too many events in one message, since that could choke up the parent
    428  // process as it tries to fire all the events synchronously. To address these
    429  // constraints, we construct batches of mutation event data, limiting our
    430  // events by number of Accessibles touched.
    431  MOZ_ASSERT(aAccCount <= kMaxAccsPerMessage,
    432             "More Accessibles given than can fit in a single batch");
    433  MOZ_ASSERT(aAccCount > 0, "Attempting to send an empty mutation event.");
    434 
    435  // If we hit the exact limit of max Accessibles per message, send the queued
    436  // mutation events. This happens somewhat often due to the logic in
    437  // InsertIntoIpcTree that attempts to generate perfectly-sized ShowEventData.
    438  if (mAccCount + aAccCount == kMaxAccsPerMessage) {
    439    mMutationEventData.AppendElement(std::move(aData));
    440    SendQueuedMutationEvents(aDocAcc);
    441    return;
    442  }
    443 
    444  // If the batch cannot accommodate the number of new Accessibles, send the
    445  // queued events, then append the event data.
    446  if (mAccCount + aAccCount > kMaxAccsPerMessage) {
    447    SendQueuedMutationEvents(aDocAcc);
    448  }
    449  mMutationEventData.AppendElement(std::move(aData));
    450  mAccCount += aAccCount;
    451 }
    452 
    453 void DocAccessibleChild::MutationEventBatcher::SendQueuedMutationEvents(
    454    DocAccessibleChild& aDocAcc) {
    455  if (AppShutdown::IsShutdownImpending()) {
    456    return;
    457  }
    458  aDocAcc.SendMutationEvents(mMutationEventData);
    459 
    460  // Reset the batcher state.
    461  mMutationEventData.Clear();
    462  mAccCount = 0;
    463 }
    464 
    465 }  // namespace a11y
    466 }  // namespace mozilla