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/. */
7 #include "DocAccessibleParent.h"
8 #include "mozilla/a11y/Platform.h"
9 #include "mozilla/dom/BrowserBridgeParent.h"
10 #include "mozilla/dom/BrowserParent.h"
11 #include "mozilla/StaticPrefs_accessibility.h"
12 #include "xpcAccessibleDocument.h"
13 #include "xpcAccEvents.h"
14 #include "nsAccUtils.h"
15 #include "TextRange.h"
18 # include "AccessibleWrap.h"
19 # include "Compatibility.h"
20 # include "mozilla/mscom/PassthruProxy.h"
21 # include "mozilla/mscom/Ptr.h"
22 # include "nsWinUtils.h"
23 # include "RootAccessible.h"
25 # include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
33 // Needed by mscom::PassthruProxy::Wrap<IAccessible>.
35 struct VTableSizer
<IAccessible
> {
36 // 3 methods in IUnknown + 4 in IDispatch + 21 in IAccessible = 28 total
41 #endif // defined (XP_WIN)
44 uint64_t DocAccessibleParent::sMaxDocID
= 0;
46 mozilla::ipc::IPCResult
DocAccessibleParent::RecvShowEvent(
47 const ShowEventData
& aData
, const bool& aFromUser
) {
48 if (mShutdown
) return IPC_OK();
50 MOZ_ASSERT(CheckDocTree());
52 if (aData
.NewTree().IsEmpty()) {
53 return IPC_FAIL(this, "No children being added");
56 RemoteAccessible
* parent
= GetAccessible(aData
.ID());
58 // XXX This should really never happen, but sometimes we fail to fire the
59 // required show events.
61 NS_ERROR("adding child to unknown accessible");
63 return IPC_FAIL(this, "unknown parent accessible");
69 uint32_t newChildIdx
= aData
.Idx();
70 if (newChildIdx
> parent
->ChildCount()) {
71 NS_ERROR("invalid index to add child at");
73 return IPC_FAIL(this, "invalid index");
79 uint32_t consumed
= AddSubtree(parent
, aData
.NewTree(), 0, newChildIdx
);
80 MOZ_ASSERT(consumed
== aData
.NewTree().Length());
82 // XXX This shouldn't happen, but if we failed to add children then the below
83 // is pointless and can crash.
85 return IPC_FAIL(this, "failed to add children");
89 for (uint32_t i
= 0; i
< consumed
; i
++) {
90 uint64_t id
= aData
.NewTree()[i
].ID();
91 MOZ_ASSERT(mAccessibles
.GetEntry(id
));
95 MOZ_ASSERT(CheckDocTree());
97 // Just update, no events.
98 if (aData
.EventSuppressed()) {
102 RemoteAccessible
* target
= parent
->RemoteChildAt(newChildIdx
);
103 ProxyShowHideEvent(target
, parent
, true, aFromUser
);
105 if (!nsCoreUtils::AccEventObserversExist()) {
109 uint32_t type
= nsIAccessibleEvent::EVENT_SHOW
;
110 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
111 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
112 nsINode
* node
= nullptr;
113 RefPtr
<xpcAccEvent
> event
=
114 new xpcAccEvent(type
, xpcAcc
, doc
, node
, aFromUser
);
115 nsCoreUtils::DispatchAccEvent(std::move(event
));
120 uint32_t DocAccessibleParent::AddSubtree(
121 RemoteAccessible
* aParent
, const nsTArray
<a11y::AccessibleData
>& aNewTree
,
122 uint32_t aIdx
, uint32_t aIdxInParent
) {
123 if (aNewTree
.Length() <= aIdx
) {
124 NS_ERROR("bad index in serialized tree!");
128 const AccessibleData
& newChild
= aNewTree
[aIdx
];
130 RemoteAccessible
* newProxy
;
131 if ((newProxy
= GetAccessible(newChild
.ID()))) {
132 // This is a move. Reuse the Accessible; don't destroy it.
133 MOZ_ASSERT(!newProxy
->RemoteParent());
134 aParent
->AddChildAt(aIdxInParent
, newProxy
);
135 newProxy
->SetParent(aParent
);
137 newProxy
= new RemoteAccessible(
138 newChild
.ID(), aParent
, this, newChild
.Role(), newChild
.Type(),
139 newChild
.GenericTypes(), newChild
.RoleMapEntryIndex());
141 aParent
->AddChildAt(aIdxInParent
, newProxy
);
142 mAccessibles
.PutEntry(newChild
.ID())->mProxy
= newProxy
;
143 ProxyCreated(newProxy
);
146 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
147 MsaaAccessible::GetFrom(newProxy
)->SetID(newChild
.MsaaID());
151 mPendingOOPChildDocs
.RemoveIf([&](dom::BrowserBridgeParent
* bridge
) {
152 MOZ_ASSERT(bridge
->GetBrowserParent(),
153 "Pending BrowserBridgeParent should be alive");
154 if (bridge
->GetEmbedderAccessibleId() != newChild
.ID()) {
157 MOZ_ASSERT(bridge
->GetEmbedderAccessibleDoc() == this);
158 if (DocAccessibleParent
* childDoc
= bridge
->GetDocAccessibleParent()) {
159 AddChildDoc(childDoc
, newChild
.ID(), false);
165 DebugOnly
<bool> isOuterDoc
= newProxy
->ChildCount() == 1;
167 uint32_t accessibles
= 1;
168 uint32_t kids
= newChild
.ChildrenCount();
169 for (uint32_t i
= 0; i
< kids
; i
++) {
170 uint32_t consumed
= AddSubtree(newProxy
, aNewTree
, aIdx
+ accessibles
, i
);
171 if (!consumed
) return 0;
173 accessibles
+= consumed
;
176 MOZ_ASSERT((isOuterDoc
&& kids
== 0) || newProxy
->ChildCount() == kids
);
181 void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible
* aAcc
) {
182 uint64_t id
= aAcc
->ID();
183 if (!mMovingIDs
.Contains(id
)) {
184 // This Accessible is being removed.
188 // This is a move. Moves are sent as a hide and then a show, but for a move,
189 // we want to keep the Accessible alive for reuse later.
190 aAcc
->SetParent(nullptr);
191 mMovingIDs
.EnsureRemoved(id
);
192 if (aAcc
->IsOuterDoc()) {
193 // Leave child documents alone. They are added and removed differently to
197 // Some children might be removed. Handle children the same way.
198 for (RemoteAccessible
* child
: aAcc
->mChildren
) {
199 ShutdownOrPrepareForMove(child
);
201 // Even if some children are kept, those will be re-attached when we handle
202 // the show event. For now, clear all of them.
203 aAcc
->mChildren
.Clear();
206 mozilla::ipc::IPCResult
DocAccessibleParent::RecvHideEvent(
207 const uint64_t& aRootID
, const bool& aFromUser
) {
208 if (mShutdown
) return IPC_OK();
210 MOZ_ASSERT(CheckDocTree());
212 // We shouldn't actually need this because mAccessibles shouldn't have an
213 // entry for the document itself, but it doesn't hurt to be explicit.
215 return IPC_FAIL(this, "Trying to hide entire document?");
218 ProxyEntry
* rootEntry
= mAccessibles
.GetEntry(aRootID
);
220 NS_ERROR("invalid root being removed!");
224 RemoteAccessible
* root
= rootEntry
->mProxy
;
226 NS_ERROR("invalid root being removed!");
230 RemoteAccessible
* parent
= root
->RemoteParent();
231 ProxyShowHideEvent(root
, parent
, false, aFromUser
);
233 RefPtr
<xpcAccHideEvent
> event
= nullptr;
234 if (nsCoreUtils::AccEventObserversExist()) {
235 uint32_t type
= nsIAccessibleEvent::EVENT_HIDE
;
236 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(root
);
237 xpcAccessibleGeneric
* xpcParent
= GetXPCAccessible(parent
);
238 RemoteAccessible
* next
= root
->RemoteNextSibling();
239 xpcAccessibleGeneric
* xpcNext
= next
? GetXPCAccessible(next
) : nullptr;
240 RemoteAccessible
* prev
= root
->RemotePrevSibling();
241 xpcAccessibleGeneric
* xpcPrev
= prev
? GetXPCAccessible(prev
) : nullptr;
242 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
243 nsINode
* node
= nullptr;
244 event
= new xpcAccHideEvent(type
, xpcAcc
, doc
, node
, aFromUser
, xpcParent
,
248 parent
->RemoveChild(root
);
249 ShutdownOrPrepareForMove(root
);
251 MOZ_ASSERT(CheckDocTree());
254 nsCoreUtils::DispatchAccEvent(std::move(event
));
260 mozilla::ipc::IPCResult
DocAccessibleParent::RecvEvent(
261 const uint64_t& aID
, const uint32_t& aEventType
) {
266 RemoteAccessible
* proxy
= GetAccessible(aID
);
268 NS_ERROR("no proxy for event!");
272 if (aEventType
== nsIAccessibleEvent::EVENT_FOCUS
) {
276 if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
277 if (aEventType
== nsIAccessibleEvent::EVENT_REORDER
||
278 aEventType
== nsIAccessibleEvent::EVENT_INNER_REORDER
) {
279 for (RemoteAccessible
* child
= proxy
->RemoteFirstChild(); child
;
280 child
= child
->RemoteNextSibling()) {
281 child
->InvalidateGroupInfo();
286 ProxyEvent(proxy
, aEventType
);
288 if (!nsCoreUtils::AccEventObserversExist()) {
292 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(proxy
);
293 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
294 nsINode
* node
= nullptr;
295 bool fromUser
= true; // XXX fix me
296 RefPtr
<xpcAccEvent
> event
=
297 new xpcAccEvent(aEventType
, xpcAcc
, doc
, node
, fromUser
);
298 nsCoreUtils::DispatchAccEvent(std::move(event
));
303 mozilla::ipc::IPCResult
DocAccessibleParent::RecvStateChangeEvent(
304 const uint64_t& aID
, const uint64_t& aState
, const bool& aEnabled
) {
309 RemoteAccessible
* target
= GetAccessible(aID
);
311 NS_ERROR("we don't know about the target of a state change event!");
315 if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
316 target
->UpdateStateCache(aState
, aEnabled
);
318 ProxyStateChangeEvent(target
, aState
, aEnabled
);
320 if (!nsCoreUtils::AccEventObserversExist()) {
324 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
325 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
326 uint32_t type
= nsIAccessibleEvent::EVENT_STATE_CHANGE
;
328 uint32_t state
= nsAccUtils::To32States(aState
, &extra
);
329 bool fromUser
= true; // XXX fix this
330 nsINode
* node
= nullptr; // XXX can we do better?
331 RefPtr
<xpcAccStateChangeEvent
> event
= new xpcAccStateChangeEvent(
332 type
, xpcAcc
, doc
, node
, fromUser
, state
, extra
, aEnabled
);
333 nsCoreUtils::DispatchAccEvent(std::move(event
));
338 mozilla::ipc::IPCResult
DocAccessibleParent::RecvCaretMoveEvent(
341 const LayoutDeviceIntRect
& aCaretRect
,
342 #endif // defined (XP_WIN)
343 const int32_t& aOffset
, const bool& aIsSelectionCollapsed
,
344 const bool& aIsAtEndOfLine
) {
349 RemoteAccessible
* proxy
= GetAccessible(aID
);
351 NS_ERROR("unknown caret move event target!");
356 mCaretOffset
= aOffset
;
357 mIsCaretAtEndOfLine
= aIsAtEndOfLine
;
358 if (aIsSelectionCollapsed
) {
359 // We don't fire selection events for collapsed selections, but we need to
360 // ensure we don't have a stale cached selection; e.g. when selecting
361 // forward and then unselecting backward.
362 mTextSelections
.ClearAndRetainStorage();
363 mTextSelections
.AppendElement(TextRangeData(aID
, aID
, aOffset
, aOffset
));
367 ProxyCaretMoveEvent(proxy
, aCaretRect
);
369 ProxyCaretMoveEvent(proxy
, aOffset
, aIsSelectionCollapsed
);
372 if (!nsCoreUtils::AccEventObserversExist()) {
376 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(proxy
);
377 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
378 nsINode
* node
= nullptr;
379 bool fromUser
= true; // XXX fix me
380 uint32_t type
= nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
;
381 RefPtr
<xpcAccCaretMoveEvent
> event
=
382 new xpcAccCaretMoveEvent(type
, xpcAcc
, doc
, node
, fromUser
, aOffset
,
383 aIsSelectionCollapsed
, aIsAtEndOfLine
);
384 nsCoreUtils::DispatchAccEvent(std::move(event
));
389 mozilla::ipc::IPCResult
DocAccessibleParent::RecvTextChangeEvent(
390 const uint64_t& aID
, const nsString
& aStr
, const int32_t& aStart
,
391 const uint32_t& aLen
, const bool& aIsInsert
, const bool& aFromUser
) {
396 RemoteAccessible
* target
= GetAccessible(aID
);
398 NS_ERROR("text change event target is unknown!");
402 ProxyTextChangeEvent(target
, aStr
, aStart
, aLen
, aIsInsert
, aFromUser
);
404 if (!nsCoreUtils::AccEventObserversExist()) {
408 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
409 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
410 uint32_t type
= aIsInsert
? nsIAccessibleEvent::EVENT_TEXT_INSERTED
411 : nsIAccessibleEvent::EVENT_TEXT_REMOVED
;
412 nsINode
* node
= nullptr;
413 RefPtr
<xpcAccTextChangeEvent
> event
= new xpcAccTextChangeEvent(
414 type
, xpcAcc
, doc
, node
, aFromUser
, aStart
, aLen
, aIsInsert
, aStr
);
415 nsCoreUtils::DispatchAccEvent(std::move(event
));
422 mozilla::ipc::IPCResult
DocAccessibleParent::RecvSyncTextChangeEvent(
423 const uint64_t& aID
, const nsString
& aStr
, const int32_t& aStart
,
424 const uint32_t& aLen
, const bool& aIsInsert
, const bool& aFromUser
) {
425 return RecvTextChangeEvent(aID
, aStr
, aStart
, aLen
, aIsInsert
, aFromUser
);
428 #endif // defined(XP_WIN)
430 mozilla::ipc::IPCResult
DocAccessibleParent::RecvSelectionEvent(
431 const uint64_t& aID
, const uint64_t& aWidgetID
, const uint32_t& aType
) {
436 RemoteAccessible
* target
= GetAccessible(aID
);
437 RemoteAccessible
* widget
= GetAccessible(aWidgetID
);
438 if (!target
|| !widget
) {
439 NS_ERROR("invalid id in selection event");
443 ProxySelectionEvent(target
, widget
, aType
);
444 if (!nsCoreUtils::AccEventObserversExist()) {
447 xpcAccessibleGeneric
* xpcTarget
= GetXPCAccessible(target
);
448 xpcAccessibleDocument
* xpcDoc
= GetAccService()->GetXPCDocument(this);
449 RefPtr
<xpcAccEvent
> event
=
450 new xpcAccEvent(aType
, xpcTarget
, xpcDoc
, nullptr, false);
451 nsCoreUtils::DispatchAccEvent(std::move(event
));
456 mozilla::ipc::IPCResult
DocAccessibleParent::RecvVirtualCursorChangeEvent(
457 const uint64_t& aID
, const uint64_t& aOldPositionID
,
458 const int32_t& aOldStartOffset
, const int32_t& aOldEndOffset
,
459 const uint64_t& aNewPositionID
, const int32_t& aNewStartOffset
,
460 const int32_t& aNewEndOffset
, const int16_t& aReason
,
461 const int16_t& aBoundaryType
, const bool& aFromUser
) {
466 RemoteAccessible
* target
= GetAccessible(aID
);
467 RemoteAccessible
* oldPosition
= GetAccessible(aOldPositionID
);
468 RemoteAccessible
* newPosition
= GetAccessible(aNewPositionID
);
471 NS_ERROR("no proxy for event!");
476 ProxyVirtualCursorChangeEvent(
477 target
, oldPosition
, aOldStartOffset
, aOldEndOffset
, newPosition
,
478 aNewStartOffset
, aNewEndOffset
, aReason
, aBoundaryType
, aFromUser
);
481 if (!nsCoreUtils::AccEventObserversExist()) {
485 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
486 RefPtr
<xpcAccVirtualCursorChangeEvent
> event
=
487 new xpcAccVirtualCursorChangeEvent(
488 nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
,
489 GetXPCAccessible(target
), doc
, nullptr, aFromUser
,
490 GetXPCAccessible(oldPosition
), aOldStartOffset
, aOldEndOffset
,
491 GetXPCAccessible(newPosition
), aNewStartOffset
, aNewEndOffset
,
492 aBoundaryType
, aReason
);
493 nsCoreUtils::DispatchAccEvent(std::move(event
));
498 mozilla::ipc::IPCResult
DocAccessibleParent::RecvScrollingEvent(
499 const uint64_t& aID
, const uint64_t& aType
, const uint32_t& aScrollX
,
500 const uint32_t& aScrollY
, const uint32_t& aMaxScrollX
,
501 const uint32_t& aMaxScrollY
) {
506 RemoteAccessible
* target
= GetAccessible(aID
);
508 NS_ERROR("no proxy for event!");
513 ProxyScrollingEvent(target
, aType
, aScrollX
, aScrollY
, aMaxScrollX
,
516 ProxyEvent(target
, aType
);
519 if (!nsCoreUtils::AccEventObserversExist()) {
523 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
524 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
525 nsINode
* node
= nullptr;
526 bool fromUser
= true; // XXX: Determine if this was from user input.
527 RefPtr
<xpcAccScrollingEvent
> event
=
528 new xpcAccScrollingEvent(aType
, xpcAcc
, doc
, node
, fromUser
, aScrollX
,
529 aScrollY
, aMaxScrollX
, aMaxScrollY
);
530 nsCoreUtils::DispatchAccEvent(std::move(event
));
535 mozilla::ipc::IPCResult
DocAccessibleParent::RecvCache(
536 const mozilla::a11y::CacheUpdateType
& aUpdateType
,
537 nsTArray
<CacheData
>&& aData
, const bool& aFinal
) {
542 for (auto& entry
: aData
) {
543 RemoteAccessible
* remote
= GetAccessible(entry
.ID());
545 MOZ_ASSERT_UNREACHABLE("No remote found!");
549 remote
->ApplyCache(aUpdateType
, entry
.Fields());
552 if (nsCOMPtr
<nsIObserverService
> obsService
=
553 services::GetObserverService()) {
554 obsService
->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC
, nullptr);
560 mozilla::ipc::IPCResult
DocAccessibleParent::RecvAccessiblesWillMove(
561 nsTArray
<uint64_t>&& aIDs
) {
562 for (uint64_t id
: aIDs
) {
563 mMovingIDs
.EnsureInserted(id
);
569 mozilla::ipc::IPCResult
DocAccessibleParent::RecvAnnouncementEvent(
570 const uint64_t& aID
, const nsString
& aAnnouncement
,
571 const uint16_t& aPriority
) {
576 RemoteAccessible
* target
= GetAccessible(aID
);
578 NS_ERROR("no proxy for event!");
582 # if defined(ANDROID)
583 ProxyAnnouncementEvent(target
, aAnnouncement
, aPriority
);
586 if (!nsCoreUtils::AccEventObserversExist()) {
590 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
591 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
592 RefPtr
<xpcAccAnnouncementEvent
> event
= new xpcAccAnnouncementEvent(
593 nsIAccessibleEvent::EVENT_ANNOUNCEMENT
, xpcAcc
, doc
, nullptr, false,
594 aAnnouncement
, aPriority
);
595 nsCoreUtils::DispatchAccEvent(std::move(event
));
599 #endif // !defined(XP_WIN)
601 mozilla::ipc::IPCResult
DocAccessibleParent::RecvTextSelectionChangeEvent(
602 const uint64_t& aID
, nsTArray
<TextRangeData
>&& aSelection
) {
607 RemoteAccessible
* target
= GetAccessible(aID
);
609 NS_ERROR("no proxy for event!");
613 if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
614 mTextSelections
.ClearAndRetainStorage();
615 mTextSelections
.AppendElements(aSelection
);
618 #ifdef MOZ_WIDGET_COCOA
619 ProxyTextSelectionChangeEvent(target
, aSelection
);
621 ProxyEvent(target
, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
);
624 if (!nsCoreUtils::AccEventObserversExist()) {
627 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(target
);
628 xpcAccessibleDocument
* doc
= nsAccessibilityService::GetXPCDocument(this);
629 nsINode
* node
= nullptr;
630 bool fromUser
= true; // XXX fix me
631 RefPtr
<xpcAccEvent
> event
=
632 new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
, xpcAcc
,
633 doc
, node
, fromUser
);
634 nsCoreUtils::DispatchAccEvent(std::move(event
));
639 mozilla::ipc::IPCResult
DocAccessibleParent::RecvRoleChangedEvent(
640 const a11y::role
& aRole
) {
647 #ifdef MOZ_WIDGET_COCOA
648 ProxyRoleChangedEvent(this, aRole
);
654 mozilla::ipc::IPCResult
DocAccessibleParent::RecvBindChildDoc(
655 PDocAccessibleParent
* aChildDoc
, const uint64_t& aID
) {
656 // One document should never directly be the child of another.
657 // We should always have at least an outer doc accessible in between.
659 if (!aID
) return IPC_FAIL(this, "ID is 0!");
665 MOZ_ASSERT(CheckDocTree());
667 auto childDoc
= static_cast<DocAccessibleParent
*>(aChildDoc
);
669 ipc::IPCResult result
= AddChildDoc(childDoc
, aID
, false);
671 MOZ_ASSERT(CheckDocTree());
683 ipc::IPCResult
DocAccessibleParent::AddChildDoc(DocAccessibleParent
* aChildDoc
,
686 // We do not use GetAccessible here because we want to be sure to not get the
688 ProxyEntry
* e
= mAccessibles
.GetEntry(aParentID
);
690 MOZ_DIAGNOSTIC_ASSERT(false, "Binding to nonexistent proxy!");
691 return IPC_FAIL(this, "binding to nonexistant proxy!");
694 RemoteAccessible
* outerDoc
= e
->mProxy
;
695 MOZ_ASSERT(outerDoc
);
697 // OuterDocAccessibles are expected to only have a document as a child.
698 // However for compatibility we tolerate replacing one document with another
700 if (!outerDoc
->IsOuterDoc() || outerDoc
->ChildCount() > 1 ||
701 (outerDoc
->ChildCount() == 1 && !outerDoc
->RemoteChildAt(0)->IsDoc())) {
702 MOZ_DIAGNOSTIC_ASSERT(false,
703 "Binding to parent that isn't a valid OuterDoc!");
704 return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!");
707 if (outerDoc
->ChildCount() == 1) {
708 MOZ_ASSERT(outerDoc
->RemoteChildAt(0)->AsDoc());
709 outerDoc
->RemoteChildAt(0)->AsDoc()->Unbind();
712 aChildDoc
->SetParent(outerDoc
);
713 outerDoc
->SetChildDoc(aChildDoc
);
714 mChildDocs
.AppendElement(aChildDoc
->mActorID
);
715 aChildDoc
->mParentDoc
= mActorID
;
718 ProxyCreated(aChildDoc
);
721 if (aChildDoc
->IsTopLevelInContentProcess()) {
722 // aChildDoc is an embedded document in a different content process to
724 auto embeddedBrowser
=
725 static_cast<dom::BrowserParent
*>(aChildDoc
->Manager());
726 dom::BrowserBridgeParent
* bridge
=
727 embeddedBrowser
->GetBrowserBridgeParent();
730 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
731 // Send a COM proxy for the embedded document to the embedder process
732 // hosting the iframe. This will be returned as the child of the
733 // embedder OuterDocAccessible.
734 RefPtr
<IDispatch
> docAcc
;
735 aChildDoc
->GetCOMInterface((void**)getter_AddRefs(docAcc
));
738 RefPtr
<IDispatch
> docWrapped(
739 mscom::PassthruProxy::Wrap
<IDispatch
>(WrapNotNull(docAcc
)));
740 IDispatchHolder::COMPtrType
docPtr(
741 mscom::ToProxyUniquePtr(std::move(docWrapped
)));
742 IDispatchHolder
docHolder(std::move(docPtr
));
743 if (bridge
->SendSetEmbeddedDocAccessibleCOMProxy(docHolder
)) {
744 # if defined(MOZ_SANDBOX)
745 aChildDoc
->mDocProxyStream
= docHolder
.GetPreservedStream();
746 # endif // defined(MOZ_SANDBOX)
749 // Send a COM proxy for the embedder OuterDocAccessible to the embedded
750 // document process. This will be returned as the parent of the
751 // embedded document.
752 aChildDoc
->SendParentCOMProxy(outerDoc
);
753 if (nsWinUtils::IsWindowEmulationStarted()) {
754 // The embedded document should use the same emulated window handle as
755 // its embedder. It will return the embedder document (not a window
756 // accessible) as the parent accessible, so we pass a null accessible
757 // when sending the window to the embedded document.
758 Unused
<< aChildDoc
->SendEmulatedWindow(
759 reinterpret_cast<uintptr_t>(mEmulatedWindowHandle
), nullptr);
761 // Send a COM proxy for the top level document to the embedded document
762 // process. This will be returned when the client calls QueryService
763 // with SID_IAccessibleContentDocument on an accessible in the embedded
765 DocAccessibleParent
* topDoc
= this;
766 while (DocAccessibleParent
* parentDoc
= topDoc
->ParentDoc()) {
769 MOZ_ASSERT(topDoc
&& topDoc
->IsTopLevel());
770 RefPtr
<IAccessible
> topDocAcc
;
771 topDoc
->GetCOMInterface((void**)getter_AddRefs(topDocAcc
));
772 MOZ_ASSERT(topDocAcc
);
774 RefPtr
<IAccessible
> topDocWrapped(
775 mscom::PassthruProxy::Wrap
<IAccessible
>(WrapNotNull(topDocAcc
)));
776 IAccessibleHolder::COMPtrType
topDocPtr(
777 mscom::ToProxyUniquePtr(std::move(topDocWrapped
)));
778 IAccessibleHolder
topDocHolder(std::move(topDocPtr
));
779 if (aChildDoc
->SendTopLevelDocCOMProxy(topDocHolder
)) {
780 # if defined(MOZ_SANDBOX)
781 aChildDoc
->mTopLevelDocProxyStream
=
782 topDocHolder
.GetPreservedStream();
783 # endif // defined(MOZ_SANDBOX)
787 if (nsWinUtils::IsWindowEmulationStarted()) {
788 aChildDoc
->SetEmulatedWindowHandle(mEmulatedWindowHandle
);
790 #endif // defined(XP_WIN)
791 // We need to fire a reorder event on the outer doc accessible.
792 // For same-process documents, this is fired by the content process, but
793 // this isn't possible when the document is in a different process to its
795 // RecvEvent fires both OS and XPCOM events.
796 Unused
<< RecvEvent(aParentID
, nsIAccessibleEvent::EVENT_REORDER
);
803 ipc::IPCResult
DocAccessibleParent::AddChildDoc(
804 dom::BrowserBridgeParent
* aBridge
) {
805 MOZ_ASSERT(aBridge
->GetEmbedderAccessibleDoc() == this);
806 uint64_t parentId
= aBridge
->GetEmbedderAccessibleId();
807 MOZ_ASSERT(parentId
);
808 if (!mAccessibles
.GetEntry(parentId
)) {
809 // Sometimes, this gets called before the embedder sends us the
810 // OuterDocAccessible. We must add the child when the OuterDocAccessible
811 // gets created later.
812 mPendingOOPChildDocs
.Insert(aBridge
);
815 return AddChildDoc(aBridge
->GetDocAccessibleParent(), parentId
,
816 /* aCreating */ false);
819 mozilla::ipc::IPCResult
DocAccessibleParent::RecvShutdown() {
822 auto mgr
= static_cast<dom::BrowserParent
*>(Manager());
823 if (!mgr
->IsDestroyed()) {
824 if (!PDocAccessibleParent::Send__delete__(this)) {
825 return IPC_FAIL_NO_REASON(mgr
);
832 void DocAccessibleParent::Destroy() {
833 // If we are already shutdown that is because our containing tab parent is
834 // shutting down in which case we don't need to do anything.
841 MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID
));
842 uint32_t childDocCount
= mChildDocs
.Length();
843 for (uint32_t i
= 0; i
< childDocCount
; i
++) {
844 for (uint32_t j
= i
+ 1; j
< childDocCount
; j
++) {
845 MOZ_DIAGNOSTIC_ASSERT(mChildDocs
[i
] != mChildDocs
[j
]);
849 // XXX This indirection through the hash map of live documents shouldn't be
850 // needed, but be paranoid for now.
851 int32_t actorID
= mActorID
;
852 for (uint32_t i
= childDocCount
- 1; i
< childDocCount
; i
--) {
853 DocAccessibleParent
* thisDoc
= LiveDocs().Get(actorID
);
859 thisDoc
->ChildDocAt(i
)->Destroy();
862 for (auto iter
= mAccessibles
.Iter(); !iter
.Done(); iter
.Next()) {
863 MOZ_ASSERT(iter
.Get()->mProxy
!= this);
864 ProxyDestroyed(iter
.Get()->mProxy
);
868 DocAccessibleParent
* thisDoc
= LiveDocs().Get(actorID
);
874 // The code above should have already completely cleared these, but to be
875 // extra safe make sure they are cleared here.
876 thisDoc
->mAccessibles
.Clear();
877 thisDoc
->mChildDocs
.Clear();
879 DocManager::NotifyOfRemoteDocShutdown(thisDoc
);
880 thisDoc
= LiveDocs().Get(actorID
);
886 ProxyDestroyed(thisDoc
);
887 thisDoc
= LiveDocs().Get(actorID
);
893 if (DocAccessibleParent
* parentDoc
= thisDoc
->ParentDoc()) {
894 parentDoc
->RemoveChildDoc(thisDoc
);
895 } else if (IsTopLevel()) {
896 GetAccService()->RemoteDocShutdown(this);
900 DocAccessibleParent
* DocAccessibleParent::ParentDoc() const {
901 if (mParentDoc
== kNoParentDoc
) {
905 return LiveDocs().Get(mParentDoc
);
908 bool DocAccessibleParent::CheckDocTree() const {
909 size_t childDocs
= mChildDocs
.Length();
910 for (size_t i
= 0; i
< childDocs
; i
++) {
911 const DocAccessibleParent
* childDoc
= ChildDocAt(i
);
912 if (!childDoc
|| childDoc
->ParentDoc() != this) return false;
914 if (!childDoc
->CheckDocTree()) {
922 xpcAccessibleGeneric
* DocAccessibleParent::GetXPCAccessible(
923 RemoteAccessible
* aProxy
) {
924 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
927 return doc
->GetAccessible(aProxy
);
931 void DocAccessibleParent::MaybeInitWindowEmulation() {
932 if (!nsWinUtils::IsWindowEmulationStarted()) {
936 // XXX get the bounds from the browserParent instead of poking at accessibles
937 // which might not exist yet.
938 LocalAccessible
* outerDoc
= OuterDocOfRemoteBrowser();
943 RootAccessible
* rootDocument
= outerDoc
->RootAccessible();
944 MOZ_ASSERT(rootDocument
);
946 bool isActive
= true;
947 LayoutDeviceIntRect
rect(CW_USEDEFAULT
, CW_USEDEFAULT
, 0, 0);
948 if (Compatibility::IsDolphin()) {
950 LayoutDeviceIntRect rootRect
= rootDocument
->Bounds();
951 rect
.MoveToX(rootRect
.X() - rect
.X());
952 rect
.MoveToY(rect
.Y() - rootRect
.Y());
954 auto browserParent
= static_cast<dom::BrowserParent
*>(Manager());
955 isActive
= browserParent
->GetDocShellIsActive();
958 // onCreate is guaranteed to be called synchronously by
959 // nsWinUtils::CreateNativeWindow, so this reference isn't really necessary.
960 // However, static analysis complains without it.
961 RefPtr
<DocAccessibleParent
> thisRef
= this;
962 nsWinUtils::NativeWindowCreateProc
onCreate([thisRef
](HWND aHwnd
) -> void {
963 IDispatchHolder hWndAccHolder
;
965 ::SetPropW(aHwnd
, kPropNameDocAccParent
,
966 reinterpret_cast<HANDLE
>(thisRef
.get()));
968 thisRef
->SetEmulatedWindowHandle(aHwnd
);
970 RefPtr
<IAccessible
> hwndAcc
;
971 if (SUCCEEDED(::AccessibleObjectFromWindow(
972 aHwnd
, OBJID_WINDOW
, IID_IAccessible
, getter_AddRefs(hwndAcc
)))) {
973 RefPtr
<IDispatch
> wrapped(
974 mscom::PassthruProxy::Wrap
<IDispatch
>(WrapNotNull(hwndAcc
)));
975 hWndAccHolder
.Set(IDispatchHolder::COMPtrType(
976 mscom::ToProxyUniquePtr(std::move(wrapped
))));
979 Unused
<< thisRef
->SendEmulatedWindow(
980 reinterpret_cast<uintptr_t>(thisRef
->mEmulatedWindowHandle
),
984 HWND parentWnd
= reinterpret_cast<HWND
>(rootDocument
->GetNativeWindow());
985 DebugOnly
<HWND
> hWnd
= nsWinUtils::CreateNativeWindow(
986 kClassNameTabContent
, parentWnd
, rect
.X(), rect
.Y(), rect
.Width(),
987 rect
.Height(), isActive
, &onCreate
);
991 void DocAccessibleParent::SendParentCOMProxy(Accessible
* aOuterDoc
) {
992 // Make sure that we're not racing with a tab shutdown
993 auto tab
= static_cast<dom::BrowserParent
*>(Manager());
995 if (tab
->IsDestroyed()) {
999 RefPtr
<IDispatch
> nativeAcc
=
1000 already_AddRefed
<IDispatch
>(MsaaAccessible::NativeAccessible(aOuterDoc
));
1001 if (NS_WARN_IF(!nativeAcc
)) {
1002 // Couldn't get a COM proxy for the outer doc. That probably means it died,
1003 // but the parent process hasn't received a message to remove it from the
1004 // RemoteAccessible tree yet.
1008 RefPtr
<IDispatch
> wrapped(
1009 mscom::PassthruProxy::Wrap
<IDispatch
>(WrapNotNull(nativeAcc
)));
1011 IDispatchHolder::COMPtrType
ptr(mscom::ToProxyUniquePtr(std::move(wrapped
)));
1012 IDispatchHolder
holder(std::move(ptr
));
1013 if (!PDocAccessibleParent::SendParentCOMProxy(holder
)) {
1017 # if defined(MOZ_SANDBOX)
1018 mParentProxyStream
= holder
.GetPreservedStream();
1019 # endif // defined(MOZ_SANDBOX)
1022 void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle
) {
1023 if (!aWindowHandle
&& mEmulatedWindowHandle
&& IsTopLevel()) {
1024 ::DestroyWindow(mEmulatedWindowHandle
);
1026 mEmulatedWindowHandle
= aWindowHandle
;
1029 mozilla::ipc::IPCResult
DocAccessibleParent::RecvFocusEvent(
1030 const uint64_t& aID
, const LayoutDeviceIntRect
& aCaretRect
) {
1035 RemoteAccessible
* proxy
= GetAccessible(aID
);
1037 NS_ERROR("no proxy for event!");
1042 ProxyFocusEvent(proxy
, aCaretRect
);
1044 if (!nsCoreUtils::AccEventObserversExist()) {
1048 xpcAccessibleGeneric
* xpcAcc
= GetXPCAccessible(proxy
);
1049 xpcAccessibleDocument
* doc
= GetAccService()->GetXPCDocument(this);
1050 nsINode
* node
= nullptr;
1051 bool fromUser
= true; // XXX fix me
1052 RefPtr
<xpcAccEvent
> event
= new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS
,
1053 xpcAcc
, doc
, node
, fromUser
);
1054 nsCoreUtils::DispatchAccEvent(std::move(event
));
1059 #endif // defined(XP_WIN)
1061 #if !defined(XP_WIN)
1062 mozilla::ipc::IPCResult
DocAccessibleParent::RecvBatch(
1063 const uint64_t& aBatchType
, nsTArray
<BatchData
>&& aData
) {
1064 // Only do something in Android. We can't ifdef the entire protocol out in
1065 // the ipdl because it doesn't allow preprocessing.
1066 # if defined(ANDROID)
1070 nsTArray
<RemoteAccessible
*> proxies(aData
.Length());
1071 for (size_t i
= 0; i
< aData
.Length(); i
++) {
1072 DocAccessibleParent
* doc
= static_cast<DocAccessibleParent
*>(
1073 aData
.ElementAt(i
).Document().get_PDocAccessibleParent());
1076 if (doc
->IsShutdown()) {
1080 RemoteAccessible
* proxy
= doc
->GetAccessible(aData
.ElementAt(i
).ID());
1082 MOZ_ASSERT_UNREACHABLE("No proxy found!");
1086 proxies
.AppendElement(proxy
);
1088 ProxyBatch(this, aBatchType
, proxies
, aData
);
1089 # endif // defined(XP_WIN)
1093 bool DocAccessibleParent::DeallocPDocAccessiblePlatformExtParent(
1094 PDocAccessiblePlatformExtParent
* aActor
) {
1099 PDocAccessiblePlatformExtParent
*
1100 DocAccessibleParent::AllocPDocAccessiblePlatformExtParent() {
1101 return new DocAccessiblePlatformExtParent();
1104 DocAccessiblePlatformExtParent
* DocAccessibleParent::GetPlatformExtension() {
1105 return static_cast<DocAccessiblePlatformExtParent
*>(
1106 SingleManagedOrNull(ManagedPDocAccessiblePlatformExtParent()));
1109 #endif // !defined(XP_WIN)
1111 void DocAccessibleParent::SelectionRanges(nsTArray
<TextRange
>* aRanges
) const {
1112 for (const auto& data
: mTextSelections
) {
1113 aRanges
->AppendElement(
1114 TextRange(const_cast<DocAccessibleParent
*>(this),
1115 const_cast<RemoteAccessible
*>(GetAccessible(data
.StartID())),
1117 const_cast<RemoteAccessible
*>(GetAccessible(data
.EndID())),
1123 } // namespace mozilla