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