Bug 1668452 require an exact match of Window, rate, and device when selecting a Media...
[gecko.git] / accessible / ipc / DocAccessibleParent.cpp
blobcd5c53980b410f2a2a26a58556da8da1b9408771
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"
17 #if defined(XP_WIN)
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"
24 #else
25 # include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
26 #endif
28 namespace mozilla {
30 #if defined(XP_WIN)
31 namespace mscom {
32 namespace detail {
33 // Needed by mscom::PassthruProxy::Wrap<IAccessible>.
34 template <>
35 struct VTableSizer<IAccessible> {
36 // 3 methods in IUnknown + 4 in IDispatch + 21 in IAccessible = 28 total
37 enum { Size = 28 };
39 } // namespace detail
40 } // namespace mscom
41 #endif // defined (XP_WIN)
43 namespace a11y {
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.
60 if (!parent) {
61 NS_ERROR("adding child to unknown accessible");
62 #ifdef DEBUG
63 return IPC_FAIL(this, "unknown parent accessible");
64 #else
65 return IPC_OK();
66 #endif
69 uint32_t newChildIdx = aData.Idx();
70 if (newChildIdx > parent->ChildCount()) {
71 NS_ERROR("invalid index to add child at");
72 #ifdef DEBUG
73 return IPC_FAIL(this, "invalid index");
74 #else
75 return IPC_OK();
76 #endif
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.
84 if (!consumed) {
85 return IPC_FAIL(this, "failed to add children");
88 #ifdef DEBUG
89 for (uint32_t i = 0; i < consumed; i++) {
90 uint64_t id = aData.NewTree()[i].ID();
91 MOZ_ASSERT(mAccessibles.GetEntry(id));
93 #endif
95 MOZ_ASSERT(CheckDocTree());
97 // Just update, no events.
98 if (aData.EventSuppressed()) {
99 return IPC_OK();
102 RemoteAccessible* target = parent->RemoteChildAt(newChildIdx);
103 ProxyShowHideEvent(target, parent, true, aFromUser);
105 if (!nsCoreUtils::AccEventObserversExist()) {
106 return IPC_OK();
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));
117 return IPC_OK();
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!");
125 return 0;
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);
136 } else {
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);
145 #if defined(XP_WIN)
146 if (!StaticPrefs::accessibility_cache_enabled_AtStartup()) {
147 MsaaAccessible::GetFrom(newProxy)->SetID(newChild.MsaaID());
149 #endif
151 mPendingOOPChildDocs.RemoveIf([&](dom::BrowserBridgeParent* bridge) {
152 MOZ_ASSERT(bridge->GetBrowserParent(),
153 "Pending BrowserBridgeParent should be alive");
154 if (bridge->GetEmbedderAccessibleId() != newChild.ID()) {
155 return false;
157 MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this);
158 if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) {
159 AddChildDoc(childDoc, newChild.ID(), false);
161 return true;
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);
178 return accessibles;
181 void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) {
182 uint64_t id = aAcc->ID();
183 if (!mMovingIDs.Contains(id)) {
184 // This Accessible is being removed.
185 aAcc->Shutdown();
186 return;
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
194 // normal children.
195 return;
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.
214 if (!aRootID) {
215 return IPC_FAIL(this, "Trying to hide entire document?");
218 ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
219 if (!rootEntry) {
220 NS_ERROR("invalid root being removed!");
221 return IPC_OK();
224 RemoteAccessible* root = rootEntry->mProxy;
225 if (!root) {
226 NS_ERROR("invalid root being removed!");
227 return IPC_OK();
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,
245 xpcNext, xpcPrev);
248 parent->RemoveChild(root);
249 ShutdownOrPrepareForMove(root);
251 MOZ_ASSERT(CheckDocTree());
253 if (event) {
254 nsCoreUtils::DispatchAccEvent(std::move(event));
257 return IPC_OK();
260 mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(
261 const uint64_t& aID, const uint32_t& aEventType) {
262 if (mShutdown) {
263 return IPC_OK();
266 RemoteAccessible* proxy = GetAccessible(aID);
267 if (!proxy) {
268 NS_ERROR("no proxy for event!");
269 return IPC_OK();
272 if (aEventType == nsIAccessibleEvent::EVENT_FOCUS) {
273 mFocus = aID;
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()) {
289 return IPC_OK();
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));
300 return IPC_OK();
303 mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(
304 const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) {
305 if (mShutdown) {
306 return IPC_OK();
309 RemoteAccessible* target = GetAccessible(aID);
310 if (!target) {
311 NS_ERROR("we don't know about the target of a state change event!");
312 return IPC_OK();
315 if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
316 target->UpdateStateCache(aState, aEnabled);
318 ProxyStateChangeEvent(target, aState, aEnabled);
320 if (!nsCoreUtils::AccEventObserversExist()) {
321 return IPC_OK();
324 xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
325 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
326 uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
327 bool extra;
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));
335 return IPC_OK();
338 mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
339 const uint64_t& aID,
340 #if defined(XP_WIN)
341 const LayoutDeviceIntRect& aCaretRect,
342 #endif // defined (XP_WIN)
343 const int32_t& aOffset, const bool& aIsSelectionCollapsed,
344 const bool& aIsAtEndOfLine) {
345 if (mShutdown) {
346 return IPC_OK();
349 RemoteAccessible* proxy = GetAccessible(aID);
350 if (!proxy) {
351 NS_ERROR("unknown caret move event target!");
352 return IPC_OK();
355 mCaretId = aID;
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));
366 #if defined(XP_WIN)
367 ProxyCaretMoveEvent(proxy, aCaretRect);
368 #else
369 ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed);
370 #endif
372 if (!nsCoreUtils::AccEventObserversExist()) {
373 return IPC_OK();
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));
386 return IPC_OK();
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) {
392 if (mShutdown) {
393 return IPC_OK();
396 RemoteAccessible* target = GetAccessible(aID);
397 if (!target) {
398 NS_ERROR("text change event target is unknown!");
399 return IPC_OK();
402 ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
404 if (!nsCoreUtils::AccEventObserversExist()) {
405 return IPC_OK();
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));
417 return IPC_OK();
420 #if defined(XP_WIN)
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) {
432 if (mShutdown) {
433 return IPC_OK();
436 RemoteAccessible* target = GetAccessible(aID);
437 RemoteAccessible* widget = GetAccessible(aWidgetID);
438 if (!target || !widget) {
439 NS_ERROR("invalid id in selection event");
440 return IPC_OK();
443 ProxySelectionEvent(target, widget, aType);
444 if (!nsCoreUtils::AccEventObserversExist()) {
445 return IPC_OK();
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));
453 return IPC_OK();
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) {
462 if (mShutdown) {
463 return IPC_OK();
466 RemoteAccessible* target = GetAccessible(aID);
467 RemoteAccessible* oldPosition = GetAccessible(aOldPositionID);
468 RemoteAccessible* newPosition = GetAccessible(aNewPositionID);
470 if (!target) {
471 NS_ERROR("no proxy for event!");
472 return IPC_OK();
475 #if defined(ANDROID)
476 ProxyVirtualCursorChangeEvent(
477 target, oldPosition, aOldStartOffset, aOldEndOffset, newPosition,
478 aNewStartOffset, aNewEndOffset, aReason, aBoundaryType, aFromUser);
479 #endif
481 if (!nsCoreUtils::AccEventObserversExist()) {
482 return IPC_OK();
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));
495 return IPC_OK();
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) {
502 if (mShutdown) {
503 return IPC_OK();
506 RemoteAccessible* target = GetAccessible(aID);
507 if (!target) {
508 NS_ERROR("no proxy for event!");
509 return IPC_OK();
512 #if defined(ANDROID)
513 ProxyScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
514 aMaxScrollY);
515 #else
516 ProxyEvent(target, aType);
517 #endif
519 if (!nsCoreUtils::AccEventObserversExist()) {
520 return IPC_OK();
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));
532 return IPC_OK();
535 mozilla::ipc::IPCResult DocAccessibleParent::RecvCache(
536 const mozilla::a11y::CacheUpdateType& aUpdateType,
537 nsTArray<CacheData>&& aData, const bool& aFinal) {
538 if (mShutdown) {
539 return IPC_OK();
542 for (auto& entry : aData) {
543 RemoteAccessible* remote = GetAccessible(entry.ID());
544 if (!remote) {
545 MOZ_ASSERT_UNREACHABLE("No remote found!");
546 continue;
549 remote->ApplyCache(aUpdateType, entry.Fields());
552 if (nsCOMPtr<nsIObserverService> obsService =
553 services::GetObserverService()) {
554 obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
557 return IPC_OK();
560 mozilla::ipc::IPCResult DocAccessibleParent::RecvAccessiblesWillMove(
561 nsTArray<uint64_t>&& aIDs) {
562 for (uint64_t id : aIDs) {
563 mMovingIDs.EnsureInserted(id);
565 return IPC_OK();
568 #if !defined(XP_WIN)
569 mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
570 const uint64_t& aID, const nsString& aAnnouncement,
571 const uint16_t& aPriority) {
572 if (mShutdown) {
573 return IPC_OK();
576 RemoteAccessible* target = GetAccessible(aID);
577 if (!target) {
578 NS_ERROR("no proxy for event!");
579 return IPC_OK();
582 # if defined(ANDROID)
583 ProxyAnnouncementEvent(target, aAnnouncement, aPriority);
584 # endif
586 if (!nsCoreUtils::AccEventObserversExist()) {
587 return IPC_OK();
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));
597 return IPC_OK();
599 #endif // !defined(XP_WIN)
601 mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
602 const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
603 if (mShutdown) {
604 return IPC_OK();
607 RemoteAccessible* target = GetAccessible(aID);
608 if (!target) {
609 NS_ERROR("no proxy for event!");
610 return IPC_OK();
613 if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
614 mTextSelections.ClearAndRetainStorage();
615 mTextSelections.AppendElements(aSelection);
618 #ifdef MOZ_WIDGET_COCOA
619 ProxyTextSelectionChangeEvent(target, aSelection);
620 #else
621 ProxyEvent(target, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
622 #endif
624 if (!nsCoreUtils::AccEventObserversExist()) {
625 return IPC_OK();
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));
636 return IPC_OK();
639 mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
640 const a11y::role& aRole) {
641 if (mShutdown) {
642 return IPC_OK();
645 mRole = aRole;
647 #ifdef MOZ_WIDGET_COCOA
648 ProxyRoleChangedEvent(this, aRole);
649 #endif
651 return IPC_OK();
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.
658 MOZ_ASSERT(aID);
659 if (!aID) return IPC_FAIL(this, "ID is 0!");
661 if (mShutdown) {
662 return IPC_OK();
665 MOZ_ASSERT(CheckDocTree());
667 auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc);
668 childDoc->Unbind();
669 ipc::IPCResult result = AddChildDoc(childDoc, aID, false);
670 MOZ_ASSERT(result);
671 MOZ_ASSERT(CheckDocTree());
672 #ifdef DEBUG
673 if (!result) {
674 return result;
676 #else
677 result = IPC_OK();
678 #endif
680 return result;
683 ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
684 uint64_t aParentID,
685 bool aCreating) {
686 // We do not use GetAccessible here because we want to be sure to not get the
687 // document it self.
688 ProxyEntry* e = mAccessibles.GetEntry(aParentID);
689 if (!e) {
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
699 // here.
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;
717 if (aCreating) {
718 ProxyCreated(aChildDoc);
721 if (aChildDoc->IsTopLevelInContentProcess()) {
722 // aChildDoc is an embedded document in a different content process to
723 // this document.
724 auto embeddedBrowser =
725 static_cast<dom::BrowserParent*>(aChildDoc->Manager());
726 dom::BrowserBridgeParent* bridge =
727 embeddedBrowser->GetBrowserBridgeParent();
728 if (bridge) {
729 #if defined(XP_WIN)
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));
736 MOZ_ASSERT(docAcc);
737 if (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
764 // document.
765 DocAccessibleParent* topDoc = this;
766 while (DocAccessibleParent* parentDoc = topDoc->ParentDoc()) {
767 topDoc = parentDoc;
769 MOZ_ASSERT(topDoc && topDoc->IsTopLevel());
770 RefPtr<IAccessible> topDocAcc;
771 topDoc->GetCOMInterface((void**)getter_AddRefs(topDocAcc));
772 MOZ_ASSERT(topDocAcc);
773 if (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
794 // embedder.
795 // RecvEvent fires both OS and XPCOM events.
796 Unused << RecvEvent(aParentID, nsIAccessibleEvent::EVENT_REORDER);
800 return IPC_OK();
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);
813 return IPC_OK();
815 return AddChildDoc(aBridge->GetDocAccessibleParent(), parentId,
816 /* aCreating */ false);
819 mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
820 Destroy();
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);
829 return IPC_OK();
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.
835 if (mShutdown) {
836 return;
839 mShutdown = true;
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);
854 MOZ_ASSERT(thisDoc);
855 if (!thisDoc) {
856 return;
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);
865 iter.Remove();
868 DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
869 MOZ_ASSERT(thisDoc);
870 if (!thisDoc) {
871 return;
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);
881 MOZ_ASSERT(thisDoc);
882 if (!thisDoc) {
883 return;
886 ProxyDestroyed(thisDoc);
887 thisDoc = LiveDocs().Get(actorID);
888 MOZ_ASSERT(thisDoc);
889 if (!thisDoc) {
890 return;
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) {
902 return nullptr;
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()) {
915 return false;
919 return true;
922 xpcAccessibleGeneric* DocAccessibleParent::GetXPCAccessible(
923 RemoteAccessible* aProxy) {
924 xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
925 MOZ_ASSERT(doc);
927 return doc->GetAccessible(aProxy);
930 #if defined(XP_WIN)
931 void DocAccessibleParent::MaybeInitWindowEmulation() {
932 if (!nsWinUtils::IsWindowEmulationStarted()) {
933 return;
936 // XXX get the bounds from the browserParent instead of poking at accessibles
937 // which might not exist yet.
938 LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
939 if (!outerDoc) {
940 return;
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()) {
949 rect = Bounds();
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),
981 hWndAccHolder);
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);
988 MOZ_ASSERT(hWnd);
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());
994 MOZ_ASSERT(tab);
995 if (tab->IsDestroyed()) {
996 return;
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.
1005 return;
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)) {
1014 return;
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) {
1031 if (mShutdown) {
1032 return IPC_OK();
1035 RemoteAccessible* proxy = GetAccessible(aID);
1036 if (!proxy) {
1037 NS_ERROR("no proxy for event!");
1038 return IPC_OK();
1041 mFocus = aID;
1042 ProxyFocusEvent(proxy, aCaretRect);
1044 if (!nsCoreUtils::AccEventObserversExist()) {
1045 return IPC_OK();
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));
1056 return IPC_OK();
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)
1067 if (mShutdown) {
1068 return IPC_OK();
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());
1074 MOZ_ASSERT(doc);
1076 if (doc->IsShutdown()) {
1077 continue;
1080 RemoteAccessible* proxy = doc->GetAccessible(aData.ElementAt(i).ID());
1081 if (!proxy) {
1082 MOZ_ASSERT_UNREACHABLE("No proxy found!");
1083 continue;
1086 proxies.AppendElement(proxy);
1088 ProxyBatch(this, aBatchType, proxies, aData);
1089 # endif // defined(XP_WIN)
1090 return IPC_OK();
1093 bool DocAccessibleParent::DeallocPDocAccessiblePlatformExtParent(
1094 PDocAccessiblePlatformExtParent* aActor) {
1095 delete aActor;
1096 return true;
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())),
1116 data.StartOffset(),
1117 const_cast<RemoteAccessible*>(GetAccessible(data.EndID())),
1118 data.EndOffset()));
1122 } // namespace a11y
1123 } // namespace mozilla