Bug 1687263: part 4) Defer and in some cases avoid removing spellchecking-ranges...
[gecko.git] / accessible / ipc / DocAccessibleParent.cpp
blob89f226841633382e2712e98d4266a1fdab3900da
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 "xpcAccessibleDocument.h"
12 #include "xpcAccEvents.h"
13 #include "nsAccUtils.h"
14 #include "TextRange.h"
16 #if defined(XP_WIN)
17 # include "AccessibleWrap.h"
18 # include "Compatibility.h"
19 # include "mozilla/mscom/PassthruProxy.h"
20 # include "mozilla/mscom/Ptr.h"
21 # include "nsWinUtils.h"
22 # include "RootAccessible.h"
23 #else
24 # include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
25 #endif
27 namespace mozilla {
29 #if defined(XP_WIN)
30 namespace mscom {
31 namespace detail {
32 // Needed by mscom::PassthruProxy::Wrap<IAccessible>.
33 template <>
34 struct VTableSizer<IAccessible> {
35 // 3 methods in IUnknown + 4 in IDispatch + 21 in IAccessible = 28 total
36 enum { Size = 28 };
38 } // namespace detail
39 } // namespace mscom
40 #endif // defined (XP_WIN)
42 namespace a11y {
43 uint64_t DocAccessibleParent::sMaxDocID = 0;
45 mozilla::ipc::IPCResult DocAccessibleParent::RecvShowEvent(
46 const ShowEventData& aData, const bool& aFromUser) {
47 if (mShutdown) return IPC_OK();
49 MOZ_ASSERT(CheckDocTree());
51 if (aData.NewTree().IsEmpty()) {
52 return IPC_FAIL(this, "No children being added");
55 RemoteAccessible* parent = GetAccessible(aData.ID());
57 // XXX This should really never happen, but sometimes we fail to fire the
58 // required show events.
59 if (!parent) {
60 NS_ERROR("adding child to unknown accessible");
61 #ifdef DEBUG
62 return IPC_FAIL(this, "unknown parent accessible");
63 #else
64 return IPC_OK();
65 #endif
68 uint32_t newChildIdx = aData.Idx();
69 if (newChildIdx > parent->ChildCount()) {
70 NS_ERROR("invalid index to add child at");
71 #ifdef DEBUG
72 return IPC_FAIL(this, "invalid index");
73 #else
74 return IPC_OK();
75 #endif
78 uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
79 MOZ_ASSERT(consumed == aData.NewTree().Length());
81 // XXX This shouldn't happen, but if we failed to add children then the below
82 // is pointless and can crash.
83 if (!consumed) {
84 return IPC_FAIL(this, "failed to add children");
87 #ifdef DEBUG
88 for (uint32_t i = 0; i < consumed; i++) {
89 uint64_t id = aData.NewTree()[i].ID();
90 MOZ_ASSERT(mAccessibles.GetEntry(id));
92 #endif
94 MOZ_ASSERT(CheckDocTree());
96 // Just update, no events.
97 if (aData.EventSuppressed()) {
98 return IPC_OK();
101 RemoteAccessible* target = parent->RemoteChildAt(newChildIdx);
102 ProxyShowHideEvent(target, parent, true, aFromUser);
104 if (!nsCoreUtils::AccEventObserversExist()) {
105 return IPC_OK();
108 uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
109 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
110 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
111 nsINode* node = nullptr;
112 RefPtr<xpcAccEvent> event =
113 new xpcAccEvent(type, xpcAcc, doc, node, aFromUser);
114 nsCoreUtils::DispatchAccEvent(std::move(event));
116 return IPC_OK();
119 uint32_t DocAccessibleParent::AddSubtree(
120 RemoteAccessible* aParent, const nsTArray<a11y::AccessibleData>& aNewTree,
121 uint32_t aIdx, uint32_t aIdxInParent) {
122 if (aNewTree.Length() <= aIdx) {
123 NS_ERROR("bad index in serialized tree!");
124 return 0;
127 const AccessibleData& newChild = aNewTree[aIdx];
129 if (mAccessibles.Contains(newChild.ID())) {
130 NS_ERROR("ID already in use");
131 return 0;
134 RemoteAccessible* newProxy = new RemoteAccessible(
135 newChild.ID(), aParent, this, newChild.Role(), newChild.Type(),
136 newChild.GenericTypes(), newChild.RoleMapEntryIndex());
138 aParent->AddChildAt(aIdxInParent, newProxy);
139 mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
140 ProxyCreated(newProxy);
142 #if defined(XP_WIN)
143 WrapperFor(newProxy)->GetMsaa()->SetID(newChild.MsaaID());
144 #endif
146 for (uint32_t index = 0, len = mPendingChildDocs.Length(); index < len;
147 ++index) {
148 PendingChildDoc& pending = mPendingChildDocs[index];
149 if (pending.mParentID == newChild.ID()) {
150 if (!pending.mChildDoc->IsShutdown()) {
151 AddChildDoc(pending.mChildDoc, pending.mParentID, false);
153 mPendingChildDocs.RemoveElementAt(index);
154 break;
157 DebugOnly<bool> isOuterDoc = newProxy->ChildCount() == 1;
159 uint32_t accessibles = 1;
160 uint32_t kids = newChild.ChildrenCount();
161 for (uint32_t i = 0; i < kids; i++) {
162 uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
163 if (!consumed) return 0;
165 accessibles += consumed;
168 MOZ_ASSERT((isOuterDoc && kids == 0) || newProxy->ChildCount() == kids);
170 return accessibles;
173 mozilla::ipc::IPCResult DocAccessibleParent::RecvHideEvent(
174 const uint64_t& aRootID, const bool& aFromUser) {
175 if (mShutdown) return IPC_OK();
177 MOZ_ASSERT(CheckDocTree());
179 // We shouldn't actually need this because mAccessibles shouldn't have an
180 // entry for the document itself, but it doesn't hurt to be explicit.
181 if (!aRootID) {
182 return IPC_FAIL(this, "Trying to hide entire document?");
185 ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
186 if (!rootEntry) {
187 NS_ERROR("invalid root being removed!");
188 return IPC_OK();
191 RemoteAccessible* root = rootEntry->mProxy;
192 if (!root) {
193 NS_ERROR("invalid root being removed!");
194 return IPC_OK();
197 RemoteAccessible* parent = root->RemoteParent();
198 ProxyShowHideEvent(root, parent, false, aFromUser);
200 RefPtr<xpcAccHideEvent> event = nullptr;
201 if (nsCoreUtils::AccEventObserversExist()) {
202 uint32_t type = nsIAccessibleEvent::EVENT_HIDE;
203 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
204 xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent);
205 RemoteAccessible* next = root->RemoteNextSibling();
206 xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
207 RemoteAccessible* prev = root->RemotePrevSibling();
208 xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr;
209 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
210 nsINode* node = nullptr;
211 event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent,
212 xpcNext, xpcPrev);
215 parent->RemoveChild(root);
216 root->Shutdown();
218 MOZ_ASSERT(CheckDocTree());
220 if (event) {
221 nsCoreUtils::DispatchAccEvent(std::move(event));
224 return IPC_OK();
227 mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(
228 const uint64_t& aID, const uint32_t& aEventType) {
229 if (mShutdown) {
230 return IPC_OK();
233 RemoteAccessible* proxy = GetAccessible(aID);
234 if (!proxy) {
235 NS_ERROR("no proxy for event!");
236 return IPC_OK();
239 ProxyEvent(proxy, aEventType);
241 if (!nsCoreUtils::AccEventObserversExist()) {
242 return IPC_OK();
245 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
246 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
247 nsINode* node = nullptr;
248 bool fromUser = true; // XXX fix me
249 RefPtr<xpcAccEvent> event =
250 new xpcAccEvent(aEventType, xpcAcc, doc, node, fromUser);
251 nsCoreUtils::DispatchAccEvent(std::move(event));
253 return IPC_OK();
256 mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(
257 const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) {
258 if (mShutdown) {
259 return IPC_OK();
262 RemoteAccessible* target = GetAccessible(aID);
263 if (!target) {
264 NS_ERROR("we don't know about the target of a state change event!");
265 return IPC_OK();
268 ProxyStateChangeEvent(target, aState, aEnabled);
270 if (!nsCoreUtils::AccEventObserversExist()) {
271 return IPC_OK();
274 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
275 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
276 uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
277 bool extra;
278 uint32_t state = nsAccUtils::To32States(aState, &extra);
279 bool fromUser = true; // XXX fix this
280 nsINode* node = nullptr; // XXX can we do better?
281 RefPtr<xpcAccStateChangeEvent> event = new xpcAccStateChangeEvent(
282 type, xpcAcc, doc, node, fromUser, state, extra, aEnabled);
283 nsCoreUtils::DispatchAccEvent(std::move(event));
285 return IPC_OK();
288 mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
289 const uint64_t& aID,
290 #if defined(XP_WIN)
291 const LayoutDeviceIntRect& aCaretRect,
292 #endif // defined (XP_WIN)
293 const int32_t& aOffset, const bool& aIsSelectionCollapsed) {
294 if (mShutdown) {
295 return IPC_OK();
298 RemoteAccessible* proxy = GetAccessible(aID);
299 if (!proxy) {
300 NS_ERROR("unknown caret move event target!");
301 return IPC_OK();
304 #if defined(XP_WIN)
305 ProxyCaretMoveEvent(proxy, aCaretRect);
306 #else
307 ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed);
308 #endif
310 if (!nsCoreUtils::AccEventObserversExist()) {
311 return IPC_OK();
314 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
315 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
316 nsINode* node = nullptr;
317 bool fromUser = true; // XXX fix me
318 uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
319 RefPtr<xpcAccCaretMoveEvent> event = new xpcAccCaretMoveEvent(
320 type, xpcAcc, doc, node, fromUser, aOffset, aIsSelectionCollapsed);
321 nsCoreUtils::DispatchAccEvent(std::move(event));
323 return IPC_OK();
326 mozilla::ipc::IPCResult DocAccessibleParent::RecvTextChangeEvent(
327 const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
328 const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
329 if (mShutdown) {
330 return IPC_OK();
333 RemoteAccessible* target = GetAccessible(aID);
334 if (!target) {
335 NS_ERROR("text change event target is unknown!");
336 return IPC_OK();
339 ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
341 if (!nsCoreUtils::AccEventObserversExist()) {
342 return IPC_OK();
345 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
346 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
347 uint32_t type = aIsInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED
348 : nsIAccessibleEvent::EVENT_TEXT_REMOVED;
349 nsINode* node = nullptr;
350 RefPtr<xpcAccTextChangeEvent> event = new xpcAccTextChangeEvent(
351 type, xpcAcc, doc, node, aFromUser, aStart, aLen, aIsInsert, aStr);
352 nsCoreUtils::DispatchAccEvent(std::move(event));
354 return IPC_OK();
357 #if defined(XP_WIN)
359 mozilla::ipc::IPCResult DocAccessibleParent::RecvSyncTextChangeEvent(
360 const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
361 const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
362 return RecvTextChangeEvent(aID, aStr, aStart, aLen, aIsInsert, aFromUser);
365 #endif // defined(XP_WIN)
367 mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent(
368 const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) {
369 if (mShutdown) {
370 return IPC_OK();
373 RemoteAccessible* target = GetAccessible(aID);
374 RemoteAccessible* widget = GetAccessible(aWidgetID);
375 if (!target || !widget) {
376 NS_ERROR("invalid id in selection event");
377 return IPC_OK();
380 ProxySelectionEvent(target, widget, aType);
381 if (!nsCoreUtils::AccEventObserversExist()) {
382 return IPC_OK();
384 xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target);
385 xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this);
386 RefPtr<xpcAccEvent> event =
387 new xpcAccEvent(aType, xpcTarget, xpcDoc, nullptr, false);
388 nsCoreUtils::DispatchAccEvent(std::move(event));
390 return IPC_OK();
393 mozilla::ipc::IPCResult DocAccessibleParent::RecvVirtualCursorChangeEvent(
394 const uint64_t& aID, const uint64_t& aOldPositionID,
395 const int32_t& aOldStartOffset, const int32_t& aOldEndOffset,
396 const uint64_t& aNewPositionID, const int32_t& aNewStartOffset,
397 const int32_t& aNewEndOffset, const int16_t& aReason,
398 const int16_t& aBoundaryType, const bool& aFromUser) {
399 if (mShutdown) {
400 return IPC_OK();
403 RemoteAccessible* target = GetAccessible(aID);
404 RemoteAccessible* oldPosition = GetAccessible(aOldPositionID);
405 RemoteAccessible* newPosition = GetAccessible(aNewPositionID);
407 if (!target) {
408 NS_ERROR("no proxy for event!");
409 return IPC_OK();
412 #if defined(ANDROID)
413 ProxyVirtualCursorChangeEvent(
414 target, oldPosition, aOldStartOffset, aOldEndOffset, newPosition,
415 aNewStartOffset, aNewEndOffset, aReason, aBoundaryType, aFromUser);
416 #endif
418 if (!nsCoreUtils::AccEventObserversExist()) {
419 return IPC_OK();
422 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
423 RefPtr<xpcAccVirtualCursorChangeEvent> event =
424 new xpcAccVirtualCursorChangeEvent(
425 nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED,
426 GetXPCAccessible(target), doc, nullptr, aFromUser,
427 GetXPCAccessible(oldPosition), aOldStartOffset, aOldEndOffset,
428 GetXPCAccessible(newPosition), aNewStartOffset, aNewEndOffset,
429 aBoundaryType, aReason);
430 nsCoreUtils::DispatchAccEvent(std::move(event));
432 return IPC_OK();
435 mozilla::ipc::IPCResult DocAccessibleParent::RecvScrollingEvent(
436 const uint64_t& aID, const uint64_t& aType, const uint32_t& aScrollX,
437 const uint32_t& aScrollY, const uint32_t& aMaxScrollX,
438 const uint32_t& aMaxScrollY) {
439 if (mShutdown) {
440 return IPC_OK();
443 RemoteAccessible* target = GetAccessible(aID);
444 if (!target) {
445 NS_ERROR("no proxy for event!");
446 return IPC_OK();
449 #if defined(ANDROID)
450 ProxyScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
451 aMaxScrollY);
452 #else
453 ProxyEvent(target, aType);
454 #endif
456 if (!nsCoreUtils::AccEventObserversExist()) {
457 return IPC_OK();
460 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
461 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
462 nsINode* node = nullptr;
463 bool fromUser = true; // XXX: Determine if this was from user input.
464 RefPtr<xpcAccScrollingEvent> event =
465 new xpcAccScrollingEvent(aType, xpcAcc, doc, node, fromUser, aScrollX,
466 aScrollY, aMaxScrollX, aMaxScrollY);
467 nsCoreUtils::DispatchAccEvent(std::move(event));
469 return IPC_OK();
472 #if !defined(XP_WIN)
473 mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
474 const uint64_t& aID, const nsString& aAnnouncement,
475 const uint16_t& aPriority) {
476 if (mShutdown) {
477 return IPC_OK();
480 RemoteAccessible* target = GetAccessible(aID);
481 if (!target) {
482 NS_ERROR("no proxy for event!");
483 return IPC_OK();
486 # if defined(ANDROID)
487 ProxyAnnouncementEvent(target, aAnnouncement, aPriority);
488 # endif
490 if (!nsCoreUtils::AccEventObserversExist()) {
491 return IPC_OK();
494 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
495 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
496 RefPtr<xpcAccAnnouncementEvent> event = new xpcAccAnnouncementEvent(
497 nsIAccessibleEvent::EVENT_ANNOUNCEMENT, xpcAcc, doc, nullptr, false,
498 aAnnouncement, aPriority);
499 nsCoreUtils::DispatchAccEvent(std::move(event));
501 return IPC_OK();
504 mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
505 const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
506 # ifdef MOZ_WIDGET_COCOA
507 if (mShutdown) {
508 return IPC_OK();
511 RemoteAccessible* target = GetAccessible(aID);
512 if (!target) {
513 NS_ERROR("no proxy for event!");
514 return IPC_OK();
517 ProxyTextSelectionChangeEvent(target, aSelection);
519 return IPC_OK();
520 # else
521 return RecvEvent(aID, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
522 # endif
525 #endif
527 mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
528 const a11y::role& aRole) {
529 if (mShutdown) {
530 return IPC_OK();
533 mRole = aRole;
535 #ifdef MOZ_WIDGET_COCOA
536 ProxyRoleChangedEvent(this, aRole);
537 #endif
539 return IPC_OK();
542 mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc(
543 PDocAccessibleParent* aChildDoc, const uint64_t& aID) {
544 // One document should never directly be the child of another.
545 // We should always have at least an outer doc accessible in between.
546 MOZ_ASSERT(aID);
547 if (!aID) return IPC_FAIL(this, "ID is 0!");
549 if (mShutdown) {
550 return IPC_OK();
553 MOZ_ASSERT(CheckDocTree());
555 auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc);
556 childDoc->Unbind();
557 ipc::IPCResult result = AddChildDoc(childDoc, aID, false);
558 MOZ_ASSERT(result);
559 MOZ_ASSERT(CheckDocTree());
560 #ifdef DEBUG
561 if (!result) {
562 return result;
564 #else
565 result = IPC_OK();
566 #endif
568 return result;
571 ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
572 uint64_t aParentID,
573 bool aCreating) {
574 // We do not use GetAccessible here because we want to be sure to not get the
575 // document it self.
576 ProxyEntry* e = mAccessibles.GetEntry(aParentID);
577 if (!e) {
578 if (aChildDoc->IsTopLevelInContentProcess()) {
579 // aChildDoc is an embedded document in a different content process to
580 // this document. Sometimes, AddChildDoc gets called before the embedder
581 // sends us the OuterDocAccessible. We must add the child when the
582 // OuterDocAccessible proxy gets created later.
583 #ifdef DEBUG
584 for (uint32_t index = 0, len = mPendingChildDocs.Length(); index < len;
585 ++index) {
586 MOZ_ASSERT(mPendingChildDocs[index].mChildDoc != aChildDoc,
587 "Child doc already pending addition!");
589 #endif
590 mPendingChildDocs.AppendElement(PendingChildDoc(aChildDoc, aParentID));
591 if (aCreating) {
592 ProxyCreated(aChildDoc);
594 return IPC_OK();
596 MOZ_DIAGNOSTIC_ASSERT(false, "Binding to nonexistent proxy!");
597 return IPC_FAIL(this, "binding to nonexistant proxy!");
600 RemoteAccessible* outerDoc = e->mProxy;
601 MOZ_ASSERT(outerDoc);
603 // OuterDocAccessibles are expected to only have a document as a child.
604 // However for compatibility we tolerate replacing one document with another
605 // here.
606 if (!outerDoc->IsOuterDoc() || outerDoc->ChildCount() > 1 ||
607 (outerDoc->ChildCount() == 1 && !outerDoc->RemoteChildAt(0)->IsDoc())) {
608 MOZ_DIAGNOSTIC_ASSERT(false,
609 "Binding to parent that isn't a valid OuterDoc!");
610 return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!");
613 if (outerDoc->ChildCount() == 1) {
614 MOZ_ASSERT(outerDoc->RemoteChildAt(0)->AsDoc());
615 outerDoc->RemoteChildAt(0)->AsDoc()->Unbind();
618 aChildDoc->SetParent(outerDoc);
619 outerDoc->SetChildDoc(aChildDoc);
620 mChildDocs.AppendElement(aChildDoc->mActorID);
621 aChildDoc->mParentDoc = mActorID;
623 if (aCreating) {
624 ProxyCreated(aChildDoc);
627 if (aChildDoc->IsTopLevelInContentProcess()) {
628 // aChildDoc is an embedded document in a different content process to
629 // this document.
630 auto embeddedBrowser =
631 static_cast<dom::BrowserParent*>(aChildDoc->Manager());
632 dom::BrowserBridgeParent* bridge =
633 embeddedBrowser->GetBrowserBridgeParent();
634 if (bridge) {
635 #if defined(XP_WIN)
636 // Send a COM proxy for the embedded document to the embedder process
637 // hosting the iframe. This will be returned as the child of the
638 // embedder OuterDocAccessible.
639 RefPtr<IDispatch> docAcc;
640 aChildDoc->GetCOMInterface((void**)getter_AddRefs(docAcc));
641 MOZ_ASSERT(docAcc);
642 if (docAcc) {
643 RefPtr<IDispatch> docWrapped(
644 mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(docAcc)));
645 IDispatchHolder::COMPtrType docPtr(
646 mscom::ToProxyUniquePtr(std::move(docWrapped)));
647 IDispatchHolder docHolder(std::move(docPtr));
648 if (bridge->SendSetEmbeddedDocAccessibleCOMProxy(docHolder)) {
649 # if defined(MOZ_SANDBOX)
650 aChildDoc->mDocProxyStream = docHolder.GetPreservedStream();
651 # endif // defined(MOZ_SANDBOX)
654 // Send a COM proxy for the embedder OuterDocAccessible to the embedded
655 // document process. This will be returned as the parent of the
656 // embedded document.
657 aChildDoc->SendParentCOMProxy(WrapperFor(outerDoc));
658 if (nsWinUtils::IsWindowEmulationStarted()) {
659 // The embedded document should use the same emulated window handle as
660 // its embedder. It will return the embedder document (not a window
661 // accessible) as the parent accessible, so we pass a null accessible
662 // when sending the window to the embedded document.
663 aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
664 Unused << aChildDoc->SendEmulatedWindow(
665 reinterpret_cast<uintptr_t>(mEmulatedWindowHandle), nullptr);
667 // Send a COM proxy for the top level document to the embedded document
668 // process. This will be returned when the client calls QueryService
669 // with SID_IAccessibleContentDocument on an accessible in the embedded
670 // document.
671 DocAccessibleParent* topDoc = this;
672 while (DocAccessibleParent* parentDoc = topDoc->ParentDoc()) {
673 topDoc = parentDoc;
675 MOZ_ASSERT(topDoc && topDoc->IsTopLevel());
676 RefPtr<IAccessible> topDocAcc;
677 topDoc->GetCOMInterface((void**)getter_AddRefs(topDocAcc));
678 MOZ_ASSERT(topDocAcc);
679 if (topDocAcc) {
680 RefPtr<IAccessible> topDocWrapped(
681 mscom::PassthruProxy::Wrap<IAccessible>(WrapNotNull(topDocAcc)));
682 IAccessibleHolder::COMPtrType topDocPtr(
683 mscom::ToProxyUniquePtr(std::move(topDocWrapped)));
684 IAccessibleHolder topDocHolder(std::move(topDocPtr));
685 if (aChildDoc->SendTopLevelDocCOMProxy(topDocHolder)) {
686 # if defined(MOZ_SANDBOX)
687 aChildDoc->mTopLevelDocProxyStream =
688 topDocHolder.GetPreservedStream();
689 # endif // defined(MOZ_SANDBOX)
692 #endif // defined(XP_WIN)
693 // We need to fire a reorder event on the outer doc accessible.
694 // For same-process documents, this is fired by the content process, but
695 // this isn't possible when the document is in a different process to its
696 // embedder.
697 // RecvEvent fires both OS and XPCOM events.
698 Unused << RecvEvent(aParentID, nsIAccessibleEvent::EVENT_REORDER);
702 return IPC_OK();
705 mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
706 Destroy();
708 auto mgr = static_cast<dom::BrowserParent*>(Manager());
709 if (!mgr->IsDestroyed()) {
710 if (!PDocAccessibleParent::Send__delete__(this)) {
711 return IPC_FAIL_NO_REASON(mgr);
715 return IPC_OK();
718 void DocAccessibleParent::Destroy() {
719 // If we are already shutdown that is because our containing tab parent is
720 // shutting down in which case we don't need to do anything.
721 if (mShutdown) {
722 return;
725 mShutdown = true;
727 MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID));
728 uint32_t childDocCount = mChildDocs.Length();
729 for (uint32_t i = 0; i < childDocCount; i++) {
730 for (uint32_t j = i + 1; j < childDocCount; j++) {
731 MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]);
735 // XXX This indirection through the hash map of live documents shouldn't be
736 // needed, but be paranoid for now.
737 int32_t actorID = mActorID;
738 for (uint32_t i = childDocCount - 1; i < childDocCount; i--) {
739 DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
740 MOZ_ASSERT(thisDoc);
741 if (!thisDoc) {
742 return;
745 thisDoc->ChildDocAt(i)->Destroy();
748 for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
749 MOZ_ASSERT(iter.Get()->mProxy != this);
750 ProxyDestroyed(iter.Get()->mProxy);
751 iter.Remove();
754 DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
755 MOZ_ASSERT(thisDoc);
756 if (!thisDoc) {
757 return;
760 // The code above should have already completely cleared these, but to be
761 // extra safe make sure they are cleared here.
762 thisDoc->mAccessibles.Clear();
763 thisDoc->mChildDocs.Clear();
765 DocManager::NotifyOfRemoteDocShutdown(thisDoc);
766 thisDoc = LiveDocs().Get(actorID);
767 MOZ_ASSERT(thisDoc);
768 if (!thisDoc) {
769 return;
772 ProxyDestroyed(thisDoc);
773 thisDoc = LiveDocs().Get(actorID);
774 MOZ_ASSERT(thisDoc);
775 if (!thisDoc) {
776 return;
779 if (DocAccessibleParent* parentDoc = thisDoc->ParentDoc()) {
780 parentDoc->RemoveChildDoc(thisDoc);
781 } else if (IsTopLevel()) {
782 GetAccService()->RemoteDocShutdown(this);
786 DocAccessibleParent* DocAccessibleParent::ParentDoc() const {
787 if (mParentDoc == kNoParentDoc) {
788 return nullptr;
791 return LiveDocs().Get(mParentDoc);
794 bool DocAccessibleParent::CheckDocTree() const {
795 size_t childDocs = mChildDocs.Length();
796 for (size_t i = 0; i < childDocs; i++) {
797 const DocAccessibleParent* childDoc = ChildDocAt(i);
798 if (!childDoc || childDoc->ParentDoc() != this) return false;
800 if (!childDoc->CheckDocTree()) {
801 return false;
805 return true;
808 xpcAccessibleGeneric* DocAccessibleParent::GetXPCAccessible(
809 RemoteAccessible* aProxy) {
810 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
811 MOZ_ASSERT(doc);
813 return doc->GetAccessible(aProxy);
816 #if defined(XP_WIN)
817 void DocAccessibleParent::MaybeInitWindowEmulation() {
818 if (!nsWinUtils::IsWindowEmulationStarted()) {
819 return;
822 // XXX get the bounds from the browserParent instead of poking at accessibles
823 // which might not exist yet.
824 LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
825 if (!outerDoc) {
826 return;
829 RootAccessible* rootDocument = outerDoc->RootAccessible();
830 MOZ_ASSERT(rootDocument);
832 bool isActive = true;
833 nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
834 if (Compatibility::IsDolphin()) {
835 rect = Bounds();
836 nsIntRect rootRect = rootDocument->Bounds();
837 rect.MoveToX(rootRect.X() - rect.X());
838 rect.MoveToY(rect.Y() - rootRect.Y());
840 auto browserParent = static_cast<dom::BrowserParent*>(Manager());
841 isActive = browserParent->GetDocShellIsActive();
844 // onCreate is guaranteed to be called synchronously by
845 // nsWinUtils::CreateNativeWindow, so this reference isn't really necessary.
846 // However, static analysis complains without it.
847 RefPtr<DocAccessibleParent> thisRef = this;
848 nsWinUtils::NativeWindowCreateProc onCreate([thisRef](HWND aHwnd) -> void {
849 IDispatchHolder hWndAccHolder;
851 ::SetPropW(aHwnd, kPropNameDocAccParent,
852 reinterpret_cast<HANDLE>(thisRef.get()));
854 thisRef->SetEmulatedWindowHandle(aHwnd);
856 RefPtr<IAccessible> hwndAcc;
857 if (SUCCEEDED(::AccessibleObjectFromWindow(
858 aHwnd, OBJID_WINDOW, IID_IAccessible, getter_AddRefs(hwndAcc)))) {
859 RefPtr<IDispatch> wrapped(
860 mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(hwndAcc)));
861 hWndAccHolder.Set(IDispatchHolder::COMPtrType(
862 mscom::ToProxyUniquePtr(std::move(wrapped))));
865 Unused << thisRef->SendEmulatedWindow(
866 reinterpret_cast<uintptr_t>(thisRef->mEmulatedWindowHandle),
867 hWndAccHolder);
870 HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
871 DebugOnly<HWND> hWnd = nsWinUtils::CreateNativeWindow(
872 kClassNameTabContent, parentWnd, rect.X(), rect.Y(), rect.Width(),
873 rect.Height(), isActive, &onCreate);
874 MOZ_ASSERT(hWnd);
877 void DocAccessibleParent::SendParentCOMProxy(LocalAccessible* aOuterDoc) {
878 // Make sure that we're not racing with a tab shutdown
879 auto tab = static_cast<dom::BrowserParent*>(Manager());
880 MOZ_ASSERT(tab);
881 if (tab->IsDestroyed()) {
882 return;
885 RefPtr<IAccessible> nativeAcc;
886 aOuterDoc->GetNativeInterface(getter_AddRefs(nativeAcc));
887 if (NS_WARN_IF(!nativeAcc)) {
888 // Couldn't get a COM proxy for the outer doc. That probably means it died,
889 // but the parent process hasn't received a message to remove it from the
890 // RemoteAccessible tree yet.
891 return;
894 RefPtr<IDispatch> wrapped(
895 mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(nativeAcc)));
897 IDispatchHolder::COMPtrType ptr(mscom::ToProxyUniquePtr(std::move(wrapped)));
898 IDispatchHolder holder(std::move(ptr));
899 if (!PDocAccessibleParent::SendParentCOMProxy(holder)) {
900 return;
903 # if defined(MOZ_SANDBOX)
904 mParentProxyStream = holder.GetPreservedStream();
905 # endif // defined(MOZ_SANDBOX)
908 void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) {
909 if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
910 ::DestroyWindow(mEmulatedWindowHandle);
912 mEmulatedWindowHandle = aWindowHandle;
915 mozilla::ipc::IPCResult DocAccessibleParent::RecvFocusEvent(
916 const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) {
917 if (mShutdown) {
918 return IPC_OK();
921 RemoteAccessible* proxy = GetAccessible(aID);
922 if (!proxy) {
923 NS_ERROR("no proxy for event!");
924 return IPC_OK();
927 ProxyFocusEvent(proxy, aCaretRect);
929 if (!nsCoreUtils::AccEventObserversExist()) {
930 return IPC_OK();
933 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
934 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
935 nsINode* node = nullptr;
936 bool fromUser = true; // XXX fix me
937 RefPtr<xpcAccEvent> event = new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS,
938 xpcAcc, doc, node, fromUser);
939 nsCoreUtils::DispatchAccEvent(std::move(event));
941 return IPC_OK();
944 #endif // defined(XP_WIN)
946 #if !defined(XP_WIN)
947 mozilla::ipc::IPCResult DocAccessibleParent::RecvBatch(
948 const uint64_t& aBatchType, nsTArray<BatchData>&& aData) {
949 // Only do something in Android. We can't ifdef the entire protocol out in
950 // the ipdl because it doesn't allow preprocessing.
951 # if defined(ANDROID)
952 if (mShutdown) {
953 return IPC_OK();
955 nsTArray<RemoteAccessible*> proxies(aData.Length());
956 for (size_t i = 0; i < aData.Length(); i++) {
957 DocAccessibleParent* doc = static_cast<DocAccessibleParent*>(
958 aData.ElementAt(i).Document().get_PDocAccessibleParent());
959 MOZ_ASSERT(doc);
961 if (doc->IsShutdown()) {
962 continue;
965 RemoteAccessible* proxy = doc->GetAccessible(aData.ElementAt(i).ID());
966 if (!proxy) {
967 MOZ_ASSERT_UNREACHABLE("No proxy found!");
968 continue;
971 proxies.AppendElement(proxy);
973 ProxyBatch(this, aBatchType, proxies, aData);
974 # endif // defined(XP_WIN)
975 return IPC_OK();
978 bool DocAccessibleParent::DeallocPDocAccessiblePlatformExtParent(
979 PDocAccessiblePlatformExtParent* aActor) {
980 delete aActor;
981 return true;
984 PDocAccessiblePlatformExtParent*
985 DocAccessibleParent::AllocPDocAccessiblePlatformExtParent() {
986 return new DocAccessiblePlatformExtParent();
989 DocAccessiblePlatformExtParent* DocAccessibleParent::GetPlatformExtension() {
990 return static_cast<DocAccessiblePlatformExtParent*>(
991 SingleManagedOrNull(ManagedPDocAccessiblePlatformExtParent()));
994 #endif // !defined(XP_WIN)
996 Tuple<DocAccessibleParent*, uint64_t> DocAccessibleParent::GetRemoteEmbedder() {
997 dom::BrowserParent* embeddedBrowser = dom::BrowserParent::GetFrom(Manager());
998 dom::BrowserBridgeParent* bridge = embeddedBrowser->GetBrowserBridgeParent();
999 if (!bridge) {
1000 return Tuple<DocAccessibleParent*, uint64_t>(nullptr, 0);
1002 DocAccessibleParent* doc;
1003 uint64_t id;
1004 Tie(doc, id) = bridge->GetEmbedderAccessible();
1005 if (doc && doc->IsShutdown()) {
1006 // Sometimes, the embedder document is destroyed before its
1007 // BrowserBridgeParent. Don't return a destroyed document.
1008 doc = nullptr;
1009 id = 0;
1011 return Tuple<DocAccessibleParent*, uint64_t>(doc, id);
1014 void DocAccessibleParent::RemovePendingChildDoc(DocAccessibleParent* aChildDoc,
1015 uint64_t aParentID) {
1016 for (uint32_t index = 0, len = mPendingChildDocs.Length(); index < len;
1017 ++index) {
1018 PendingChildDoc& pending = mPendingChildDocs[index];
1019 if (pending.mParentID == aParentID) {
1020 MOZ_ASSERT(pending.mChildDoc == aChildDoc);
1021 mPendingChildDocs.RemoveElementAt(index);
1022 break;
1027 } // namespace a11y
1028 } // namespace mozilla