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