DocAccessibleParent.cpp (43967B)
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 "ARIAMap.h" 8 #include "CachedTableAccessible.h" 9 #include "DocAccessibleParent.h" 10 #include "mozilla/a11y/Platform.h" 11 #include "mozilla/Components.h" // for mozilla::components 12 #include "mozilla/dom/BrowserBridgeParent.h" 13 #include "mozilla/dom/BrowserParent.h" 14 #include "mozilla/dom/CanonicalBrowsingContext.h" 15 #include "mozilla/PerfStats.h" 16 #include "mozilla/ProfilerMarkers.h" 17 #include "nsAccessibilityService.h" 18 #include "xpcAccessibleDocument.h" 19 #include "xpcAccEvents.h" 20 #include "nsAccUtils.h" 21 #include "nsIIOService.h" 22 #include "TextRange.h" 23 #include "Relation.h" 24 #include "RootAccessible.h" 25 26 #if defined(XP_WIN) 27 # include "Compatibility.h" 28 # include "nsWinUtils.h" 29 #endif 30 31 #if defined(ANDROID) 32 # define ACQUIRE_ANDROID_LOCK \ 33 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); 34 #else 35 # define ACQUIRE_ANDROID_LOCK \ 36 do { \ 37 } while (0); 38 #endif 39 40 namespace mozilla { 41 42 namespace a11y { 43 uint64_t DocAccessibleParent::sMaxDocID = 0; 44 45 DocAccessibleParent::DocAccessibleParent() 46 : RemoteAccessible(this), 47 #if defined(XP_WIN) 48 mEmulatedWindowHandle(nullptr), 49 #endif // defined(XP_WIN) 50 mTopLevel(false), 51 mTopLevelInContentProcess(false), 52 mShutdown(false), 53 mFocus(0), 54 mCaretId(0), 55 mCaretOffset(-1), 56 mIsCaretAtEndOfLine(false) { 57 sMaxDocID++; 58 mActorID = sMaxDocID; 59 MOZ_ASSERT(!LiveDocs().Get(mActorID)); 60 LiveDocs().InsertOrUpdate(mActorID, this); 61 } 62 63 DocAccessibleParent::~DocAccessibleParent() { 64 UnregisterWeakMemoryReporter(this); 65 LiveDocs().Remove(mActorID); 66 MOZ_ASSERT(mChildDocs.Length() == 0); 67 MOZ_ASSERT(!ParentDoc()); 68 } 69 70 already_AddRefed<DocAccessibleParent> DocAccessibleParent::New() { 71 RefPtr<DocAccessibleParent> dap(new DocAccessibleParent()); 72 // We need to do this with a non-zero reference count. The easiest way is to 73 // do it in this static method and hide the constructor. 74 RegisterWeakMemoryReporter(dap); 75 return dap.forget(); 76 } 77 78 void DocAccessibleParent::SetBrowsingContext( 79 dom::CanonicalBrowsingContext* aBrowsingContext) { 80 mBrowsingContext = aBrowsingContext; 81 } 82 83 mozilla::ipc::IPCResult DocAccessibleParent::ProcessShowEvent( 84 nsTArray<AccessibleData>&& aNewTree, const bool& aEventSuppressed, 85 const bool& aComplete, const bool& aFromUser) { 86 AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessShowEvent", A11Y, {}, 87 ""_ns); 88 PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessShowEvent> 89 autoRecording; 90 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS. 91 92 ACQUIRE_ANDROID_LOCK 93 94 MOZ_ASSERT(CheckDocTree()); 95 96 if (aNewTree.IsEmpty()) { 97 return IPC_FAIL(this, "No children being added"); 98 } 99 100 RemoteAccessible* root = nullptr; 101 RemoteAccessible* rootParent = nullptr; 102 RemoteAccessible* lastParent = this; 103 uint64_t lastParentID = 0; 104 for (const auto& accData : aNewTree) { 105 // Avoid repeated hash lookups when there are multiple children of the same 106 // parent. 107 RemoteAccessible* parent = accData.ParentID() == lastParentID 108 ? lastParent 109 : GetAccessible(accData.ParentID()); 110 // XXX This should really never happen, but sometimes we fail to fire the 111 // required show events. 112 if (!parent) { 113 NS_ERROR("adding child to unknown accessible"); 114 #ifdef DEBUG 115 return IPC_FAIL(this, "unknown parent accessible"); 116 #else 117 return IPC_OK(); 118 #endif 119 } 120 lastParent = parent; 121 lastParentID = accData.ParentID(); 122 123 uint32_t childIdx = accData.IndexInParent(); 124 if (childIdx > parent->ChildCount()) { 125 NS_ERROR("invalid index to add child at"); 126 #ifdef DEBUG 127 return IPC_FAIL(this, "invalid index"); 128 #else 129 return IPC_OK(); 130 #endif 131 } 132 133 RemoteAccessible* child = CreateAcc(accData); 134 if (!child) { 135 // This shouldn't happen. 136 return IPC_FAIL(this, "failed to add children"); 137 } 138 if (!root && !mPendingShowChild) { 139 // This is the first Accessible, which is the root of the shown subtree. 140 root = child; 141 rootParent = parent; 142 } 143 // If this show event has been split across multiple messages and this is 144 // not the last message, don't attach the shown root to the tree yet. 145 // Otherwise, clients might crawl the incomplete subtree and they won't get 146 // mutation events for the remaining pieces. 147 if (aComplete || root != child) { 148 AttachChild(parent, childIdx, child); 149 } 150 } 151 152 MOZ_ASSERT(CheckDocTree()); 153 154 if (!aComplete && !mPendingShowChild) { 155 // This is the first message for a show event split across multiple 156 // messages. Save the show target for subsequent messages and return. 157 const auto& accData = aNewTree[0]; 158 mPendingShowChild = accData.ID(); 159 mPendingShowParent = accData.ParentID(); 160 mPendingShowIndex = accData.IndexInParent(); 161 return IPC_OK(); 162 } 163 if (!aComplete) { 164 // This show event has been split into multiple messages, but this is 165 // neither the first nor the last message. There's nothing more to do here. 166 return IPC_OK(); 167 } 168 MOZ_ASSERT(aComplete); 169 if (mPendingShowChild) { 170 // This is the last message for a show event split across multiple 171 // messages. Retrieve the saved show target, attach it to the tree and fire 172 // an event if appropriate. 173 rootParent = GetAccessible(mPendingShowParent); 174 MOZ_ASSERT(rootParent); 175 root = GetAccessible(mPendingShowChild); 176 MOZ_ASSERT(root); 177 AttachChild(rootParent, mPendingShowIndex, root); 178 mPendingShowChild = 0; 179 mPendingShowParent = 0; 180 mPendingShowIndex = 0; 181 } 182 183 // Just update, no events. 184 if (aEventSuppressed) { 185 return IPC_OK(); 186 } 187 188 { 189 // Scope for PerfStats 190 AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns); 191 PerfStats::AutoMetricRecording< 192 PerfStats::Metric::A11Y_PlatformShowHideEvent> 193 autoRecording; 194 // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK: 195 // THIS CODE IS MEASURING TIMINGS. 196 PlatformShowHideEvent(root, rootParent, true, aFromUser); 197 } 198 199 if (nsCOMPtr<nsIObserverService> obsService = 200 services::GetObserverService()) { 201 obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr); 202 } 203 204 if (!nsCoreUtils::AccEventObserversExist()) { 205 return IPC_OK(); 206 } 207 208 uint32_t type = nsIAccessibleEvent::EVENT_SHOW; 209 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root); 210 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 211 nsINode* node = nullptr; 212 RefPtr<xpcAccEvent> event = 213 new xpcAccEvent(type, xpcAcc, doc, node, aFromUser); 214 nsCoreUtils::DispatchAccEvent(std::move(event)); 215 216 return IPC_OK(); 217 } 218 219 RemoteAccessible* DocAccessibleParent::CreateAcc( 220 const AccessibleData& aAccData) { 221 RemoteAccessible* newProxy; 222 if ((newProxy = GetAccessible(aAccData.ID()))) { 223 // This is a move. Reuse the Accessible; don't destroy it. 224 if (newProxy->RemoteParent()) { 225 MOZ_ASSERT_UNREACHABLE( 226 "Attempt to move RemoteAccessible which still has a parent!"); 227 return nullptr; 228 } 229 return newProxy; 230 } 231 232 if (!aria::IsRoleMapIndexValid(aAccData.RoleMapEntryIndex())) { 233 MOZ_ASSERT_UNREACHABLE("Invalid role map entry index"); 234 return nullptr; 235 } 236 237 newProxy = new RemoteAccessible(aAccData.ID(), this, aAccData.Role(), 238 aAccData.Type(), aAccData.GenericTypes(), 239 aAccData.RoleMapEntryIndex()); 240 mAccessibles.PutEntry(aAccData.ID())->mProxy = newProxy; 241 242 if (RefPtr<AccAttributes> fields = aAccData.CacheFields()) { 243 newProxy->ApplyCache(CacheUpdateType::Initial, fields); 244 } 245 246 return newProxy; 247 } 248 249 void DocAccessibleParent::AttachChild(RemoteAccessible* aParent, 250 uint32_t aIndex, 251 RemoteAccessible* aChild) { 252 aParent->AddChildAt(aIndex, aChild); 253 aChild->SetParent(aParent); 254 // ProxyCreated might have already been called if aChild is being moved. 255 if (!aChild->GetWrapper()) { 256 ProxyCreated(aChild); 257 } 258 if (aChild->IsTableCell()) { 259 CachedTableAccessible::Invalidate(aChild); 260 } 261 if (aChild->IsOuterDoc()) { 262 // We can only do this after ProxyCreated is called because it will fire an 263 // event on aChild. 264 mPendingOOPChildDocs.RemoveIf([&](dom::BrowserBridgeParent* bridge) { 265 MOZ_ASSERT(bridge->GetBrowserParent(), 266 "Pending BrowserBridgeParent should be alive"); 267 if (bridge->GetEmbedderAccessibleId() != aChild->ID()) { 268 return false; 269 } 270 MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this); 271 if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) { 272 AddChildDoc(childDoc, aChild->ID(), false); 273 } 274 return true; 275 }); 276 } 277 } 278 279 void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) { 280 // Children might be removed or moved. Handle them the same way. We do this 281 // before checking the moving IDs set in order to ensure that we handle moved 282 // descendants properly. Avoid descending into the children of outer documents 283 // for moves since they are added and removed differently to normal children. 284 if (!aAcc->IsOuterDoc()) { 285 // Even if some children are kept, those will be re-attached when we handle 286 // the show event. For now, clear all of them by moving them to a temporary. 287 auto children{std::move(aAcc->mChildren)}; 288 for (RemoteAccessible* child : children) { 289 ShutdownOrPrepareForMove(child); 290 } 291 } 292 293 const uint64_t id = aAcc->ID(); 294 if (!mMovingIDs.Contains(id)) { 295 // This Accessible is being removed. 296 aAcc->Shutdown(); 297 return; 298 } 299 // This is a move. Moves are sent as a hide and then a show, but for a move, 300 // we want to keep the Accessible alive for reuse later. 301 if (aAcc->IsTable() || aAcc->IsTableCell()) { 302 // For table cells, it's important that we do this before the parent is 303 // cleared because CachedTableAccessible::Invalidate needs the ancestry. 304 CachedTableAccessible::Invalidate(aAcc); 305 } 306 if (aAcc->IsHyperText()) { 307 aAcc->InvalidateCachedHyperTextOffsets(); 308 } 309 aAcc->SetParent(nullptr); 310 mMovingIDs.EnsureRemoved(id); 311 } 312 313 mozilla::ipc::IPCResult DocAccessibleParent::ProcessHideEvent( 314 const uint64_t& aRootID, const bool& aFromUser) { 315 AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessHideEvent", A11Y, {}, 316 ""_ns); 317 PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessHideEvent> 318 autoRecording; 319 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS. 320 ACQUIRE_ANDROID_LOCK 321 322 MOZ_ASSERT(CheckDocTree()); 323 324 // We shouldn't actually need this because mAccessibles shouldn't have an 325 // entry for the document itself, but it doesn't hurt to be explicit. 326 if (!aRootID) { 327 return IPC_FAIL(this, "Trying to hide entire document?"); 328 } 329 330 ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID); 331 if (!rootEntry) { 332 NS_ERROR("invalid root being removed!"); 333 return IPC_OK(); 334 } 335 336 RemoteAccessible* root = rootEntry->mProxy; 337 if (!root) { 338 NS_ERROR("invalid root being removed!"); 339 return IPC_OK(); 340 } 341 342 RemoteAccessible* parent = root->RemoteParent(); 343 { 344 // Scope for PerfStats 345 AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns); 346 PerfStats::AutoMetricRecording< 347 PerfStats::Metric::A11Y_PlatformShowHideEvent> 348 autoRecording; 349 // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK: 350 // THIS CODE IS MEASURING TIMINGS. 351 PlatformShowHideEvent(root, parent, false, aFromUser); 352 } 353 354 RefPtr<xpcAccHideEvent> event = nullptr; 355 if (nsCoreUtils::AccEventObserversExist()) { 356 uint32_t type = nsIAccessibleEvent::EVENT_HIDE; 357 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root); 358 xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent); 359 RemoteAccessible* next = root->RemoteNextSibling(); 360 xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr; 361 RemoteAccessible* prev = root->RemotePrevSibling(); 362 xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr; 363 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 364 nsINode* node = nullptr; 365 event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent, 366 xpcNext, xpcPrev); 367 } 368 369 parent->RemoveChild(root); 370 ShutdownOrPrepareForMove(root); 371 372 MOZ_ASSERT(CheckDocTree()); 373 374 if (event) { 375 nsCoreUtils::DispatchAccEvent(std::move(event)); 376 } 377 378 return IPC_OK(); 379 } 380 381 mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent( 382 const uint64_t& aID, const uint32_t& aEventType) { 383 ACQUIRE_ANDROID_LOCK 384 if (mShutdown) { 385 return IPC_OK(); 386 } 387 if (aEventType == 0 || aEventType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) { 388 MOZ_ASSERT_UNREACHABLE("Invalid event"); 389 return IPC_FAIL(this, "Invalid event"); 390 } 391 392 RemoteAccessible* remote = GetAccessible(aID); 393 if (!remote) { 394 NS_ERROR("no proxy for event!"); 395 return IPC_OK(); 396 } 397 398 FireEvent(remote, aEventType); 399 return IPC_OK(); 400 } 401 402 void DocAccessibleParent::FireEvent(RemoteAccessible* aAcc, 403 const uint32_t& aEventType) { 404 if (aEventType == nsIAccessibleEvent::EVENT_REORDER || 405 aEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) { 406 uint32_t count = aAcc->ChildCount(); 407 for (uint32_t c = 0; c < count; ++c) { 408 aAcc->RemoteChildAt(c)->InvalidateGroupInfo(); 409 } 410 } else if (aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE && 411 aAcc == this) { 412 // A DocAccessible gets the STALE state while it is still loading, but we 413 // don't fire a state change for that. That state might have been 414 // included in the initial cache push, so clear it here. 415 // We also clear the BUSY state here. Although we do fire a state change 416 // for that, we fire it after doc load complete. It doesn't make sense 417 // for the document to report BUSY after doc load complete and doing so 418 // confuses JAWS. 419 UpdateStateCache(states::STALE | states::BUSY, false); 420 } 421 422 PlatformEvent(aAcc, aEventType); 423 424 if (!nsCoreUtils::AccEventObserversExist()) { 425 return; 426 } 427 428 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(aAcc); 429 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 430 nsINode* node = nullptr; 431 bool fromUser = true; // XXX fix me 432 RefPtr<xpcAccEvent> event = 433 new xpcAccEvent(aEventType, xpcAcc, doc, node, fromUser); 434 nsCoreUtils::DispatchAccEvent(std::move(event)); 435 } 436 437 mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent( 438 const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) { 439 ACQUIRE_ANDROID_LOCK 440 if (mShutdown) { 441 return IPC_OK(); 442 } 443 444 RemoteAccessible* target = GetAccessible(aID); 445 if (!target) { 446 NS_ERROR("we don't know about the target of a state change event!"); 447 return IPC_OK(); 448 } 449 450 target->UpdateStateCache(aState, aEnabled); 451 if (nsCOMPtr<nsIObserverService> obsService = 452 services::GetObserverService()) { 453 obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr); 454 } 455 PlatformStateChangeEvent(target, aState, aEnabled); 456 457 if (!nsCoreUtils::AccEventObserversExist()) { 458 return IPC_OK(); 459 } 460 461 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); 462 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 463 uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE; 464 bool extra; 465 uint32_t state = nsAccUtils::To32States(aState, &extra); 466 bool fromUser = true; // XXX fix this 467 nsINode* node = nullptr; // XXX can we do better? 468 RefPtr<xpcAccStateChangeEvent> event = new xpcAccStateChangeEvent( 469 type, xpcAcc, doc, node, fromUser, state, extra, aEnabled); 470 nsCoreUtils::DispatchAccEvent(std::move(event)); 471 472 return IPC_OK(); 473 } 474 475 mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent( 476 const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect, 477 const int32_t& aOffset, const bool& aIsSelectionCollapsed, 478 const bool& aIsAtEndOfLine, const int32_t& aGranularity, 479 const bool& aFromUser) { 480 ACQUIRE_ANDROID_LOCK 481 if (mShutdown) { 482 return IPC_OK(); 483 } 484 485 RemoteAccessible* proxy = GetAccessible(aID); 486 if (!proxy) { 487 NS_ERROR("unknown caret move event target!"); 488 return IPC_OK(); 489 } 490 491 mCaretId = aID; 492 mCaretOffset = aOffset; 493 mIsCaretAtEndOfLine = aIsAtEndOfLine; 494 mCaretRect = aCaretRect; 495 if (aIsSelectionCollapsed) { 496 // We don't fire selection events for collapsed selections, but we need to 497 // ensure we don't have a stale cached selection; e.g. when selecting 498 // forward and then unselecting backward. 499 mTextSelections.ClearAndRetainStorage(); 500 mTextSelections.AppendElement(TextRangeData(aID, aID, aOffset, aOffset)); 501 } 502 503 PlatformCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed, aGranularity, 504 aFromUser); 505 506 if (!nsCoreUtils::AccEventObserversExist()) { 507 return IPC_OK(); 508 } 509 510 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy); 511 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 512 nsINode* node = nullptr; 513 bool fromUser = true; // XXX fix me 514 uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED; 515 RefPtr<xpcAccCaretMoveEvent> event = new xpcAccCaretMoveEvent( 516 type, xpcAcc, doc, node, fromUser, aOffset, aIsSelectionCollapsed, 517 aIsAtEndOfLine, aGranularity); 518 nsCoreUtils::DispatchAccEvent(std::move(event)); 519 520 return IPC_OK(); 521 } 522 523 mozilla::ipc::IPCResult DocAccessibleParent::ProcessTextChangeEvent( 524 const uint64_t& aID, const nsAString& aStr, const int32_t& aStart, 525 const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) { 526 ACQUIRE_ANDROID_LOCK 527 528 RemoteAccessible* target = GetAccessible(aID); 529 if (!target) { 530 NS_ERROR("text change event target is unknown!"); 531 return IPC_OK(); 532 } 533 534 PlatformTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser); 535 536 if (!nsCoreUtils::AccEventObserversExist()) { 537 return IPC_OK(); 538 } 539 540 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); 541 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 542 uint32_t type = aIsInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED 543 : nsIAccessibleEvent::EVENT_TEXT_REMOVED; 544 nsINode* node = nullptr; 545 RefPtr<xpcAccTextChangeEvent> event = new xpcAccTextChangeEvent( 546 type, xpcAcc, doc, node, aFromUser, aStart, aLen, aIsInsert, aStr); 547 nsCoreUtils::DispatchAccEvent(std::move(event)); 548 549 return IPC_OK(); 550 } 551 552 mozilla::ipc::IPCResult DocAccessibleParent::RecvMutationEvents( 553 nsTArray<MutationEventData>&& aData) { 554 // We do not use ACQUIRE_ANDROID_LOCK here since we call functions that do 555 // that for us. The lock is not re-entrant. 556 mozilla::ipc::IPCResult result = IPC_OK(); 557 if (mShutdown) { 558 return result; 559 } 560 for (MutationEventData& data : aData) { 561 switch (data.type()) { 562 case MutationEventData::Type::TCacheEventData: { 563 CacheEventData& cacheEventData = data; 564 result = RecvCache(cacheEventData.UpdateType(), 565 std::move(cacheEventData.aData())); 566 break; 567 } 568 case MutationEventData::Type::TReorderEventData: { 569 ReorderEventData& reorderEventData = data; 570 result = RecvEvent(reorderEventData.ID(), reorderEventData.Type()); 571 break; 572 } 573 case MutationEventData::Type::THideEventData: { 574 HideEventData& hideEventData = data; 575 result = ProcessHideEvent(hideEventData.ID(), 576 hideEventData.IsFromUserInput()); 577 break; 578 } 579 case MutationEventData::Type::TShowEventData: { 580 ShowEventData& showEventData = data; 581 result = ProcessShowEvent( 582 std::move(showEventData.NewTree()), showEventData.EventSuppressed(), 583 showEventData.Complete(), showEventData.FromUser()); 584 break; 585 } 586 case MutationEventData::Type::TTextChangeEventData: { 587 TextChangeEventData& textChangeEventData = data; 588 result = ProcessTextChangeEvent( 589 textChangeEventData.ID(), textChangeEventData.Str(), 590 textChangeEventData.Start(), textChangeEventData.Len(), 591 textChangeEventData.IsInsert(), textChangeEventData.FromUser()); 592 break; 593 } 594 default: 595 break; 596 } 597 if (!result) { 598 return result; 599 } 600 } 601 602 return IPC_OK(); 603 } 604 605 mozilla::ipc::IPCResult DocAccessibleParent::RecvRequestAckMutationEvents() { 606 if (!mShutdown) { 607 (void)SendAckMutationEvents(); 608 } 609 return IPC_OK(); 610 } 611 612 mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent( 613 const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) { 614 ACQUIRE_ANDROID_LOCK 615 if (mShutdown) { 616 return IPC_OK(); 617 } 618 if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) { 619 MOZ_ASSERT_UNREACHABLE("Invalid event"); 620 return IPC_FAIL(this, "Invalid event"); 621 } 622 623 RemoteAccessible* target = GetAccessible(aID); 624 RemoteAccessible* widget = GetAccessible(aWidgetID); 625 if (!target || !widget) { 626 NS_ERROR("invalid id in selection event"); 627 return IPC_OK(); 628 } 629 630 PlatformSelectionEvent(target, widget, aType); 631 if (!nsCoreUtils::AccEventObserversExist()) { 632 return IPC_OK(); 633 } 634 xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target); 635 xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this); 636 RefPtr<xpcAccEvent> event = 637 new xpcAccEvent(aType, xpcTarget, xpcDoc, nullptr, false); 638 nsCoreUtils::DispatchAccEvent(std::move(event)); 639 640 return IPC_OK(); 641 } 642 643 mozilla::ipc::IPCResult DocAccessibleParent::RecvScrollingEvent( 644 const uint64_t& aID, const uint64_t& aType, const uint32_t& aScrollX, 645 const uint32_t& aScrollY, const uint32_t& aMaxScrollX, 646 const uint32_t& aMaxScrollY) { 647 ACQUIRE_ANDROID_LOCK 648 if (mShutdown) { 649 return IPC_OK(); 650 } 651 if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) { 652 MOZ_ASSERT_UNREACHABLE("Invalid event"); 653 return IPC_FAIL(this, "Invalid event"); 654 } 655 656 RemoteAccessible* target = GetAccessible(aID); 657 if (!target) { 658 NS_ERROR("no proxy for event!"); 659 return IPC_OK(); 660 } 661 662 #if defined(ANDROID) 663 PlatformScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX, 664 aMaxScrollY); 665 #else 666 PlatformEvent(target, aType); 667 #endif 668 669 if (!nsCoreUtils::AccEventObserversExist()) { 670 return IPC_OK(); 671 } 672 673 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); 674 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 675 nsINode* node = nullptr; 676 bool fromUser = true; // XXX: Determine if this was from user input. 677 RefPtr<xpcAccScrollingEvent> event = 678 new xpcAccScrollingEvent(aType, xpcAcc, doc, node, fromUser, aScrollX, 679 aScrollY, aMaxScrollX, aMaxScrollY); 680 nsCoreUtils::DispatchAccEvent(std::move(event)); 681 682 return IPC_OK(); 683 } 684 685 mozilla::ipc::IPCResult DocAccessibleParent::RecvCache( 686 const mozilla::a11y::CacheUpdateType& aUpdateType, 687 nsTArray<CacheData>&& aData) { 688 AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::RecvCache", A11Y, {}, ""_ns); 689 PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_RecvCache> 690 autoRecording; 691 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS. 692 693 ACQUIRE_ANDROID_LOCK 694 if (mShutdown) { 695 return IPC_OK(); 696 } 697 698 for (auto& entry : aData) { 699 RemoteAccessible* remote = GetAccessible(entry.ID()); 700 if (!remote) { 701 MOZ_ASSERT_UNREACHABLE("No remote found!"); 702 continue; 703 } 704 705 remote->ApplyCache(aUpdateType, entry.Fields()); 706 } 707 708 if (nsCOMPtr<nsIObserverService> obsService = 709 services::GetObserverService()) { 710 obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr); 711 } 712 713 return IPC_OK(); 714 } 715 716 mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectedAccessiblesChanged( 717 nsTArray<uint64_t>&& aSelectedIDs, nsTArray<uint64_t>&& aUnselectedIDs) { 718 ACQUIRE_ANDROID_LOCK 719 if (mShutdown) { 720 return IPC_OK(); 721 } 722 723 for (auto& id : aSelectedIDs) { 724 RemoteAccessible* remote = GetAccessible(id); 725 if (!remote) { 726 MOZ_ASSERT_UNREACHABLE("No remote found!"); 727 continue; 728 } 729 730 remote->UpdateStateCache(states::SELECTED, true); 731 } 732 733 for (auto& id : aUnselectedIDs) { 734 RemoteAccessible* remote = GetAccessible(id); 735 if (!remote) { 736 MOZ_ASSERT_UNREACHABLE("No remote found!"); 737 continue; 738 } 739 740 remote->UpdateStateCache(states::SELECTED, false); 741 } 742 743 if (nsCOMPtr<nsIObserverService> obsService = 744 services::GetObserverService()) { 745 obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr); 746 } 747 748 return IPC_OK(); 749 } 750 751 mozilla::ipc::IPCResult DocAccessibleParent::RecvAccessiblesWillMove( 752 nsTArray<uint64_t>&& aIDs) { 753 for (uint64_t id : aIDs) { 754 mMovingIDs.EnsureInserted(id); 755 } 756 return IPC_OK(); 757 } 758 759 #if !defined(XP_WIN) 760 mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent( 761 const uint64_t& aID, const nsAString& aAnnouncement, 762 const uint16_t& aPriority) { 763 ACQUIRE_ANDROID_LOCK 764 if (mShutdown) { 765 return IPC_OK(); 766 } 767 768 RemoteAccessible* target = GetAccessible(aID); 769 if (!target) { 770 NS_ERROR("no proxy for event!"); 771 return IPC_OK(); 772 } 773 774 # if defined(ANDROID) 775 PlatformAnnouncementEvent(target, aAnnouncement, aPriority); 776 # endif 777 778 if (!nsCoreUtils::AccEventObserversExist()) { 779 return IPC_OK(); 780 } 781 782 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); 783 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 784 RefPtr<xpcAccAnnouncementEvent> event = new xpcAccAnnouncementEvent( 785 nsIAccessibleEvent::EVENT_ANNOUNCEMENT, xpcAcc, doc, nullptr, false, 786 aAnnouncement, aPriority); 787 nsCoreUtils::DispatchAccEvent(std::move(event)); 788 789 return IPC_OK(); 790 } 791 #endif // !defined(XP_WIN) 792 793 mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent( 794 const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) { 795 ACQUIRE_ANDROID_LOCK 796 if (mShutdown) { 797 return IPC_OK(); 798 } 799 800 RemoteAccessible* target = GetAccessible(aID); 801 if (!target) { 802 NS_ERROR("no proxy for event!"); 803 return IPC_OK(); 804 } 805 806 mTextSelections.ClearAndRetainStorage(); 807 mTextSelections.AppendElements(aSelection); 808 809 #ifdef MOZ_WIDGET_COCOA 810 AutoTArray<TextRange, 1> ranges; 811 SelectionRanges(&ranges); 812 PlatformTextSelectionChangeEvent(target, ranges); 813 #else 814 PlatformEvent(target, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED); 815 #endif 816 817 if (!nsCoreUtils::AccEventObserversExist()) { 818 return IPC_OK(); 819 } 820 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target); 821 xpcAccessibleDocument* doc = nsAccessibilityService::GetXPCDocument(this); 822 nsINode* node = nullptr; 823 bool fromUser = true; // XXX fix me 824 RefPtr<xpcAccEvent> event = 825 new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, xpcAcc, 826 doc, node, fromUser); 827 nsCoreUtils::DispatchAccEvent(std::move(event)); 828 829 return IPC_OK(); 830 } 831 832 mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent( 833 const a11y::role& aRole, const uint8_t& aRoleMapEntryIndex) { 834 ACQUIRE_ANDROID_LOCK 835 if (mShutdown) { 836 return IPC_OK(); 837 } 838 if (!aria::IsRoleMapIndexValid(aRoleMapEntryIndex)) { 839 MOZ_ASSERT_UNREACHABLE("Invalid role map entry index"); 840 return IPC_FAIL(this, "Invalid role map entry index"); 841 } 842 843 mNativeRole = aRole; 844 mRoleMapEntryIndex = aRoleMapEntryIndex; 845 846 #ifdef MOZ_WIDGET_COCOA 847 PlatformRoleChangedEvent(this, aRole, aRoleMapEntryIndex); 848 #endif 849 850 return IPC_OK(); 851 } 852 853 mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc( 854 NotNull<PDocAccessibleParent*> aChildDoc, const uint64_t& aID) { 855 ACQUIRE_ANDROID_LOCK 856 // One document should never directly be the child of another. 857 // We should always have at least an outer doc accessible in between. 858 MOZ_ASSERT(aID); 859 if (!aID) return IPC_FAIL(this, "ID is 0!"); 860 861 if (mShutdown) { 862 return IPC_OK(); 863 } 864 865 MOZ_ASSERT(CheckDocTree()); 866 867 auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc.get()); 868 childDoc->Unbind(); 869 ipc::IPCResult result = AddChildDoc(childDoc, aID, false); 870 MOZ_ASSERT(result); 871 MOZ_ASSERT(CheckDocTree()); 872 #ifdef DEBUG 873 if (!result) { 874 return result; 875 } 876 #else 877 result = IPC_OK(); 878 #endif 879 880 return result; 881 } 882 883 ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc, 884 uint64_t aParentID, 885 bool aCreating) { 886 // We do not use GetAccessible here because we want to be sure to not get the 887 // document it self. 888 ProxyEntry* e = mAccessibles.GetEntry(aParentID); 889 if (!e) { 890 #ifndef FUZZING_SNAPSHOT 891 // This diagnostic assert and the one down below expect a well-behaved 892 // child process. In IPC fuzzing, we directly fuzz parameters of each 893 // method over IPDL and the asserts are not valid under these conditions. 894 MOZ_DIAGNOSTIC_CRASH("Binding to nonexistent proxy!"); 895 #endif 896 return IPC_FAIL(this, "binding to nonexistant proxy!"); 897 } 898 899 RemoteAccessible* outerDoc = e->mProxy; 900 MOZ_ASSERT(outerDoc); 901 902 // OuterDocAccessibles are expected to only have a document as a child. 903 // However for compatibility we tolerate replacing one document with another 904 // here. 905 if (!outerDoc->IsOuterDoc() || outerDoc->ChildCount() > 1 || 906 (outerDoc->ChildCount() == 1 && !outerDoc->RemoteChildAt(0)->IsDoc())) { 907 #ifndef FUZZING_SNAPSHOT 908 MOZ_DIAGNOSTIC_CRASH("Binding to parent that isn't a valid OuterDoc!"); 909 #endif 910 return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!"); 911 } 912 913 if (outerDoc->ChildCount() == 1) { 914 MOZ_ASSERT(outerDoc->RemoteChildAt(0)->AsDoc()); 915 outerDoc->RemoteChildAt(0)->AsDoc()->Unbind(); 916 } 917 918 aChildDoc->SetParent(outerDoc); 919 outerDoc->SetChildDoc(aChildDoc); 920 mChildDocs.AppendElement(aChildDoc->mActorID); 921 922 if (aCreating) { 923 ProxyCreated(aChildDoc); 924 } 925 926 if (aChildDoc->IsTopLevelInContentProcess()) { 927 // aChildDoc is an embedded document in a different content process to 928 // this document. 929 #if defined(XP_WIN) 930 if (nsWinUtils::IsWindowEmulationStarted()) { 931 aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle); 932 } 933 #endif // defined(XP_WIN) 934 // We need to fire a reorder event on the outer doc accessible. 935 // For same-process documents, this is fired by the content process, but 936 // this isn't possible when the document is in a different process to its 937 // embedder. 938 // FireEvent fires both OS and XPCOM events. 939 FireEvent(outerDoc, nsIAccessibleEvent::EVENT_REORDER); 940 } 941 942 return IPC_OK(); 943 } 944 945 ipc::IPCResult DocAccessibleParent::AddChildDoc( 946 dom::BrowserBridgeParent* aBridge) { 947 MOZ_ASSERT(aBridge->GetEmbedderAccessibleDoc() == this); 948 uint64_t parentId = aBridge->GetEmbedderAccessibleId(); 949 MOZ_ASSERT(parentId); 950 if (!mAccessibles.GetEntry(parentId)) { 951 // Sometimes, this gets called before the embedder sends us the 952 // OuterDocAccessible. We must add the child when the OuterDocAccessible 953 // gets created later. 954 mPendingOOPChildDocs.Insert(aBridge); 955 return IPC_OK(); 956 } 957 return AddChildDoc(aBridge->GetDocAccessibleParent(), parentId, 958 /* aCreating */ false); 959 } 960 961 mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() { 962 ACQUIRE_ANDROID_LOCK 963 Destroy(); 964 965 auto mgr = static_cast<dom::BrowserParent*>(Manager()); 966 if (!mgr->IsDestroyed()) { 967 if (!PDocAccessibleParent::Send__delete__(this)) { 968 return IPC_FAIL_NO_REASON(mgr); 969 } 970 } 971 972 return IPC_OK(); 973 } 974 975 void DocAccessibleParent::Destroy() { 976 // If we are already shutdown that is because our containing tab parent is 977 // shutting down in which case we don't need to do anything. 978 if (mShutdown) { 979 return; 980 } 981 982 mShutdown = true; 983 mBrowsingContext = nullptr; 984 985 #ifdef ANDROID 986 if (FocusMgr() && FocusMgr()->IsFocusedRemoteDoc(this)) { 987 FocusMgr()->SetFocusedRemoteDoc(nullptr); 988 } 989 #endif 990 991 MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID)); 992 uint32_t childDocCount = mChildDocs.Length(); 993 for (uint32_t i = 0; i < childDocCount; i++) { 994 for (uint32_t j = i + 1; j < childDocCount; j++) { 995 MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]); 996 } 997 } 998 999 // XXX This indirection through the hash map of live documents shouldn't be 1000 // needed, but be paranoid for now. 1001 int32_t actorID = mActorID; 1002 for (uint32_t i = childDocCount - 1; i < childDocCount; i--) { 1003 DocAccessibleParent* thisDoc = LiveDocs().Get(actorID); 1004 MOZ_ASSERT(thisDoc); 1005 if (!thisDoc) { 1006 return; 1007 } 1008 1009 thisDoc->ChildDocAt(i)->Destroy(); 1010 } 1011 1012 for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) { 1013 RemoteAccessible* acc = iter.Get()->mProxy; 1014 MOZ_ASSERT(acc != this); 1015 if (acc->IsTable()) { 1016 CachedTableAccessible::Invalidate(acc); 1017 } 1018 ProxyDestroyed(acc); 1019 // mAccessibles owns acc, so removing it deletes acc. 1020 iter.Remove(); 1021 } 1022 1023 DocAccessibleParent* thisDoc = LiveDocs().Get(actorID); 1024 MOZ_ASSERT(thisDoc); 1025 if (!thisDoc) { 1026 return; 1027 } 1028 1029 mChildren.Clear(); 1030 // The code above should have already completely cleared these, but to be 1031 // extra safe make sure they are cleared here. 1032 thisDoc->mAccessibles.Clear(); 1033 thisDoc->mChildDocs.Clear(); 1034 1035 DocManager::NotifyOfRemoteDocShutdown(thisDoc); 1036 thisDoc = LiveDocs().Get(actorID); 1037 MOZ_ASSERT(thisDoc); 1038 if (!thisDoc) { 1039 return; 1040 } 1041 1042 ProxyDestroyed(thisDoc); 1043 thisDoc = LiveDocs().Get(actorID); 1044 MOZ_ASSERT(thisDoc); 1045 if (!thisDoc) { 1046 return; 1047 } 1048 1049 if (IsTopLevel()) { 1050 GetAccService()->RemoteDocShutdown(this); 1051 } else { 1052 Unbind(); 1053 } 1054 } 1055 1056 void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy) { 1057 MOZ_ASSERT(CheckDocTree()); 1058 if (!mShutdown) { 1059 ACQUIRE_ANDROID_LOCK 1060 Destroy(); 1061 } 1062 } 1063 1064 DocAccessibleParent* DocAccessibleParent::ParentDoc() const { 1065 if (RemoteAccessible* parent = RemoteParent()) { 1066 return parent->Document(); 1067 } 1068 return nullptr; 1069 } 1070 1071 bool DocAccessibleParent::CheckDocTree() const { 1072 size_t childDocs = mChildDocs.Length(); 1073 for (size_t i = 0; i < childDocs; i++) { 1074 const DocAccessibleParent* childDoc = ChildDocAt(i); 1075 if (!childDoc || childDoc->ParentDoc() != this) return false; 1076 1077 if (!childDoc->CheckDocTree()) { 1078 return false; 1079 } 1080 } 1081 1082 return true; 1083 } 1084 1085 xpcAccessibleGeneric* DocAccessibleParent::GetXPCAccessible( 1086 RemoteAccessible* aProxy) { 1087 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 1088 MOZ_ASSERT(doc); 1089 1090 return doc->GetAccessible(aProxy); 1091 } 1092 1093 #if defined(XP_WIN) 1094 void DocAccessibleParent::MaybeInitWindowEmulation() { 1095 if (!nsWinUtils::IsWindowEmulationStarted()) { 1096 return; 1097 } 1098 1099 // XXX get the bounds from the browserParent instead of poking at accessibles 1100 // which might not exist yet. 1101 LocalAccessible* outerDoc = OuterDocOfRemoteBrowser(); 1102 if (!outerDoc) { 1103 return; 1104 } 1105 1106 RootAccessible* rootDocument = outerDoc->RootAccessible(); 1107 MOZ_ASSERT(rootDocument); 1108 1109 bool isActive = true; 1110 LayoutDeviceIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0); 1111 if (Compatibility::IsDolphin()) { 1112 rect = Bounds(); 1113 LayoutDeviceIntRect rootRect = rootDocument->Bounds(); 1114 rect.MoveToX(rootRect.X() - rect.X()); 1115 rect.MoveToY(rect.Y() - rootRect.Y()); 1116 1117 auto browserParent = static_cast<dom::BrowserParent*>(Manager()); 1118 isActive = browserParent->GetDocShellIsActive(); 1119 } 1120 1121 // onCreate is guaranteed to be called synchronously by 1122 // nsWinUtils::CreateNativeWindow, so this reference isn't really necessary. 1123 // However, static analysis complains without it. 1124 RefPtr<DocAccessibleParent> thisRef = this; 1125 nsWinUtils::NativeWindowCreateProc onCreate([thisRef](HWND aHwnd) -> void { 1126 ::SetPropW(aHwnd, kPropNameDocAccParent, 1127 reinterpret_cast<HANDLE>(thisRef.get())); 1128 thisRef->SetEmulatedWindowHandle(aHwnd); 1129 }); 1130 1131 HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow()); 1132 DebugOnly<HWND> hWnd = nsWinUtils::CreateNativeWindow( 1133 kClassNameTabContent, parentWnd, rect.X(), rect.Y(), rect.Width(), 1134 rect.Height(), isActive, &onCreate); 1135 MOZ_ASSERT(hWnd); 1136 } 1137 1138 void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) { 1139 if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) { 1140 ::DestroyWindow(mEmulatedWindowHandle); 1141 } 1142 mEmulatedWindowHandle = aWindowHandle; 1143 } 1144 #endif // defined(XP_WIN) 1145 1146 mozilla::ipc::IPCResult DocAccessibleParent::RecvFocusEvent( 1147 const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) { 1148 ACQUIRE_ANDROID_LOCK 1149 if (mShutdown) { 1150 return IPC_OK(); 1151 } 1152 1153 RemoteAccessible* proxy = GetAccessible(aID); 1154 if (!proxy) { 1155 NS_ERROR("no proxy for event!"); 1156 return IPC_OK(); 1157 } 1158 1159 #ifdef ANDROID 1160 if (FocusMgr()) { 1161 FocusMgr()->SetFocusedRemoteDoc(this); 1162 } 1163 #endif 1164 1165 mFocus = aID; 1166 mCaretRect = aCaretRect; 1167 PlatformFocusEvent(proxy); 1168 1169 if (!nsCoreUtils::AccEventObserversExist()) { 1170 return IPC_OK(); 1171 } 1172 1173 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy); 1174 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); 1175 nsINode* node = nullptr; 1176 bool fromUser = true; // XXX fix me 1177 RefPtr<xpcAccEvent> event = new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS, 1178 xpcAcc, doc, node, fromUser); 1179 nsCoreUtils::DispatchAccEvent(std::move(event)); 1180 1181 return IPC_OK(); 1182 } 1183 1184 LayoutDeviceIntRect DocAccessibleParent::GetCachedCaretRect() { 1185 LayoutDeviceIntRect caretRect = mCaretRect; 1186 if (!caretRect.IsEmpty()) { 1187 // Reapply doc offset to the caret rect. 1188 LayoutDeviceIntRect docRect = Bounds(); 1189 caretRect.MoveBy(docRect.X(), docRect.Y()); 1190 } 1191 1192 return caretRect; 1193 } 1194 1195 void DocAccessibleParent::SelectionRanges(nsTArray<TextRange>* aRanges) const { 1196 aRanges->SetCapacity(mTextSelections.Length()); 1197 for (const auto& data : mTextSelections) { 1198 // Selection ranges should usually be in sync with the tree. However, tree 1199 // and selection updates happen using separate IPDL calls, so it's possible 1200 // for a client selection query to arrive between them. Thus, we validate 1201 // the Accessibles and offsets here. 1202 auto* startAcc = 1203 const_cast<RemoteAccessible*>(GetAccessible(data.StartID())); 1204 auto* endAcc = const_cast<RemoteAccessible*>(GetAccessible(data.EndID())); 1205 if (!startAcc || !endAcc) { 1206 continue; 1207 } 1208 // Offset 0 is always valid, even if the container is empty. 1209 if (data.StartOffset() > 0) { 1210 uint32_t startCount = startAcc->CharacterCount(); 1211 if (data.StartOffset() > static_cast<int32_t>(startCount)) { 1212 continue; 1213 } 1214 } 1215 if (data.EndOffset() > 0) { 1216 uint32_t endCount = endAcc->CharacterCount(); 1217 if (data.EndOffset() > static_cast<int32_t>(endCount)) { 1218 continue; 1219 } 1220 } 1221 aRanges->AppendElement(TextRange(const_cast<DocAccessibleParent*>(this), 1222 startAcc, data.StartOffset(), endAcc, 1223 data.EndOffset())); 1224 } 1225 } 1226 1227 Accessible* DocAccessibleParent::FocusedChild() { 1228 LocalAccessible* outerDoc = OuterDocOfRemoteBrowser(); 1229 if (!outerDoc) { 1230 return nullptr; 1231 } 1232 1233 RootAccessible* rootDocument = outerDoc->RootAccessible(); 1234 return rootDocument->FocusedChild(); 1235 } 1236 1237 void DocAccessibleParent::URL(nsACString& aURL) const { 1238 if (!mBrowsingContext) { 1239 return; 1240 } 1241 nsCOMPtr<nsIURI> uri = mBrowsingContext->GetCurrentURI(); 1242 if (!uri) { 1243 return; 1244 } 1245 // Let's avoid treating too long URI in the main process for avoiding 1246 // memory fragmentation as far as possible. 1247 if (uri->SchemeIs("data") || uri->SchemeIs("blob")) { 1248 return; 1249 } 1250 nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service(); 1251 if (NS_WARN_IF(!io)) { 1252 return; 1253 } 1254 nsCOMPtr<nsIURI> exposableURI; 1255 if (NS_FAILED(io->CreateExposableURI(uri, getter_AddRefs(exposableURI))) || 1256 MOZ_UNLIKELY(!exposableURI)) { 1257 return; 1258 } 1259 exposableURI->GetSpec(aURL); 1260 } 1261 1262 void DocAccessibleParent::URL(nsAString& aURL) const { 1263 nsAutoCString url; 1264 URL(url); 1265 CopyUTF8toUTF16(url, aURL); 1266 } 1267 1268 void DocAccessibleParent::MimeType(nsAString& aMime) const { 1269 if (mCachedFields) { 1270 mCachedFields->GetAttribute(CacheKey::MimeType, aMime); 1271 } 1272 } 1273 1274 Relation DocAccessibleParent::RelationByType(RelationType aType) const { 1275 // If the accessible is top-level, provide the NODE_CHILD_OF relation so that 1276 // MSAA clients can easily get to true parent instead of getting to oleacc's 1277 // ROLE_WINDOW accessible when window emulation is enabled which will prevent 1278 // us from going up further (because it is system generated and has no idea 1279 // about the hierarchy above it). 1280 if (aType == RelationType::NODE_CHILD_OF && IsTopLevel()) { 1281 return Relation(Parent()); 1282 } 1283 1284 return RemoteAccessible::RelationByType(aType); 1285 } 1286 1287 DocAccessibleParent* DocAccessibleParent::GetFrom( 1288 dom::BrowsingContext* aBrowsingContext) { 1289 if (!aBrowsingContext) { 1290 return nullptr; 1291 } 1292 1293 dom::BrowserParent* bp = aBrowsingContext->Canonical()->GetBrowserParent(); 1294 if (!bp) { 1295 return nullptr; 1296 } 1297 1298 const ManagedContainer<PDocAccessibleParent>& docs = 1299 bp->ManagedPDocAccessibleParent(); 1300 for (auto* key : docs) { 1301 // Iterate over our docs until we find one with a browsing 1302 // context that matches the one we passed in. Return that 1303 // document. 1304 auto* doc = static_cast<a11y::DocAccessibleParent*>(key); 1305 if (doc->GetBrowsingContext() == aBrowsingContext) { 1306 return doc; 1307 } 1308 } 1309 1310 return nullptr; 1311 } 1312 1313 size_t DocAccessibleParent::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { 1314 size_t size = 0; 1315 1316 size += RemoteAccessible::SizeOfExcludingThis(aMallocSizeOf); 1317 1318 size += mReverseRelations.ShallowSizeOfExcludingThis(aMallocSizeOf); 1319 for (auto i = mReverseRelations.Iter(); !i.Done(); i.Next()) { 1320 size += i.Data().ShallowSizeOfExcludingThis(aMallocSizeOf); 1321 for (auto j = i.Data().Iter(); !j.Done(); j.Next()) { 1322 size += j.Data().ShallowSizeOfExcludingThis(aMallocSizeOf); 1323 } 1324 } 1325 1326 size += mOnScreenAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf); 1327 1328 size += mChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf); 1329 1330 size += mAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf); 1331 for (auto i = mAccessibles.Iter(); !i.Done(); i.Next()) { 1332 size += i.Get()->mProxy->SizeOfIncludingThis(aMallocSizeOf); 1333 } 1334 1335 size += mPendingOOPChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf); 1336 1337 // The mTextSelections array contains structs of integers. We can count them 1338 // by counting the size of the array - there's no deep structure here. 1339 size += mTextSelections.ShallowSizeOfExcludingThis(aMallocSizeOf); 1340 1341 return size; 1342 } 1343 1344 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOfAccessibilityCache); 1345 1346 NS_IMETHODIMP 1347 DocAccessibleParent::CollectReports(nsIHandleReportCallback* aHandleReport, 1348 nsISupports* aData, bool aAnon) { 1349 nsAutoCString path; 1350 1351 if (aAnon) { 1352 path = nsPrintfCString("explicit/a11y/cache(%" PRIu64 ")", mActorID); 1353 } else { 1354 nsCString url; 1355 URL(url); 1356 url.ReplaceChar( 1357 '/', '\\'); // Tell the memory reporter this is not a path seperator. 1358 path = nsPrintfCString("explicit/a11y/cache(%s)", url.get()); 1359 } 1360 1361 aHandleReport->Callback( 1362 /* process */ ""_ns, path, KIND_HEAP, UNITS_BYTES, 1363 SizeOfIncludingThis(MallocSizeOfAccessibilityCache), 1364 nsLiteralCString("Size of the accessability cache for this document."), 1365 aData); 1366 1367 return NS_OK; 1368 } 1369 1370 NS_IMPL_ISUPPORTS(DocAccessibleParent, nsIMemoryReporter); 1371 1372 } // namespace a11y 1373 } // namespace mozilla