tor-browser

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

EventQueue.cpp (20482B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "EventQueue.h"
      7 
      8 #include "mozilla/PerfStats.h"
      9 #include "mozilla/ProfilerMarkers.h"
     10 
     11 #include "LocalAccessible-inl.h"
     12 #include "nsEventShell.h"
     13 #include "DocAccessibleChild.h"
     14 #include "nsTextEquivUtils.h"
     15 #ifdef A11Y_LOG
     16 #  include "Logging.h"
     17 #endif
     18 #include "Relation.h"
     19 
     20 namespace mozilla {
     21 namespace a11y {
     22 
     23 // Defines the number of selection add/remove events in the queue when they
     24 // aren't packed into single selection within event.
     25 const unsigned int kSelChangeCountToPack = 5;
     26 
     27 ////////////////////////////////////////////////////////////////////////////////
     28 // EventQueue
     29 ////////////////////////////////////////////////////////////////////////////////
     30 
     31 bool EventQueue::PushEvent(AccEvent* aEvent) {
     32  NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
     33                   aEvent->Document() == mDocument,
     34               "Queued event belongs to another document!");
     35 
     36  if (aEvent->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
     37    mFocusEvent = aEvent;
     38    return true;
     39  }
     40 
     41  if (aEvent->mEventRule == AccEvent::eRemoveDupes && !mEvents.IsEmpty()) {
     42    // Check for duplicate events. If aEvent is identical to an older event, do
     43    // not append aEvent. We do this here rather than in CoalesceEvents because
     44    // CoalesceEvents never *removes* events; it only sets them to eDoNotEmit.
     45    // If there are many duplicate events and we appended them, this would
     46    // result in a massive event queue and coalescing would become increasingly
     47    // slow with each event queued. Doing it here, we avoid appending a
     48    // duplicate event in the first place.
     49    uint32_t last = mEvents.Length() - 1;
     50    for (uint32_t index = last; index <= last; --index) {
     51      AccEvent* checkEvent = mEvents[index];
     52      if (checkEvent->mEventType == aEvent->mEventType &&
     53          checkEvent->mEventRule == aEvent->mEventRule &&
     54          checkEvent->mAccessible == aEvent->mAccessible) {
     55        aEvent->mEventRule = AccEvent::eDoNotEmit;
     56        return true;
     57      }
     58    }
     59  }
     60 
     61  // XXX(Bug 1631371) Check if this should use a fallible operation as it
     62  // pretended earlier, or change the return type to void.
     63  mEvents.AppendElement(aEvent);
     64 
     65  // Filter events.
     66  CoalesceEvents();
     67 
     68  if (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
     69      aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
     70      aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) {
     71    MOZ_ASSERT(aEvent->mEventRule != AccEvent::eDoNotEmit);
     72    PushNameOrDescriptionChange(aEvent);
     73  }
     74  return true;
     75 }
     76 
     77 bool EventQueue::PushNameOrDescriptionChangeToRelations(
     78    LocalAccessible* aAccessible, RelationType aType) {
     79  MOZ_ASSERT(aType == RelationType::LABEL_FOR ||
     80             aType == RelationType::DESCRIPTION_FOR);
     81 
     82  bool pushed = false;
     83  uint32_t eventType = aType == RelationType::LABEL_FOR
     84                           ? nsIAccessibleEvent::EVENT_NAME_CHANGE
     85                           : nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE;
     86  Relation rel = aAccessible->RelationByType(aType);
     87  while (LocalAccessible* relTarget = rel.LocalNext()) {
     88    RefPtr<AccEvent> nameChangeEvent = new AccEvent(eventType, relTarget);
     89    pushed |= PushEvent(nameChangeEvent);
     90  }
     91 
     92  return pushed;
     93 }
     94 
     95 bool EventQueue::PushNameOrDescriptionChange(AccEvent* aOrigEvent) {
     96  // Fire name/description change event on parent or related LocalAccessible
     97  // being labelled/described given that this event hasn't been coalesced, the
     98  // dependent's name/description was calculated from this subtree, and the
     99  // subtree was changed.
    100  LocalAccessible* target = aOrigEvent->mAccessible;
    101  // If the text of a text leaf changed without replacing the leaf, the only
    102  // event we get is text inserted on the container. Or, a reorder event may
    103  // change the container's name. In this case, we might need to fire a name
    104  // change event on the target itself.
    105  const bool maybeTargetNameChanged =
    106      (aOrigEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
    107       aOrigEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED ||
    108       aOrigEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER ||
    109       aOrigEvent->mEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) &&
    110      nsTextEquivUtils::HasNameRule(target, eNameFromSubtreeRule);
    111  const bool doName = target->HasNameDependent() || maybeTargetNameChanged;
    112  const bool doDesc = target->HasDescriptionDependent();
    113 
    114  if (!doName && !doDesc) {
    115    return false;
    116  }
    117  bool pushed = false;
    118  bool nameCheckAncestor = true;
    119  // Only continue traversing up the tree if it's possible that the parent
    120  // LocalAccessible's name (or a LocalAccessible being labelled by this
    121  // LocalAccessible or an ancestor) can depend on this LocalAccessible's name.
    122  LocalAccessible* parent = target;
    123  do {
    124    // Test possible name dependent parent.
    125    if (doName) {
    126      if (nameCheckAncestor && (maybeTargetNameChanged || parent != target) &&
    127          nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
    128        // HTML file inputs always get part of their name from the subtree, even
    129        // if the author provided a name.
    130        bool fireNameChange = parent->IsHTMLFileInput();
    131        if (!fireNameChange) {
    132          nsAutoString name;
    133          ENameValueFlag nameFlag = parent->DirectName(name);
    134          switch (nameFlag) {
    135            case eNameOK:
    136              // Descendants of subtree may have been removed, making the name
    137              // void.
    138              fireNameChange = name.IsVoid();
    139              break;
    140            case eNameFromSubtree:
    141              // If name is obtained from subtree, fire name change event.
    142              fireNameChange = true;
    143              break;
    144            case eNameFromTooltip:
    145              // If the descendants of this accessible were removed, the name
    146              // may be calculated using the tooltip. We can guess that the name
    147              // was obtained from the subtree before.
    148              fireNameChange = true;
    149              break;
    150            case eNameFromRelations:
    151              fireNameChange = true;
    152              break;
    153            default:
    154              MOZ_ASSERT_UNREACHABLE("All name flags not covered!");
    155          }
    156        }
    157 
    158        if (fireNameChange) {
    159          RefPtr<AccEvent> nameChangeEvent =
    160              new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
    161          pushed |= PushEvent(nameChangeEvent);
    162        }
    163        nameCheckAncestor = false;
    164      }
    165 
    166      pushed |= PushNameOrDescriptionChangeToRelations(parent,
    167                                                       RelationType::LABEL_FOR);
    168    }
    169 
    170    if (doDesc) {
    171      pushed |= PushNameOrDescriptionChangeToRelations(
    172          parent, RelationType::DESCRIPTION_FOR);
    173    }
    174 
    175    if (parent->IsDoc()) {
    176      // Never cross document boundaries.
    177      break;
    178    }
    179    parent = parent->LocalParent();
    180  } while (parent &&
    181           nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule));
    182 
    183  return pushed;
    184 }
    185 
    186 ////////////////////////////////////////////////////////////////////////////////
    187 // EventQueue: private
    188 
    189 void EventQueue::CoalesceEvents() {
    190  AUTO_PROFILER_MARKER_TEXT("EventQueue::CoalesceEvents", A11Y, {}, ""_ns);
    191  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_CoalesceEvents>
    192      autoRecording;
    193  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
    194 
    195  NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
    196  uint32_t tail = mEvents.Length() - 1;
    197  AccEvent* tailEvent = mEvents[tail];
    198 
    199  switch (tailEvent->mEventRule) {
    200    case AccEvent::eCoalesceReorder: {
    201      DebugOnly<LocalAccessible*> target = tailEvent->mAccessible.get();
    202      MOZ_ASSERT(
    203          target->IsApplication() || target->IsOuterDoc() ||
    204              target->IsXULTree(),
    205          "Only app or outerdoc accessible reorder events are in the queue");
    206      MOZ_ASSERT(tailEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER,
    207                 "only reorder events should be queued");
    208      break;  // case eCoalesceReorder
    209    }
    210 
    211    case AccEvent::eCoalesceOfSameType: {
    212      // Coalesce old events by newer event.
    213      for (uint32_t index = tail - 1; index < tail; index--) {
    214        AccEvent* accEvent = mEvents[index];
    215        if (accEvent->mEventType == tailEvent->mEventType &&
    216            accEvent->mEventRule == tailEvent->mEventRule) {
    217          accEvent->mEventRule = AccEvent::eDoNotEmit;
    218          return;
    219        }
    220      }
    221      break;  // case eCoalesceOfSameType
    222    }
    223 
    224    case AccEvent::eCoalesceSelectionChange: {
    225      AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
    226      for (uint32_t index = tail - 1; index < tail; index--) {
    227        AccEvent* thisEvent = mEvents[index];
    228        if (thisEvent->mEventRule == tailEvent->mEventRule) {
    229          AccSelChangeEvent* thisSelChangeEvent = downcast_accEvent(thisEvent);
    230 
    231          // Coalesce selection change events within same control.
    232          if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
    233            CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent,
    234                                    index);
    235            return;
    236          }
    237        }
    238      }
    239      break;  // eCoalesceSelectionChange
    240    }
    241 
    242    case AccEvent::eCoalesceStateChange: {
    243      // If state change event is duped then ignore previous event. If state
    244      // change event is opposite to previous event then no event is emitted
    245      // (accessible state wasn't changed).
    246      for (uint32_t index = tail - 1; index < tail; index--) {
    247        AccEvent* thisEvent = mEvents[index];
    248        if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
    249            thisEvent->mEventType == tailEvent->mEventType &&
    250            thisEvent->mAccessible == tailEvent->mAccessible) {
    251          AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
    252          AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
    253          if (thisSCEvent->mState == tailSCEvent->mState) {
    254            thisEvent->mEventRule = AccEvent::eDoNotEmit;
    255            if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled) {
    256              tailEvent->mEventRule = AccEvent::eDoNotEmit;
    257            }
    258          }
    259        }
    260      }
    261      break;  // eCoalesceStateChange
    262    }
    263 
    264    case AccEvent::eCoalesceTextSelChange: {
    265      // Coalesce older event by newer event for the same selection or target.
    266      // Events for same selection may have different targets and vice versa one
    267      // target may be pointed by different selections (for latter see
    268      // bug 927159).
    269      for (uint32_t index = tail - 1; index < tail; index--) {
    270        AccEvent* thisEvent = mEvents[index];
    271        if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
    272            thisEvent->mEventType == tailEvent->mEventType) {
    273          AccTextSelChangeEvent* thisTSCEvent = downcast_accEvent(thisEvent);
    274          AccTextSelChangeEvent* tailTSCEvent = downcast_accEvent(tailEvent);
    275          if (thisTSCEvent->mSel == tailTSCEvent->mSel ||
    276              thisEvent->mAccessible == tailEvent->mAccessible) {
    277            thisEvent->mEventRule = AccEvent::eDoNotEmit;
    278          }
    279        }
    280      }
    281      break;  // eCoalesceTextSelChange
    282    }
    283 
    284    default:
    285      // eRemoveDupes is handled in PushEvent.
    286      break;  // case eRemoveDupes, eAllowDupes, eDoNotEmit
    287  }  // switch
    288 }
    289 
    290 void EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
    291                                         AccSelChangeEvent* aThisEvent,
    292                                         uint32_t aThisIndex) {
    293  aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
    294 
    295  // Pack all preceding events into single selection within event
    296  // when we receive too much selection add/remove events.
    297  if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
    298    aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
    299    aTailEvent->mAccessible = aTailEvent->mWidget;
    300    aThisEvent->mEventRule = AccEvent::eDoNotEmit;
    301 
    302    // Do not emit any preceding selection events for same widget if they
    303    // weren't coalesced yet.
    304    if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
    305      for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
    306        AccEvent* prevEvent = mEvents[jdx];
    307        if (prevEvent->mEventRule == aTailEvent->mEventRule) {
    308          AccSelChangeEvent* prevSelChangeEvent = downcast_accEvent(prevEvent);
    309          if (prevSelChangeEvent->mWidget == aTailEvent->mWidget) {
    310            prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
    311          }
    312        }
    313      }
    314    }
    315    return;
    316  }
    317 
    318  // Pack sequential selection remove and selection add events into
    319  // single selection change event.
    320  if (aTailEvent->mPreceedingCount == 1 &&
    321      aTailEvent->mItem != aThisEvent->mItem) {
    322    if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
    323        aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
    324      aThisEvent->mEventRule = AccEvent::eDoNotEmit;
    325      aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
    326      aTailEvent->mPackedEvent = aThisEvent;
    327      return;
    328    }
    329 
    330    if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
    331        aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
    332      aTailEvent->mEventRule = AccEvent::eDoNotEmit;
    333      aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
    334      aThisEvent->mPackedEvent = aTailEvent;
    335      return;
    336    }
    337  }
    338 
    339  // Unpack the packed selection change event because we've got one
    340  // more selection add/remove.
    341  if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
    342    if (aThisEvent->mPackedEvent) {
    343      aThisEvent->mPackedEvent->mEventType =
    344          aThisEvent->mPackedEvent->mSelChangeType ==
    345                  AccSelChangeEvent::eSelectionAdd
    346              ? nsIAccessibleEvent::EVENT_SELECTION_ADD
    347              : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
    348 
    349      aThisEvent->mPackedEvent->mEventRule = AccEvent::eCoalesceSelectionChange;
    350 
    351      aThisEvent->mPackedEvent = nullptr;
    352    }
    353 
    354    aThisEvent->mEventType =
    355        aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd
    356            ? nsIAccessibleEvent::EVENT_SELECTION_ADD
    357            : nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
    358 
    359    return;
    360  }
    361 
    362  // Convert into selection add since control has single selection but other
    363  // selection events for this control are queued.
    364  if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
    365    aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
    366  }
    367 }
    368 
    369 ////////////////////////////////////////////////////////////////////////////////
    370 // EventQueue: event queue
    371 
    372 void EventQueue::ProcessEventQueue() {
    373  // Process only currently queued events.
    374  const nsTArray<RefPtr<AccEvent> > events = std::move(mEvents);
    375  nsTArray<uint64_t> selectedIDs;
    376  nsTArray<uint64_t> unselectedIDs;
    377 
    378  uint32_t eventCount = events.Length();
    379 #ifdef A11Y_LOG
    380  if ((eventCount > 0 || mFocusEvent) && logging::IsEnabled(logging::eEvents)) {
    381    logging::MsgBegin("EVENTS", "events processing");
    382    logging::Address("document", mDocument);
    383    logging::MsgEnd();
    384  }
    385 #endif
    386 
    387  if (mFocusEvent) {
    388    // Always fire a pending focus event before all other events. We do this for
    389    // two reasons:
    390    // 1. It prevents extraneous screen reader speech if the name, states, etc.
    391    // of the currently focused item change at the same time as another item is
    392    // focused. If aria-activedescendant is used, even setting
    393    // aria-activedescendant before changing other properties results in the
    394    // property change events being queued before the focus event because we
    395    // process  aria-activedescendant async.
    396    // 2. It improves perceived performance. Focus changes should be reported as
    397    // soon as possible, so clients should handle focus events before they spend
    398    // time on anything else.
    399    RefPtr<AccEvent> event = std::move(mFocusEvent);
    400    if (!event->mAccessible->IsDefunct()) {
    401      FocusMgr()->ProcessFocusEvent(event);
    402    }
    403  }
    404 
    405  for (uint32_t idx = 0; idx < eventCount; idx++) {
    406    AccEvent* event = events[idx];
    407    uint32_t eventType = event->mEventType;
    408    LocalAccessible* target = event->GetAccessible();
    409    if (!target || target->IsDefunct()) {
    410      continue;
    411    }
    412 
    413    // Collect select changes
    414    if (IPCAccessibilityActive()) {
    415      if ((event->mEventRule == AccEvent::eDoNotEmit &&
    416           (eventType == nsIAccessibleEvent::EVENT_SELECTION_ADD ||
    417            eventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE ||
    418            eventType == nsIAccessibleEvent::EVENT_SELECTION)) ||
    419          eventType == nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
    420        // The selection even was either dropped or morphed to a
    421        // selection-within. We need to collect the items from all these events
    422        // and manually push their new state to the parent process.
    423        AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
    424        LocalAccessible* item = selChangeEvent->mItem;
    425        if (!item->IsDefunct()) {
    426          uint64_t itemID =
    427              item->IsDoc() ? 0 : reinterpret_cast<uint64_t>(item->UniqueID());
    428          bool selected = selChangeEvent->mSelChangeType ==
    429                          AccSelChangeEvent::eSelectionAdd;
    430          if (selected) {
    431            selectedIDs.AppendElement(itemID);
    432          } else {
    433            unselectedIDs.AppendElement(itemID);
    434          }
    435        }
    436      }
    437    }
    438 
    439    if (event->mEventRule == AccEvent::eDoNotEmit) {
    440      continue;
    441    }
    442 
    443    // Dispatch caret moved and text selection change events.
    444    if (eventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
    445      SelectionMgr()->ProcessTextSelChangeEvent(event);
    446      continue;
    447    }
    448 
    449    // Fire selected state change events in support to selection events.
    450    if (eventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
    451      nsEventShell::FireEvent(event->mAccessible, states::SELECTED, true,
    452                              event->mIsFromUserInput);
    453 
    454    } else if (eventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
    455      nsEventShell::FireEvent(event->mAccessible, states::SELECTED, false,
    456                              event->mIsFromUserInput);
    457 
    458    } else if (eventType == nsIAccessibleEvent::EVENT_SELECTION) {
    459      AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
    460      nsEventShell::FireEvent(
    461          event->mAccessible, states::SELECTED,
    462          (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
    463          event->mIsFromUserInput);
    464 
    465      if (selChangeEvent->mPackedEvent) {
    466        nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
    467                                states::SELECTED,
    468                                (selChangeEvent->mPackedEvent->mSelChangeType ==
    469                                 AccSelChangeEvent::eSelectionAdd),
    470                                selChangeEvent->mPackedEvent->mIsFromUserInput);
    471      }
    472    }
    473 
    474    nsEventShell::FireEvent(event);
    475 
    476    if (!mDocument) {
    477      return;
    478    }
    479 
    480    // Some mutation events may be queued incidentally by this function. Send
    481    // them immediately so they stay in order. This can happen due to code in
    482    // DoInitialUpdate and TextUpdater that calls FireDelayedEvent for mutation
    483    // events, rather than QueueMutationEvent. DoInitialUpdate can do this with
    484    // reorder events, and TextUpdater can do this with text inserted/removed
    485    // events. Process these events now to avoid sending them out-of-order.
    486    if (eventType == nsIAccessibleEvent::EVENT_REORDER ||
    487        eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED ||
    488        eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED) {
    489      if (auto* ipcDoc = mDocument->IPCDoc()) {
    490        ipcDoc->SendQueuedMutationEvents();
    491      }
    492    }
    493  }
    494 
    495  if (mDocument && IPCAccessibilityActive() &&
    496      (!selectedIDs.IsEmpty() || !unselectedIDs.IsEmpty())) {
    497    DocAccessibleChild* ipcDoc = mDocument->IPCDoc();
    498    ipcDoc->SendSelectedAccessiblesChanged(selectedIDs, unselectedIDs);
    499  }
    500 }
    501 
    502 }  // namespace a11y
    503 }  // namespace mozilla