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/. */
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 "nsAccessibilityService.h"
16 #include "xpcAccessibleDocument.h"
17 #include "xpcAccEvents.h"
18 #include "nsAccUtils.h"
19 #include "nsIIOService.h"
20 #include "TextRange.h"
22 #include "RootAccessible.h"
25 # include "Compatibility.h"
26 # include "nsWinUtils.h"
30 # define ACQUIRE_ANDROID_LOCK \
31 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
33 # define ACQUIRE_ANDROID_LOCK \
41 uint64_t DocAccessibleParent::sMaxDocID
= 0;
43 DocAccessibleParent::DocAccessibleParent()
44 : RemoteAccessible(this),
45 mParentDoc(kNoParentDoc
),
47 mEmulatedWindowHandle(nullptr),
48 #endif // defined(XP_WIN)
50 mTopLevelInContentProcess(false),
55 mIsCaretAtEndOfLine(false) {
58 MOZ_ASSERT(!LiveDocs().Get(mActorID
));
59 LiveDocs().InsertOrUpdate(mActorID
, this);
62 DocAccessibleParent::~DocAccessibleParent() {
63 UnregisterWeakMemoryReporter(this);
64 LiveDocs().Remove(mActorID
);
65 MOZ_ASSERT(mChildDocs
.Length() == 0);
66 MOZ_ASSERT(!ParentDoc());
69 already_AddRefed
<DocAccessibleParent
> DocAccessibleParent::New() {
70 RefPtr
<DocAccessibleParent
> dap(new DocAccessibleParent());
71 // We need to do this with a non-zero reference count. The easiest way is to
72 // do it in this static method and hide the constructor.
73 RegisterWeakMemoryReporter(dap
);
77 void DocAccessibleParent::SetBrowsingContext(
78 dom::CanonicalBrowsingContext
* aBrowsingContext
) {
79 mBrowsingContext
= aBrowsingContext
;
82 mozilla::ipc::IPCResult
DocAccessibleParent::RecvShowEvent(
83 nsTArray
<AccessibleData
>&& aNewTree
, const bool& aEventSuppressed
,
84 const bool& aComplete
, const bool& aFromUser
) {
86 if (mShutdown
) return IPC_OK();
88 MOZ_ASSERT(CheckDocTree());
90 if (aNewTree
.IsEmpty()) {
91 return IPC_FAIL(this, "No children being added");
94 RemoteAccessible
* root
= nullptr;
95 RemoteAccessible
* rootParent
= nullptr;
96 RemoteAccessible
* lastParent
= this;
97 uint64_t lastParentID
= 0;
98 for (const auto& accData
: aNewTree
) {
99 RemoteAccessible
* parent
= accData
.ParentID() == lastParentID
101 : GetAccessible(accData
.ParentID());
102 // XXX This should really never happen, but sometimes we fail to fire the
103 // required show events.
105 NS_ERROR("adding child to unknown accessible");
107 return IPC_FAIL(this, "unknown parent accessible");
113 uint32_t childIdx
= accData
.IndexInParent();
114 if (childIdx
> parent
->ChildCount()) {
115 NS_ERROR("invalid index to add child at");
117 return IPC_FAIL(this, "invalid index");
123 RemoteAccessible
* child
= CreateAcc(accData
);
125 // This shouldn't happen.
126 return IPC_FAIL(this, "failed to add children");
128 if (!root
&& !mPendingShowChild
) {
129 // This is the first Accessible, which is the root of the shown subtree.
133 // If this show event has been split across multiple messages and this is
134 // not the last message, don't attach the shown root to the tree yet.
135 // Otherwise, clients might crawl the incomplete subtree and they won't get
136 // mutation events for the remaining pieces.
137 if (aComplete
|| root
!= child
) {
138 AttachChild(parent
, childIdx
, child
);
142 MOZ_ASSERT(CheckDocTree());
144 if (!aComplete
&& !mPendingShowChild
) {
145 // This is the first message for a show event split across multiple
146 // messages. Save the show target for subsequent messages and return.
147 const auto& accData
= aNewTree
[0];
148 mPendingShowChild
= accData
.ID();
149 mPendingShowParent
= accData
.ParentID();
150 mPendingShowIndex
= accData
.IndexInParent();
154 // This show event has been split into multiple messages, but this is
155 // neither the first nor the last message. There's nothing more to do here.
158 MOZ_ASSERT(aComplete
);
159 if (mPendingShowChild
) {
160 // This is the last message for a show event split across multiple
161 // messages. Retrieve the saved show target, attach it to the tree and fire
162 // an event if appropriate.
163 rootParent
= GetAccessible(mPendingShowParent
);
164 MOZ_ASSERT(rootParent
);
165 root
= GetAccessible(mPendingShowChild
);
167 AttachChild(rootParent
, mPendingShowIndex
, root
);
168 mPendingShowChild
= 0;
169 mPendingShowParent
= 0;
170 mPendingShowIndex
= 0;
173 // Just update, no events.
174 if (aEventSuppressed
) {
178 PlatformShowHideEvent(root
, rootParent
, true, aFromUser
);
180 if (nsCOMPtr
<nsIObserverService
> obsService
=
181 services::GetObserverService()) {
182 obsService
->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC
, nullptr);
185 if (!nsCoreUtils::AccEventObserversExist()) {
189 uint32_t type
= nsIAccessibleEvent::EVENT_SHOW
;
190 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(root
);
191 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
192 nsINode
* node
= nullptr;
193 RefPtr
<xpcAccEvent
> event
=
194 new xpcAccEvent(type
, xpcAcc
, doc
, node
, aFromUser
);
195 nsCoreUtils::DispatchAccEvent(std::move(event
));
200 RemoteAccessible
* DocAccessibleParent::CreateAcc(
201 const AccessibleData
& aAccData
) {
202 RemoteAccessible
* newProxy
;
203 if ((newProxy
= GetAccessible(aAccData
.ID()))) {
204 // This is a move. Reuse the Accessible; don't destroy it.
205 MOZ_ASSERT(!newProxy
->RemoteParent());
209 if (!aria::IsRoleMapIndexValid(aAccData
.RoleMapEntryIndex())) {
210 MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
214 newProxy
= new RemoteAccessible(aAccData
.ID(), this, aAccData
.Role(),
215 aAccData
.Type(), aAccData
.GenericTypes(),
216 aAccData
.RoleMapEntryIndex());
217 mAccessibles
.PutEntry(aAccData
.ID())->mProxy
= newProxy
;
219 if (RefPtr
<AccAttributes
> fields
= aAccData
.CacheFields()) {
220 newProxy
->ApplyCache(CacheUpdateType::Initial
, fields
);
226 void DocAccessibleParent::AttachChild(RemoteAccessible
* aParent
,
228 RemoteAccessible
* aChild
) {
229 aParent
->AddChildAt(aIndex
, aChild
);
230 aChild
->SetParent(aParent
);
231 // ProxyCreated might have already been called if aChild is being moved.
232 if (!aChild
->GetWrapper()) {
233 ProxyCreated(aChild
);
235 if (aChild
->IsTableCell()) {
236 CachedTableAccessible::Invalidate(aChild
);
238 if (aChild
->IsOuterDoc()) {
239 // We can only do this after ProxyCreated is called because it will fire an
241 mPendingOOPChildDocs
.RemoveIf([&](dom::BrowserBridgeParent
* bridge
) {
242 MOZ_ASSERT(bridge
->GetBrowserParent(),
243 "Pending BrowserBridgeParent should be alive");
244 if (bridge
->GetEmbedderAccessibleId() != aChild
->ID()) {
247 MOZ_ASSERT(bridge
->GetEmbedderAccessibleDoc() == this);
248 if (DocAccessibleParent
* childDoc
= bridge
->GetDocAccessibleParent()) {
249 AddChildDoc(childDoc
, aChild
->ID(), false);
256 void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible
* aAcc
) {
257 // Children might be removed or moved. Handle them the same way. We do this
258 // before checking the moving IDs set in order to ensure that we handle moved
259 // descendants properly. Avoid descending into the children of outer documents
260 // for moves since they are added and removed differently to normal children.
261 if (!aAcc
->IsOuterDoc()) {
262 // Even if some children are kept, those will be re-attached when we handle
263 // the show event. For now, clear all of them by moving them to a temporary.
264 auto children
{std::move(aAcc
->mChildren
)};
265 for (RemoteAccessible
* child
: children
) {
266 ShutdownOrPrepareForMove(child
);
270 const uint64_t id
= aAcc
->ID();
271 if (!mMovingIDs
.Contains(id
)) {
272 // This Accessible is being removed.
276 // This is a move. Moves are sent as a hide and then a show, but for a move,
277 // we want to keep the Accessible alive for reuse later.
278 if (aAcc
->IsTable() || aAcc
->IsTableCell()) {
279 // For table cells, it's important that we do this before the parent is
280 // cleared because CachedTableAccessible::Invalidate needs the ancestry.
281 CachedTableAccessible::Invalidate(aAcc
);
283 if (aAcc
->IsHyperText()) {
284 aAcc
->InvalidateCachedHyperTextOffsets();
286 aAcc
->SetParent(nullptr);
287 mMovingIDs
.EnsureRemoved(id
);
290 mozilla::ipc::IPCResult
DocAccessibleParent::RecvHideEvent(
291 const uint64_t& aRootID
, const bool& aFromUser
) {
293 if (mShutdown
) return IPC_OK();
295 MOZ_ASSERT(CheckDocTree());
297 // We shouldn't actually need this because mAccessibles shouldn't have an
298 // entry for the document itself, but it doesn't hurt to be explicit.
300 return IPC_FAIL(this, "Trying to hide entire document?");
303 ProxyEntry
* rootEntry
= mAccessibles
.GetEntry(aRootID
);
305 NS_ERROR("invalid root being removed!");
309 RemoteAccessible
* root
= rootEntry
->mProxy
;
311 NS_ERROR("invalid root being removed!");
315 RemoteAccessible
* parent
= root
->RemoteParent();
316 PlatformShowHideEvent(root
, parent
, false, aFromUser
);
318 RefPtr
<xpcAccHideEvent
> event
= nullptr;
319 if (nsCoreUtils::AccEventObserversExist()) {
320 uint32_t type
= nsIAccessibleEvent::EVENT_HIDE
;
321 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(root
);
322 xpcAccessibleGeneric
* xpcParent
= GetXPCAccessible(parent
);
323 RemoteAccessible
* next
= root
->RemoteNextSibling();
324 xpcAccessibleGeneric
* xpcNext
= next
? GetXPCAccessible(next
) : nullptr;
325 RemoteAccessible
* prev
= root
->RemotePrevSibling();
326 xpcAccessibleGeneric
* xpcPrev
= prev
? GetXPCAccessible(prev
) : nullptr;
327 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
328 nsINode
* node
= nullptr;
329 event
= new xpcAccHideEvent(type
, xpcAcc
, doc
, node
, aFromUser
, xpcParent
,
333 parent
->RemoveChild(root
);
334 ShutdownOrPrepareForMove(root
);
336 MOZ_ASSERT(CheckDocTree());
339 nsCoreUtils::DispatchAccEvent(std::move(event
));
345 mozilla::ipc::IPCResult
DocAccessibleParent::RecvEvent(
346 const uint64_t& aID
, const uint32_t& aEventType
) {
352 RemoteAccessible
* remote
= GetAccessible(aID
);
354 NS_ERROR("no proxy for event!");
358 FireEvent(remote
, aEventType
);
362 void DocAccessibleParent::FireEvent(RemoteAccessible
* aAcc
,
363 const uint32_t& aEventType
) {
364 if (aEventType
== nsIAccessibleEvent::EVENT_REORDER
||
365 aEventType
== nsIAccessibleEvent::EVENT_INNER_REORDER
) {
366 for (RemoteAccessible
* child
= aAcc
->RemoteFirstChild(); child
;
367 child
= child
->RemoteNextSibling()) {
368 child
->InvalidateGroupInfo();
370 } else if (aEventType
== nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
&&
372 // A DocAccessible gets the STALE state while it is still loading, but we
373 // don't fire a state change for that. That state might have been
374 // included in the initial cache push, so clear it here.
375 // We also clear the BUSY state here. Although we do fire a state change
376 // for that, we fire it after doc load complete. It doesn't make sense
377 // for the document to report BUSY after doc load complete and doing so
379 UpdateStateCache(states::STALE
| states::BUSY
, false);
382 PlatformEvent(aAcc
, aEventType
);
384 if (!nsCoreUtils::AccEventObserversExist()) {
388 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(aAcc
);
389 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
390 nsINode
* node
= nullptr;
391 bool fromUser
= true; // XXX fix me
392 RefPtr
<xpcAccEvent
> event
=
393 new xpcAccEvent(aEventType
, xpcAcc
, doc
, node
, fromUser
);
394 nsCoreUtils::DispatchAccEvent(std::move(event
));
397 mozilla::ipc::IPCResult
DocAccessibleParent::RecvStateChangeEvent(
398 const uint64_t& aID
, const uint64_t& aState
, const bool& aEnabled
) {
404 RemoteAccessible
* target
= GetAccessible(aID
);
406 NS_ERROR("we don't know about the target of a state change event!");
410 target
->UpdateStateCache(aState
, aEnabled
);
411 if (nsCOMPtr
<nsIObserverService
> obsService
=
412 services::GetObserverService()) {
413 obsService
->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC
, nullptr);
415 PlatformStateChangeEvent(target
, aState
, aEnabled
);
417 if (!nsCoreUtils::AccEventObserversExist()) {
421 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
422 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
423 uint32_t type
= nsIAccessibleEvent::EVENT_STATE_CHANGE
;
425 uint32_t state
= nsAccUtils::To32States(aState
, &extra
);
426 bool fromUser
= true; // XXX fix this
427 nsINode
* node
= nullptr; // XXX can we do better?
428 RefPtr
<xpcAccStateChangeEvent
> event
= new xpcAccStateChangeEvent(
429 type
, xpcAcc
, doc
, node
, fromUser
, state
, extra
, aEnabled
);
430 nsCoreUtils::DispatchAccEvent(std::move(event
));
435 mozilla::ipc::IPCResult
DocAccessibleParent::RecvCaretMoveEvent(
436 const uint64_t& aID
, const LayoutDeviceIntRect
& aCaretRect
,
437 const int32_t& aOffset
, const bool& aIsSelectionCollapsed
,
438 const bool& aIsAtEndOfLine
, const int32_t& aGranularity
) {
444 RemoteAccessible
* proxy
= GetAccessible(aID
);
446 NS_ERROR("unknown caret move event target!");
451 mCaretOffset
= aOffset
;
452 mIsCaretAtEndOfLine
= aIsAtEndOfLine
;
453 if (aIsSelectionCollapsed
) {
454 // We don't fire selection events for collapsed selections, but we need to
455 // ensure we don't have a stale cached selection; e.g. when selecting
456 // forward and then unselecting backward.
457 mTextSelections
.ClearAndRetainStorage();
458 mTextSelections
.AppendElement(TextRangeData(aID
, aID
, aOffset
, aOffset
));
461 PlatformCaretMoveEvent(proxy
, aOffset
, aIsSelectionCollapsed
, aGranularity
,
464 if (!nsCoreUtils::AccEventObserversExist()) {
468 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(proxy
);
469 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
470 nsINode
* node
= nullptr;
471 bool fromUser
= true; // XXX fix me
472 uint32_t type
= nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
;
473 RefPtr
<xpcAccCaretMoveEvent
> event
= new xpcAccCaretMoveEvent(
474 type
, xpcAcc
, doc
, node
, fromUser
, aOffset
, aIsSelectionCollapsed
,
475 aIsAtEndOfLine
, aGranularity
);
476 nsCoreUtils::DispatchAccEvent(std::move(event
));
481 mozilla::ipc::IPCResult
DocAccessibleParent::RecvTextChangeEvent(
482 const uint64_t& aID
, const nsAString
& aStr
, const int32_t& aStart
,
483 const uint32_t& aLen
, const bool& aIsInsert
, const bool& aFromUser
) {
489 RemoteAccessible
* target
= GetAccessible(aID
);
491 NS_ERROR("text change event target is unknown!");
495 PlatformTextChangeEvent(target
, aStr
, aStart
, aLen
, aIsInsert
, aFromUser
);
497 if (!nsCoreUtils::AccEventObserversExist()) {
501 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
502 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
503 uint32_t type
= aIsInsert
? nsIAccessibleEvent::EVENT_TEXT_INSERTED
504 : nsIAccessibleEvent::EVENT_TEXT_REMOVED
;
505 nsINode
* node
= nullptr;
506 RefPtr
<xpcAccTextChangeEvent
> event
= new xpcAccTextChangeEvent(
507 type
, xpcAcc
, doc
, node
, aFromUser
, aStart
, aLen
, aIsInsert
, aStr
);
508 nsCoreUtils::DispatchAccEvent(std::move(event
));
513 mozilla::ipc::IPCResult
DocAccessibleParent::RecvSelectionEvent(
514 const uint64_t& aID
, const uint64_t& aWidgetID
, const uint32_t& aType
) {
520 RemoteAccessible
* target
= GetAccessible(aID
);
521 RemoteAccessible
* widget
= GetAccessible(aWidgetID
);
522 if (!target
|| !widget
) {
523 NS_ERROR("invalid id in selection event");
527 PlatformSelectionEvent(target
, widget
, aType
);
528 if (!nsCoreUtils::AccEventObserversExist()) {
531 xpcAccessibleGeneric
* xpcTarget
= GetXPCAccessible(target
);
532 xpcAccessibleDocument
* xpcDoc
= GetAccService()->GetXPCDocument(this);
533 RefPtr
<xpcAccEvent
> event
=
534 new xpcAccEvent(aType
, xpcTarget
, xpcDoc
, nullptr, false);
535 nsCoreUtils::DispatchAccEvent(std::move(event
));
540 mozilla::ipc::IPCResult
DocAccessibleParent::RecvVirtualCursorChangeEvent(
541 const uint64_t& aID
, const uint64_t& aOldPositionID
,
542 const uint64_t& aNewPositionID
, const int16_t& aReason
,
543 const bool& aFromUser
) {
549 RemoteAccessible
* target
= GetAccessible(aID
);
550 RemoteAccessible
* oldPosition
= GetAccessible(aOldPositionID
);
551 RemoteAccessible
* newPosition
= GetAccessible(aNewPositionID
);
554 NS_ERROR("no proxy for event!");
559 PlatformVirtualCursorChangeEvent(target
, oldPosition
, newPosition
, aReason
,
563 if (!nsCoreUtils::AccEventObserversExist()) {
567 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
568 RefPtr
<xpcAccVirtualCursorChangeEvent
> event
=
569 new xpcAccVirtualCursorChangeEvent(
570 nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
,
571 GetXPCAccessible(target
), doc
, nullptr, aFromUser
,
572 GetXPCAccessible(oldPosition
), GetXPCAccessible(newPosition
),
574 nsCoreUtils::DispatchAccEvent(std::move(event
));
579 mozilla::ipc::IPCResult
DocAccessibleParent::RecvScrollingEvent(
580 const uint64_t& aID
, const uint64_t& aType
, const uint32_t& aScrollX
,
581 const uint32_t& aScrollY
, const uint32_t& aMaxScrollX
,
582 const uint32_t& aMaxScrollY
) {
588 RemoteAccessible
* target
= GetAccessible(aID
);
590 NS_ERROR("no proxy for event!");
595 PlatformScrollingEvent(target
, aType
, aScrollX
, aScrollY
, aMaxScrollX
,
598 PlatformEvent(target
, aType
);
601 if (!nsCoreUtils::AccEventObserversExist()) {
605 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
606 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
607 nsINode
* node
= nullptr;
608 bool fromUser
= true; // XXX: Determine if this was from user input.
609 RefPtr
<xpcAccScrollingEvent
> event
=
610 new xpcAccScrollingEvent(aType
, xpcAcc
, doc
, node
, fromUser
, aScrollX
,
611 aScrollY
, aMaxScrollX
, aMaxScrollY
);
612 nsCoreUtils::DispatchAccEvent(std::move(event
));
617 mozilla::ipc::IPCResult
DocAccessibleParent::RecvCache(
618 const mozilla::a11y::CacheUpdateType
& aUpdateType
,
619 nsTArray
<CacheData
>&& aData
) {
625 for (auto& entry
: aData
) {
626 RemoteAccessible
* remote
= GetAccessible(entry
.ID());
628 MOZ_ASSERT_UNREACHABLE("No remote found!");
632 remote
->ApplyCache(aUpdateType
, entry
.Fields());
635 if (nsCOMPtr
<nsIObserverService
> obsService
=
636 services::GetObserverService()) {
637 obsService
->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC
, nullptr);
643 mozilla::ipc::IPCResult
DocAccessibleParent::RecvSelectedAccessiblesChanged(
644 nsTArray
<uint64_t>&& aSelectedIDs
, nsTArray
<uint64_t>&& aUnselectedIDs
) {
650 for (auto& id
: aSelectedIDs
) {
651 RemoteAccessible
* remote
= GetAccessible(id
);
653 MOZ_ASSERT_UNREACHABLE("No remote found!");
657 remote
->UpdateStateCache(states::SELECTED
, true);
660 for (auto& id
: aUnselectedIDs
) {
661 RemoteAccessible
* remote
= GetAccessible(id
);
663 MOZ_ASSERT_UNREACHABLE("No remote found!");
667 remote
->UpdateStateCache(states::SELECTED
, false);
670 if (nsCOMPtr
<nsIObserverService
> obsService
=
671 services::GetObserverService()) {
672 obsService
->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC
, nullptr);
678 mozilla::ipc::IPCResult
DocAccessibleParent::RecvAccessiblesWillMove(
679 nsTArray
<uint64_t>&& aIDs
) {
680 for (uint64_t id
: aIDs
) {
681 mMovingIDs
.EnsureInserted(id
);
687 mozilla::ipc::IPCResult
DocAccessibleParent::RecvAnnouncementEvent(
688 const uint64_t& aID
, const nsAString
& aAnnouncement
,
689 const uint16_t& aPriority
) {
695 RemoteAccessible
* target
= GetAccessible(aID
);
697 NS_ERROR("no proxy for event!");
701 # if defined(ANDROID)
702 PlatformAnnouncementEvent(target
, aAnnouncement
, aPriority
);
705 if (!nsCoreUtils::AccEventObserversExist()) {
709 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
710 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
711 RefPtr
<xpcAccAnnouncementEvent
> event
= new xpcAccAnnouncementEvent(
712 nsIAccessibleEvent::EVENT_ANNOUNCEMENT
, xpcAcc
, doc
, nullptr, false,
713 aAnnouncement
, aPriority
);
714 nsCoreUtils::DispatchAccEvent(std::move(event
));
718 #endif // !defined(XP_WIN)
720 mozilla::ipc::IPCResult
DocAccessibleParent::RecvTextSelectionChangeEvent(
721 const uint64_t& aID
, nsTArray
<TextRangeData
>&& aSelection
) {
727 RemoteAccessible
* target
= GetAccessible(aID
);
729 NS_ERROR("no proxy for event!");
733 mTextSelections
.ClearAndRetainStorage();
734 mTextSelections
.AppendElements(aSelection
);
736 #ifdef MOZ_WIDGET_COCOA
737 AutoTArray
<TextRange
, 1> ranges
;
738 SelectionRanges(&ranges
);
739 PlatformTextSelectionChangeEvent(target
, ranges
);
741 PlatformEvent(target
, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
);
744 if (!nsCoreUtils::AccEventObserversExist()) {
747 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
748 xpcAccessibleDocument
* doc
= nsAccessibilityService::GetXPCDocument(this);
749 nsINode
* node
= nullptr;
750 bool fromUser
= true; // XXX fix me
751 RefPtr
<xpcAccEvent
> event
=
752 new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
, xpcAcc
,
753 doc
, node
, fromUser
);
754 nsCoreUtils::DispatchAccEvent(std::move(event
));
759 mozilla::ipc::IPCResult
DocAccessibleParent::RecvRoleChangedEvent(
760 const a11y::role
& aRole
, const uint8_t& aRoleMapEntryIndex
) {
767 mRoleMapEntryIndex
= aRoleMapEntryIndex
;
769 #ifdef MOZ_WIDGET_COCOA
770 PlatformRoleChangedEvent(this, aRole
, aRoleMapEntryIndex
);
776 mozilla::ipc::IPCResult
DocAccessibleParent::RecvBindChildDoc(
777 NotNull
<PDocAccessibleParent
*> aChildDoc
, const uint64_t& aID
) {
779 // One document should never directly be the child of another.
780 // We should always have at least an outer doc accessible in between.
782 if (!aID
) return IPC_FAIL(this, "ID is 0!");
788 MOZ_ASSERT(CheckDocTree());
790 auto childDoc
= static_cast<DocAccessibleParent
*>(aChildDoc
.get());
792 ipc::IPCResult result
= AddChildDoc(childDoc
, aID
, false);
794 MOZ_ASSERT(CheckDocTree());
806 ipc::IPCResult
DocAccessibleParent::AddChildDoc(DocAccessibleParent
* aChildDoc
,
809 // We do not use GetAccessible here because we want to be sure to not get the
811 ProxyEntry
* e
= mAccessibles
.GetEntry(aParentID
);
813 #ifndef FUZZING_SNAPSHOT
814 // This diagnostic assert and the one down below expect a well-behaved
815 // child process. In IPC fuzzing, we directly fuzz parameters of each
816 // method over IPDL and the asserts are not valid under these conditions.
817 MOZ_DIAGNOSTIC_ASSERT(false, "Binding to nonexistent proxy!");
819 return IPC_FAIL(this, "binding to nonexistant proxy!");
822 RemoteAccessible
* outerDoc
= e
->mProxy
;
823 MOZ_ASSERT(outerDoc
);
825 // OuterDocAccessibles are expected to only have a document as a child.
826 // However for compatibility we tolerate replacing one document with another
828 if (!outerDoc
->IsOuterDoc() || outerDoc
->ChildCount() > 1 ||
829 (outerDoc
->ChildCount() == 1 && !outerDoc
->RemoteChildAt(0)->IsDoc())) {
830 #ifndef FUZZING_SNAPSHOT
831 MOZ_DIAGNOSTIC_ASSERT(false,
832 "Binding to parent that isn't a valid OuterDoc!");
834 return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!");
837 if (outerDoc
->ChildCount() == 1) {
838 MOZ_ASSERT(outerDoc
->RemoteChildAt(0)->AsDoc());
839 outerDoc
->RemoteChildAt(0)->AsDoc()->Unbind();
842 aChildDoc
->SetParent(outerDoc
);
843 outerDoc
->SetChildDoc(aChildDoc
);
844 mChildDocs
.AppendElement(aChildDoc
->mActorID
);
845 aChildDoc
->mParentDoc
= mActorID
;
848 ProxyCreated(aChildDoc
);
851 if (aChildDoc
->IsTopLevelInContentProcess()) {
852 // aChildDoc is an embedded document in a different content process to
854 auto embeddedBrowser
=
855 static_cast<dom::BrowserParent
*>(aChildDoc
->Manager());
856 dom::BrowserBridgeParent
* bridge
=
857 embeddedBrowser
->GetBrowserBridgeParent();
860 if (nsWinUtils::IsWindowEmulationStarted()) {
861 aChildDoc
->SetEmulatedWindowHandle(mEmulatedWindowHandle
);
863 #endif // defined(XP_WIN)
864 // We need to fire a reorder event on the outer doc accessible.
865 // For same-process documents, this is fired by the content process, but
866 // this isn't possible when the document is in a different process to its
868 // FireEvent fires both OS and XPCOM events.
869 FireEvent(outerDoc
, nsIAccessibleEvent::EVENT_REORDER
);
876 ipc::IPCResult
DocAccessibleParent::AddChildDoc(
877 dom::BrowserBridgeParent
* aBridge
) {
878 MOZ_ASSERT(aBridge
->GetEmbedderAccessibleDoc() == this);
879 uint64_t parentId
= aBridge
->GetEmbedderAccessibleId();
880 MOZ_ASSERT(parentId
);
881 if (!mAccessibles
.GetEntry(parentId
)) {
882 // Sometimes, this gets called before the embedder sends us the
883 // OuterDocAccessible. We must add the child when the OuterDocAccessible
884 // gets created later.
885 mPendingOOPChildDocs
.Insert(aBridge
);
888 return AddChildDoc(aBridge
->GetDocAccessibleParent(), parentId
,
889 /* aCreating */ false);
892 mozilla::ipc::IPCResult
DocAccessibleParent::RecvShutdown() {
896 auto mgr
= static_cast<dom::BrowserParent
*>(Manager());
897 if (!mgr
->IsDestroyed()) {
898 if (!PDocAccessibleParent::Send__delete__(this)) {
899 return IPC_FAIL_NO_REASON(mgr
);
906 void DocAccessibleParent::Destroy() {
907 // If we are already shutdown that is because our containing tab parent is
908 // shutting down in which case we don't need to do anything.
914 mBrowsingContext
= nullptr;
917 if (FocusMgr() && FocusMgr()->IsFocusedRemoteDoc(this)) {
918 FocusMgr()->SetFocusedRemoteDoc(nullptr);
922 MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID
));
923 uint32_t childDocCount
= mChildDocs
.Length();
924 for (uint32_t i
= 0; i
< childDocCount
; i
++) {
925 for (uint32_t j
= i
+ 1; j
< childDocCount
; j
++) {
926 MOZ_DIAGNOSTIC_ASSERT(mChildDocs
[i
] != mChildDocs
[j
]);
930 // XXX This indirection through the hash map of live documents shouldn't be
931 // needed, but be paranoid for now.
932 int32_t actorID
= mActorID
;
933 for (uint32_t i
= childDocCount
- 1; i
< childDocCount
; i
--) {
934 DocAccessibleParent
* thisDoc
= LiveDocs().Get(actorID
);
940 thisDoc
->ChildDocAt(i
)->Destroy();
943 for (auto iter
= mAccessibles
.Iter(); !iter
.Done(); iter
.Next()) {
944 RemoteAccessible
* acc
= iter
.Get()->mProxy
;
945 MOZ_ASSERT(acc
!= this);
946 if (acc
->IsTable()) {
947 CachedTableAccessible::Invalidate(acc
);
953 DocAccessibleParent
* thisDoc
= LiveDocs().Get(actorID
);
960 // The code above should have already completely cleared these, but to be
961 // extra safe make sure they are cleared here.
962 thisDoc
->mAccessibles
.Clear();
963 thisDoc
->mChildDocs
.Clear();
965 DocManager::NotifyOfRemoteDocShutdown(thisDoc
);
966 thisDoc
= LiveDocs().Get(actorID
);
972 ProxyDestroyed(thisDoc
);
973 thisDoc
= LiveDocs().Get(actorID
);
979 if (DocAccessibleParent
* parentDoc
= thisDoc
->ParentDoc()) {
980 parentDoc
->RemoveChildDoc(thisDoc
);
981 } else if (IsTopLevel()) {
982 GetAccService()->RemoteDocShutdown(this);
986 void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy
) {
987 MOZ_ASSERT(CheckDocTree());
994 DocAccessibleParent
* DocAccessibleParent::ParentDoc() const {
995 if (mParentDoc
== kNoParentDoc
) {
999 return LiveDocs().Get(mParentDoc
);
1002 bool DocAccessibleParent::CheckDocTree() const {
1003 size_t childDocs
= mChildDocs
.Length();
1004 for (size_t i
= 0; i
< childDocs
; i
++) {
1005 const DocAccessibleParent
* childDoc
= ChildDocAt(i
);
1006 if (!childDoc
|| childDoc
->ParentDoc() != this) return false;
1008 if (!childDoc
->CheckDocTree()) {
1016 xpcAccessibleGeneric
* DocAccessibleParent::GetXPCAccessible(
1017 RemoteAccessible
* aProxy
) {
1018 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
1021 return doc
->GetAccessible(aProxy
);
1025 void DocAccessibleParent::MaybeInitWindowEmulation() {
1026 if (!nsWinUtils::IsWindowEmulationStarted()) {
1030 // XXX get the bounds from the browserParent instead of poking at accessibles
1031 // which might not exist yet.
1032 LocalAccessible
* outerDoc
= OuterDocOfRemoteBrowser();
1037 RootAccessible
* rootDocument
= outerDoc
->RootAccessible();
1038 MOZ_ASSERT(rootDocument
);
1040 bool isActive
= true;
1041 LayoutDeviceIntRect
rect(CW_USEDEFAULT
, CW_USEDEFAULT
, 0, 0);
1042 if (Compatibility::IsDolphin()) {
1044 LayoutDeviceIntRect rootRect
= rootDocument
->Bounds();
1045 rect
.MoveToX(rootRect
.X() - rect
.X());
1046 rect
.MoveToY(rect
.Y() - rootRect
.Y());
1048 auto browserParent
= static_cast<dom::BrowserParent
*>(Manager());
1049 isActive
= browserParent
->GetDocShellIsActive();
1052 // onCreate is guaranteed to be called synchronously by
1053 // nsWinUtils::CreateNativeWindow, so this reference isn't really necessary.
1054 // However, static analysis complains without it.
1055 RefPtr
<DocAccessibleParent
> thisRef
= this;
1056 nsWinUtils::NativeWindowCreateProc
onCreate([thisRef
](HWND aHwnd
) -> void {
1057 ::SetPropW(aHwnd
, kPropNameDocAccParent
,
1058 reinterpret_cast<HANDLE
>(thisRef
.get()));
1059 thisRef
->SetEmulatedWindowHandle(aHwnd
);
1062 HWND parentWnd
= reinterpret_cast<HWND
>(rootDocument
->GetNativeWindow());
1063 DebugOnly
<HWND
> hWnd
= nsWinUtils::CreateNativeWindow(
1064 kClassNameTabContent
, parentWnd
, rect
.X(), rect
.Y(), rect
.Width(),
1065 rect
.Height(), isActive
, &onCreate
);
1069 void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle
) {
1070 if (!aWindowHandle
&& mEmulatedWindowHandle
&& IsTopLevel()) {
1071 ::DestroyWindow(mEmulatedWindowHandle
);
1073 mEmulatedWindowHandle
= aWindowHandle
;
1075 #endif // defined(XP_WIN)
1077 mozilla::ipc::IPCResult
DocAccessibleParent::RecvFocusEvent(
1078 const uint64_t& aID
, const LayoutDeviceIntRect
& aCaretRect
) {
1079 ACQUIRE_ANDROID_LOCK
1084 RemoteAccessible
* proxy
= GetAccessible(aID
);
1086 NS_ERROR("no proxy for event!");
1092 FocusMgr()->SetFocusedRemoteDoc(this);
1097 PlatformFocusEvent(proxy
, aCaretRect
);
1099 if (!nsCoreUtils::AccEventObserversExist()) {
1103 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(proxy
);
1104 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
1105 nsINode
* node
= nullptr;
1106 bool fromUser
= true; // XXX fix me
1107 RefPtr
<xpcAccEvent
> event
= new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS
,
1108 xpcAcc
, doc
, node
, fromUser
);
1109 nsCoreUtils::DispatchAccEvent(std::move(event
));
1114 void DocAccessibleParent::SelectionRanges(nsTArray
<TextRange
>* aRanges
) const {
1115 aRanges
->SetCapacity(mTextSelections
.Length());
1116 for (const auto& data
: mTextSelections
) {
1117 // Selection ranges should usually be in sync with the tree. However, tree
1118 // and selection updates happen using separate IPDL calls, so it's possible
1119 // for a client selection query to arrive between them. Thus, we validate
1120 // the Accessibles and offsets here.
1122 const_cast<RemoteAccessible
*>(GetAccessible(data
.StartID()));
1123 auto* endAcc
= const_cast<RemoteAccessible
*>(GetAccessible(data
.EndID()));
1124 if (!startAcc
|| !endAcc
) {
1127 uint32_t startCount
= startAcc
->CharacterCount();
1128 if (startCount
== 0 ||
1129 data
.StartOffset() > static_cast<int32_t>(startCount
)) {
1132 uint32_t endCount
= endAcc
->CharacterCount();
1133 if (endCount
== 0 || data
.EndOffset() > static_cast<int32_t>(endCount
)) {
1136 aRanges
->AppendElement(TextRange(const_cast<DocAccessibleParent
*>(this),
1137 startAcc
, data
.StartOffset(), endAcc
,
1142 Accessible
* DocAccessibleParent::FocusedChild() {
1143 LocalAccessible
* outerDoc
= OuterDocOfRemoteBrowser();
1148 RootAccessible
* rootDocument
= outerDoc
->RootAccessible();
1149 return rootDocument
->FocusedChild();
1152 void DocAccessibleParent::URL(nsACString
& aURL
) const {
1153 if (!mBrowsingContext
) {
1156 nsCOMPtr
<nsIURI
> uri
= mBrowsingContext
->GetCurrentURI();
1160 // Let's avoid treating too long URI in the main process for avoiding
1161 // memory fragmentation as far as possible.
1162 if (uri
->SchemeIs("data") || uri
->SchemeIs("blob")) {
1165 nsCOMPtr
<nsIIOService
> io
= mozilla::components::IO::Service();
1166 if (NS_WARN_IF(!io
)) {
1169 nsCOMPtr
<nsIURI
> exposableURI
;
1170 if (NS_FAILED(io
->CreateExposableURI(uri
, getter_AddRefs(exposableURI
))) ||
1171 MOZ_UNLIKELY(!exposableURI
)) {
1174 exposableURI
->GetSpec(aURL
);
1177 void DocAccessibleParent::URL(nsAString
& aURL
) const {
1180 CopyUTF8toUTF16(url
, aURL
);
1183 void DocAccessibleParent::MimeType(nsAString
& aMime
) const {
1184 if (mCachedFields
) {
1185 mCachedFields
->GetAttribute(CacheKey::MimeType
, aMime
);
1189 Relation
DocAccessibleParent::RelationByType(RelationType aType
) const {
1190 // If the accessible is top-level, provide the NODE_CHILD_OF relation so that
1191 // MSAA clients can easily get to true parent instead of getting to oleacc's
1192 // ROLE_WINDOW accessible when window emulation is enabled which will prevent
1193 // us from going up further (because it is system generated and has no idea
1194 // about the hierarchy above it).
1195 if (aType
== RelationType::NODE_CHILD_OF
&& IsTopLevel()) {
1196 return Relation(Parent());
1199 return RemoteAccessible::RelationByType(aType
);
1202 DocAccessibleParent
* DocAccessibleParent::GetFrom(
1203 dom::BrowsingContext
* aBrowsingContext
) {
1204 if (!aBrowsingContext
) {
1208 dom::BrowserParent
* bp
= aBrowsingContext
->Canonical()->GetBrowserParent();
1213 const ManagedContainer
<PDocAccessibleParent
>& docs
=
1214 bp
->ManagedPDocAccessibleParent();
1215 for (auto* key
: docs
) {
1216 // Iterate over our docs until we find one with a browsing
1217 // context that matches the one we passed in. Return that
1219 auto* doc
= static_cast<a11y::DocAccessibleParent
*>(key
);
1220 if (doc
->GetBrowsingContext() == aBrowsingContext
) {
1228 size_t DocAccessibleParent::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) {
1231 size
+= RemoteAccessible::SizeOfExcludingThis(aMallocSizeOf
);
1233 size
+= mReverseRelations
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1234 for (auto i
= mReverseRelations
.Iter(); !i
.Done(); i
.Next()) {
1235 size
+= i
.Data().ShallowSizeOfExcludingThis(aMallocSizeOf
);
1236 for (auto j
= i
.Data().Iter(); !j
.Done(); j
.Next()) {
1237 size
+= j
.Data().ShallowSizeOfExcludingThis(aMallocSizeOf
);
1241 size
+= mOnScreenAccessibles
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1243 size
+= mChildDocs
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1245 size
+= mAccessibles
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1246 for (auto i
= mAccessibles
.Iter(); !i
.Done(); i
.Next()) {
1247 size
+= i
.Get()->mProxy
->SizeOfIncludingThis(aMallocSizeOf
);
1250 size
+= mPendingOOPChildDocs
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1252 // The mTextSelections array contains structs of integers. We can count them
1253 // by counting the size of the array - there's no deep structure here.
1254 size
+= mTextSelections
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1259 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOfAccessibilityCache
);
1262 DocAccessibleParent::CollectReports(nsIHandleReportCallback
* aHandleReport
,
1263 nsISupports
* aData
, bool aAnon
) {
1267 path
= nsPrintfCString("explicit/a11y/cache(%" PRIu64
")", mActorID
);
1272 '/', '\\'); // Tell the memory reporter this is not a path seperator.
1273 path
= nsPrintfCString("explicit/a11y/cache(%s)", url
.get());
1276 aHandleReport
->Callback(
1277 /* process */ ""_ns
, path
, KIND_HEAP
, UNITS_BYTES
,
1278 SizeOfIncludingThis(MallocSizeOfAccessibilityCache
),
1279 nsLiteralCString("Size of the accessability cache for this document."),
1285 NS_IMPL_ISUPPORTS(DocAccessibleParent
, nsIMemoryReporter
);
1288 } // namespace mozilla