1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=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 "LocalAccessible-inl.h"
8 #include "AccIterator.h"
9 #include "AccAttributes.h"
10 #include "CachedTableAccessible.h"
11 #include "DocAccessible-inl.h"
12 #include "EventTree.h"
13 #include "HTMLImageMapAccessible.h"
14 #include "mozilla/ProfilerMarkers.h"
15 #include "nsAccUtils.h"
16 #include "nsEventShell.h"
17 #include "nsIIOService.h"
18 #include "nsLayoutUtils.h"
19 #include "nsTextEquivUtils.h"
20 #include "mozilla/a11y/Role.h"
21 #include "TreeWalker.h"
22 #include "xpcAccessibleDocument.h"
24 #include "nsIDocShell.h"
25 #include "mozilla/dom/Document.h"
26 #include "nsPIDOMWindow.h"
27 #include "nsIContentInlines.h"
28 #include "nsIEditingSession.h"
30 #include "nsIInterfaceRequestorUtils.h"
31 #include "nsImageFrame.h"
32 #include "nsViewManager.h"
33 #include "nsIScrollableFrame.h"
35 #include "nsIWebNavigation.h"
36 #include "nsFocusManager.h"
37 #include "mozilla/ArrayUtils.h"
38 #include "mozilla/Assertions.h"
39 #include "mozilla/Components.h" // for mozilla::components
40 #include "mozilla/EditorBase.h"
41 #include "mozilla/HTMLEditor.h"
42 #include "mozilla/ipc/ProcessChild.h"
43 #include "mozilla/PerfStats.h"
44 #include "mozilla/PresShell.h"
45 #include "nsAccessibilityService.h"
46 #include "mozilla/a11y/DocAccessibleChild.h"
47 #include "mozilla/dom/AncestorIterator.h"
48 #include "mozilla/dom/BrowserChild.h"
49 #include "mozilla/dom/DocumentType.h"
50 #include "mozilla/dom/Element.h"
51 #include "mozilla/dom/HTMLSelectElement.h"
52 #include "mozilla/dom/MutationEventBinding.h"
53 #include "mozilla/dom/UserActivation.h"
55 using namespace mozilla
;
56 using namespace mozilla::a11y
;
58 ////////////////////////////////////////////////////////////////////////////////
59 // Static member initialization
61 static nsStaticAtom
* const kRelationAttrs
[] = {nsGkAtoms::aria_labelledby
,
62 nsGkAtoms::aria_describedby
,
63 nsGkAtoms::aria_details
,
65 nsGkAtoms::aria_controls
,
66 nsGkAtoms::aria_flowto
,
67 nsGkAtoms::aria_errormessage
,
71 static const uint32_t kRelationAttrsLen
= ArrayLength(kRelationAttrs
);
73 ////////////////////////////////////////////////////////////////////////////////
74 // Constructor/desctructor
76 DocAccessible::DocAccessible(dom::Document
* aDocument
,
77 PresShell
* aPresShell
)
78 : // XXX don't pass a document to the LocalAccessible constructor so that
79 // we don't set mDoc until our vtable is fully setup. If we set mDoc
80 // before setting up the vtable we will call LocalAccessible::AddRef()
81 // but not the overrides of it for subclasses. It is important to call
82 // those overrides to avoid confusing leak checking machinary.
83 HyperTextAccessible(nullptr, nullptr),
84 // XXX aaronl should we use an algorithm for the initial cache size?
85 mAccessibleCache(kDefaultCacheLength
),
86 mNodeToAccessibleMap(kDefaultCacheLength
),
87 mDocumentNode(aDocument
),
88 mLoadState(eTreeConstructionPending
),
90 mViewportCacheDirty(false),
93 mPresShell(aPresShell
),
95 mGenericTypes
|= eDocument
;
96 mStateFlags
|= eNotNodeMapEntry
;
99 MOZ_ASSERT(mPresShell
, "should have been given a pres shell");
100 mPresShell
->SetDocAccessible(this);
103 DocAccessible::~DocAccessible() {
104 NS_ASSERTION(!mPresShell
, "LastRelease was never called!?!");
107 ////////////////////////////////////////////////////////////////////////////////
110 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible
)
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible
,
114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController
)
115 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments
)
116 for (const auto& hashEntry
: tmp
->mDependentIDsHashes
.Values()) {
117 for (const auto& providers
: hashEntry
->Values()) {
118 for (int32_t provIdx
= providers
->Length() - 1; provIdx
>= 0; provIdx
--) {
119 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
120 cb
, "content of dependent ids hash entry of document accessible");
122 const auto& provider
= (*providers
)[provIdx
];
123 cb
.NoteXPCOMChild(provider
->mContent
);
127 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache
)
128 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm
)
129 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList
)
130 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingUpdates
)
131 for (const auto& ar
: tmp
->mARIAOwnsHash
.Values()) {
132 for (uint32_t i
= 0; i
< ar
->Length(); i
++) {
133 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb
, "mARIAOwnsHash entry item");
134 cb
.NoteXPCOMChild(ar
->ElementAt(i
));
137 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
139 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible
, LocalAccessible
)
140 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController
)
141 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments
)
142 tmp
->mDependentIDsHashes
.Clear();
143 tmp
->mNodeToAccessibleMap
.Clear();
144 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache
)
145 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm
)
146 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList
)
147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingUpdates
)
148 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
149 tmp
->mARIAOwnsHash
.Clear();
150 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
152 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocAccessible
)
153 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver
)
154 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver
)
155 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
156 NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible
)
158 NS_IMPL_ADDREF_INHERITED(DocAccessible
, HyperTextAccessible
)
159 NS_IMPL_RELEASE_INHERITED(DocAccessible
, HyperTextAccessible
)
161 ////////////////////////////////////////////////////////////////////////////////
164 ENameValueFlag
DocAccessible::Name(nsString
& aName
) const {
168 mParent
->Name(aName
); // Allow owning iframe to override the name
170 if (aName
.IsEmpty()) {
171 // Allow name via aria-labelledby or title attribute
172 LocalAccessible::Name(aName
);
174 if (aName
.IsEmpty()) {
175 Title(aName
); // Try title element
177 if (aName
.IsEmpty()) { // Last resort: use URL
184 // LocalAccessible public method
185 role
DocAccessible::NativeRole() const {
186 nsCOMPtr
<nsIDocShell
> docShell
= nsCoreUtils::GetDocShellFor(mDocumentNode
);
188 nsCOMPtr
<nsIDocShellTreeItem
> sameTypeRoot
;
189 docShell
->GetInProcessSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot
));
190 int32_t itemType
= docShell
->ItemType();
191 if (sameTypeRoot
== docShell
) {
192 // Root of content or chrome tree
193 if (itemType
== nsIDocShellTreeItem::typeChrome
) {
194 return roles::CHROME_WINDOW
;
197 if (itemType
== nsIDocShellTreeItem::typeContent
) {
198 return roles::DOCUMENT
;
200 } else if (itemType
== nsIDocShellTreeItem::typeContent
) {
201 return roles::DOCUMENT
;
205 return roles::PANE
; // Fall back;
208 void DocAccessible::Description(nsString
& aDescription
) const {
209 if (mParent
) mParent
->Description(aDescription
);
211 if (HasOwnContent() && aDescription
.IsEmpty()) {
212 nsTextEquivUtils::GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby
,
217 // LocalAccessible public method
218 uint64_t DocAccessible::NativeState() const {
219 // Document is always focusable.
221 states::FOCUSABLE
; // keep in sync with NativeInteractiveState() impl
222 if (FocusMgr()->IsFocused(this)) state
|= states::FOCUSED
;
224 // Expose stale state until the document is ready (DOM is loaded and tree is
226 if (!HasLoadState(eReady
)) state
|= states::STALE
;
228 // Expose state busy until the document and all its subdocuments is completely
230 if (!HasLoadState(eCompletelyLoaded
)) state
|= states::BUSY
;
232 nsIFrame
* frame
= GetFrame();
233 if (!frame
|| !frame
->IsVisibleConsideringAncestors(
234 nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY
)) {
235 state
|= states::INVISIBLE
| states::OFFSCREEN
;
238 RefPtr
<EditorBase
> editorBase
= GetEditor();
239 state
|= editorBase
? states::EDITABLE
: states::READONLY
;
244 uint64_t DocAccessible::NativeInteractiveState() const {
245 // Document is always focusable.
246 return states::FOCUSABLE
;
249 bool DocAccessible::NativelyUnavailable() const { return false; }
251 // LocalAccessible public method
252 void DocAccessible::ApplyARIAState(uint64_t* aState
) const {
253 // Grab states from content element.
254 if (mContent
) LocalAccessible::ApplyARIAState(aState
);
256 // Allow iframe/frame etc. to have final state override via ARIA.
257 if (mParent
) mParent
->ApplyARIAState(aState
);
260 Accessible
* DocAccessible::FocusedChild() {
261 // Return an accessible for the current global focus, which does not have to
262 // be contained within the current document.
263 return FocusMgr()->FocusedAccessible();
266 void DocAccessible::TakeFocus() const {
267 // Focus the document.
268 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
269 RefPtr
<dom::Element
> newFocus
;
270 dom::AutoHandlingUserInputStatePusher
inputStatePusher(true);
271 fm
->MoveFocus(mDocumentNode
->GetWindow(), nullptr,
272 nsFocusManager::MOVEFOCUS_ROOT
, 0, getter_AddRefs(newFocus
));
275 // HyperTextAccessible method
276 already_AddRefed
<EditorBase
> DocAccessible::GetEditor() const {
277 // Check if document is editable (designMode="on" case). Otherwise check if
278 // the html:body (for HTML document case) or document element is editable.
279 if (!mDocumentNode
->IsInDesignMode() &&
280 (!mContent
|| !mContent
->HasFlag(NODE_IS_EDITABLE
))) {
284 nsCOMPtr
<nsIDocShell
> docShell
= mDocumentNode
->GetDocShell();
289 nsCOMPtr
<nsIEditingSession
> editingSession
;
290 docShell
->GetEditingSession(getter_AddRefs(editingSession
));
291 if (!editingSession
) return nullptr; // No editing session interface
293 RefPtr
<HTMLEditor
> htmlEditor
=
294 editingSession
->GetHTMLEditorForWindow(mDocumentNode
->GetWindow());
299 bool isEditable
= false;
300 htmlEditor
->GetIsDocumentEditable(&isEditable
);
302 return htmlEditor
.forget();
308 // DocAccessible public method
310 void DocAccessible::URL(nsAString
& aURL
) const {
312 nsCOMPtr
<nsISupports
> container
= mDocumentNode
->GetContainer();
313 nsCOMPtr
<nsIWebNavigation
> webNav(do_GetInterface(container
));
314 if (MOZ_UNLIKELY(!webNav
)) {
318 nsCOMPtr
<nsIURI
> uri
;
319 webNav
->GetCurrentURI(getter_AddRefs(uri
));
320 if (MOZ_UNLIKELY(!uri
)) {
323 // Let's avoid treating too long URI in the main process for avoiding
324 // memory fragmentation as far as possible.
325 if (uri
->SchemeIs("data") || uri
->SchemeIs("blob")) {
329 nsCOMPtr
<nsIIOService
> io
= mozilla::components::IO::Service();
330 if (NS_WARN_IF(!io
)) {
333 nsCOMPtr
<nsIURI
> exposableURI
;
334 if (NS_FAILED(io
->CreateExposableURI(uri
, getter_AddRefs(exposableURI
))) ||
335 MOZ_UNLIKELY(!exposableURI
)) {
338 nsAutoCString theURL
;
339 if (NS_SUCCEEDED(exposableURI
->GetSpec(theURL
))) {
340 CopyUTF8toUTF16(theURL
, aURL
);
344 void DocAccessible::Title(nsString
& aTitle
) const {
345 mDocumentNode
->GetTitle(aTitle
);
348 void DocAccessible::MimeType(nsAString
& aType
) const {
349 mDocumentNode
->GetContentType(aType
);
352 void DocAccessible::DocType(nsAString
& aType
) const {
353 dom::DocumentType
* docType
= mDocumentNode
->GetDoctype();
354 if (docType
) docType
->GetPublicId(aType
);
357 void DocAccessible::QueueCacheUpdate(LocalAccessible
* aAcc
,
358 uint64_t aNewDomain
) {
362 // These strong references aren't necessary because WithEntryHandle is
363 // guaranteed to run synchronously. However, static analysis complains without
365 RefPtr
<DocAccessible
> self
= this;
366 RefPtr
<LocalAccessible
> acc
= aAcc
;
368 mQueuedCacheUpdatesHash
.WithEntryHandle(aAcc
, [self
, acc
](auto&& entry
) {
369 if (entry
.HasEntry()) {
370 // This LocalAccessible has already been queued. Return its index in
371 // the queue array so we can update its queued domains.
374 // Add this LocalAccessible to the queue array.
375 size_t index
= self
->mQueuedCacheUpdatesArray
.Length();
376 self
->mQueuedCacheUpdatesArray
.EmplaceBack(std::make_pair(acc
, 0));
377 // Also add it to the hash map so we can avoid processing the same
378 // LocalAccessible twice.
379 return entry
.Insert(index
);
381 auto& [arrayAcc
, domain
] = mQueuedCacheUpdatesArray
[arrayIndex
];
382 MOZ_ASSERT(arrayAcc
== aAcc
);
383 domain
|= aNewDomain
;
384 Controller()->ScheduleProcessing();
387 void DocAccessible::QueueCacheUpdateForDependentRelations(
388 LocalAccessible
* aAcc
) {
389 if (!mIPCDoc
|| !aAcc
|| !aAcc
->Elm() || !aAcc
->IsInDocument() ||
395 if (AttrRelProviders
* list
= GetRelProviders(aAcc
->Elm(), ID
)) {
396 // We call this function when we've noticed an ID change, or when an acc
397 // is getting bound to its document. We need to ensure any existing accs
398 // that depend on this acc's ID have their rel cache entries updated.
399 for (const auto& provider
: *list
) {
400 LocalAccessible
* relatedAcc
= GetAccessible(provider
->mContent
);
401 if (!relatedAcc
|| relatedAcc
->IsDefunct() ||
402 !relatedAcc
->IsInDocument() ||
403 mInsertedAccessibles
.Contains(relatedAcc
)) {
406 QueueCacheUpdate(relatedAcc
, CacheDomain::Relations
);
411 ////////////////////////////////////////////////////////////////////////////////
414 void DocAccessible::Init() {
416 if (logging::IsEnabled(logging::eDocCreate
)) {
417 logging::DocCreate("document initialize", mDocumentNode
, this);
421 // Initialize notification controller.
422 mNotificationController
= new NotificationController(this, mPresShell
);
424 // Mark the DocAccessible as loaded if its DOM document is already loaded at
425 // this point. This can happen for one of three reasons:
426 // 1. A11y was started late.
427 // 2. DOM loading for a document (probably an in-process iframe) completed
428 // before its Accessible container was created.
429 // 3. The PresShell for the document was created after DOM loading completed.
430 // In that case, we tried to create the DocAccessible when DOM loading
431 // completed, but we can't create a DocAccessible without a PresShell, so
432 // this failed. The DocAccessible was subsequently created due to a layout
434 if (mDocumentNode
->GetReadyStateEnum() ==
435 dom::Document::READYSTATE_COMPLETE
) {
436 mLoadState
|= eDOMLoaded
;
437 // If this happened due to reasons 1 or 2, it isn't *necessary* to fire a
438 // doc load complete event. If it happened due to reason 3, we need to fire
439 // doc load complete because clients (especially tests) might be waiting
440 // for the document to load using this event. We can't distinguish why this
441 // happened at this point, so just fire it regardless. It won't do any
442 // harm even if it isn't necessary. We set mLoadEventType here and it will
443 // be fired in ProcessLoad as usual.
444 mLoadEventType
= nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
;
445 } else if (mDocumentNode
->IsInitialDocument()) {
446 // The initial about:blank document will never finish loading, so we can
447 // immediately mark it loaded to avoid waiting for its load.
448 mLoadState
|= eDOMLoaded
;
454 void DocAccessible::Shutdown() {
455 if (!mPresShell
) { // already shutdown
460 if (logging::IsEnabled(logging::eDocDestroy
)) {
461 logging::DocDestroy("document shutdown", mDocumentNode
, this);
465 // Mark the document as shutdown before AT is notified about the document
466 // removal from its container (valid for root documents on ATK and due to
467 // some reason for MSAA, refer to bug 757392 for details).
468 mStateFlags
|= eIsDefunct
;
470 if (mNotificationController
) {
471 mNotificationController
->Shutdown();
472 mNotificationController
= nullptr;
475 RemoveEventListeners();
477 // mParent->RemoveChild clears mParent, but we need to know whether we were a
478 // child later, so use a flag.
479 const bool isChild
= !!mParent
;
481 DocAccessible
* parentDocument
= mParent
->Document();
482 if (parentDocument
) parentDocument
->RemoveChildDocument(this);
484 mParent
->RemoveChild(this);
485 MOZ_ASSERT(!mParent
, "Parent has to be null!");
488 mPresShell
->SetDocAccessible(nullptr);
489 mPresShell
= nullptr; // Avoid reentrancy
491 // Walk the array backwards because child documents remove themselves from the
492 // array as they are shutdown.
493 int32_t childDocCount
= mChildDocuments
.Length();
494 for (int32_t idx
= childDocCount
- 1; idx
>= 0; idx
--) {
495 mChildDocuments
[idx
]->Shutdown();
498 mChildDocuments
.Clear();
499 // mQueuedCacheUpdates* can contain a reference to this document (ex. if the
500 // doc is scrollable and we're sending a scroll position update). Clear the
501 // map here to avoid creating ref cycles.
502 mQueuedCacheUpdatesArray
.Clear();
503 mQueuedCacheUpdatesHash
.Clear();
505 // XXX thinking about ordering?
507 MOZ_ASSERT(IPCAccessibilityActive());
509 MOZ_ASSERT(!mIPCDoc
);
512 mDependentIDsHashes
.Clear();
513 mNodeToAccessibleMap
.Clear();
515 mAnchorJumpElm
= nullptr;
516 mInvalidationList
.Clear();
517 mPendingUpdates
.Clear();
519 for (auto iter
= mAccessibleCache
.Iter(); !iter
.Done(); iter
.Next()) {
520 LocalAccessible
* accessible
= iter
.Data();
521 MOZ_ASSERT(accessible
);
523 // This might have been focused with FocusManager::ActiveItemChanged. In
524 // that case, we must notify FocusManager so that it clears the active
525 // item. Otherwise, it will hold on to a defunct Accessible. Normally,
526 // this happens in UnbindFromDocument, but we don't call that when the
527 // whole document shuts down.
528 if (FocusMgr()->WasLastFocused(accessible
)) {
529 FocusMgr()->ActiveItemChanged(nullptr);
531 if (logging::IsEnabled(logging::eFocus
)) {
532 logging::ActiveItemChangeCausedBy("doc shutdown", accessible
);
536 if (!accessible
->IsDefunct()) {
537 // Unlink parent to avoid its cleaning overhead in shutdown.
538 accessible
->mParent
= nullptr;
539 accessible
->Shutdown();
545 HyperTextAccessible::Shutdown();
547 MOZ_ASSERT(GetAccService());
548 GetAccService()->NotifyOfDocumentShutdown(
550 // Make sure we don't shut down AccService while a parent document is
551 // still shutting down. The parent will allow service shutdown when it
552 // reaches this point.
553 /* aAllowServiceShutdown */ !isChild
);
554 mDocumentNode
= nullptr;
557 nsIFrame
* DocAccessible::GetFrame() const {
558 nsIFrame
* root
= nullptr;
560 root
= mPresShell
->GetRootFrame();
566 nsINode
* DocAccessible::GetNode() const { return mDocumentNode
; }
568 // DocAccessible protected member
569 nsRect
DocAccessible::RelativeBounds(nsIFrame
** aRelativeFrame
) const {
570 *aRelativeFrame
= GetFrame();
572 dom::Document
* document
= mDocumentNode
;
573 dom::Document
* parentDoc
= nullptr;
577 PresShell
* presShell
= document
->GetPresShell();
583 nsIScrollableFrame
* sf
= presShell
->GetRootScrollFrameAsScrollable();
585 scrollPort
= sf
->GetScrollPortRect();
587 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
588 if (!rootFrame
) return nsRect();
590 scrollPort
= rootFrame
->GetRect();
593 if (parentDoc
) { // After first time thru loop
594 // XXXroc bogus code! scrollPort is relative to the viewport of
595 // this document, but we're intersecting rectangles derived from
596 // multiple documents and assuming they're all in the same coordinate
597 // system. See bug 514117.
598 bounds
.IntersectRect(scrollPort
, bounds
);
599 } else { // First time through loop
603 document
= parentDoc
= document
->GetInProcessParentDocument();
609 // DocAccessible protected member
610 nsresult
DocAccessible::AddEventListeners() {
611 SelectionMgr()->AddDocSelectionListener(mPresShell
);
613 // Add document observer.
614 mDocumentNode
->AddObserver(this);
618 // DocAccessible protected member
619 nsresult
DocAccessible::RemoveEventListeners() {
620 // Remove listeners associated with content documents
621 NS_ASSERTION(mDocumentNode
, "No document during removal of listeners.");
624 mDocumentNode
->RemoveObserver(this);
627 if (mScrollWatchTimer
) {
628 mScrollWatchTimer
->Cancel();
629 mScrollWatchTimer
= nullptr;
630 NS_RELEASE_THIS(); // Kung fu death grip
633 SelectionMgr()->RemoveDocSelectionListener(mPresShell
);
637 void DocAccessible::ScrollTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
638 DocAccessible
* docAcc
= reinterpret_cast<DocAccessible
*>(aClosure
);
641 // Dispatch a scroll-end for all entries in table. They have not
642 // been scrolled in at least `kScrollEventInterval`.
643 for (auto iter
= docAcc
->mLastScrollingDispatch
.Iter(); !iter
.Done();
645 docAcc
->DispatchScrollingEvent(iter
.Key(),
646 nsIAccessibleEvent::EVENT_SCROLLING_END
);
650 if (docAcc
->mScrollWatchTimer
) {
651 docAcc
->mScrollWatchTimer
= nullptr;
652 NS_RELEASE(docAcc
); // Release kung fu death grip
657 void DocAccessible::HandleScroll(nsINode
* aTarget
) {
658 nsINode
* target
= aTarget
;
659 LocalAccessible
* targetAcc
= GetAccessible(target
);
660 if (!targetAcc
&& target
->IsInNativeAnonymousSubtree()) {
661 // The scroll event for textareas comes from a native anonymous div. We need
662 // the closest non-anonymous ancestor to get the right Accessible.
663 target
= target
->GetClosestNativeAnonymousSubtreeRootParentOrHost();
664 targetAcc
= GetAccessible(target
);
666 // Regardless of our scroll timer, we need to send a cache update
667 // to ensure the next Bounds() query accurately reflects our position
670 QueueCacheUpdate(targetAcc
, CacheDomain::ScrollPosition
);
673 const uint32_t kScrollEventInterval
= 100;
674 // If we haven't dispatched a scrolling event for a target in at least
675 // kScrollEventInterval milliseconds, dispatch one now.
676 mLastScrollingDispatch
.WithEntryHandle(target
, [&](auto&& lastDispatch
) {
677 const TimeStamp now
= TimeStamp::Now();
680 (now
- lastDispatch
.Data()).ToMilliseconds() >= kScrollEventInterval
) {
681 // We can't fire events on a document whose tree isn't constructed yet.
682 if (HasLoadState(eTreeConstructed
)) {
683 DispatchScrollingEvent(target
, nsIAccessibleEvent::EVENT_SCROLLING
);
685 lastDispatch
.InsertOrUpdate(now
);
689 // If timer callback is still pending, push it 100ms into the future.
690 // When scrolling ends and we don't fire this callback anymore, the
691 // timer callback will fire and dispatch an EVENT_SCROLLING_END.
692 if (mScrollWatchTimer
) {
693 mScrollWatchTimer
->SetDelay(kScrollEventInterval
);
695 NS_NewTimerWithFuncCallback(getter_AddRefs(mScrollWatchTimer
),
696 ScrollTimerCallback
, this, kScrollEventInterval
,
697 nsITimer::TYPE_ONE_SHOT
,
698 "a11y::DocAccessible::ScrollPositionDidChange");
699 if (mScrollWatchTimer
) {
700 NS_ADDREF_THIS(); // Kung fu death grip
705 std::pair
<nsPoint
, nsRect
> DocAccessible::ComputeScrollData(
706 LocalAccessible
* aAcc
) {
710 if (nsIFrame
* frame
= aAcc
->GetFrame()) {
711 nsIScrollableFrame
* sf
= aAcc
== this
712 ? mPresShell
->GetRootScrollFrameAsScrollable()
713 : frame
->GetScrollTargetFrame();
715 // If there is no scrollable frame, it's likely a scroll in a popup, like
716 // <select>. Return a scroll offset and range of 0. The scroll info
717 // is currently only used on Android, and popups are rendered natively
720 scrollPoint
= sf
->GetScrollPosition() * mPresShell
->GetResolution();
721 scrollRange
= sf
->GetScrollRange();
722 scrollRange
.ScaleRoundOut(mPresShell
->GetResolution());
726 return {scrollPoint
, scrollRange
};
729 ////////////////////////////////////////////////////////////////////////////////
730 // nsIDocumentObserver
732 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible
)
733 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible
)
735 void DocAccessible::AttributeWillChange(dom::Element
* aElement
,
736 int32_t aNameSpaceID
,
737 nsAtom
* aAttribute
, int32_t aModType
) {
738 LocalAccessible
* accessible
= GetAccessible(aElement
);
740 if (aElement
!= mContent
) return;
745 // Update dependent IDs cache. Take care of elements that are accessible
746 // because dependent IDs cache doesn't contain IDs from non accessible
748 if (aModType
!= dom::MutationEvent_Binding::ADDITION
) {
749 RemoveDependentIDsFor(accessible
, aAttribute
);
752 if (aAttribute
== nsGkAtoms::id
) {
753 if (accessible
->IsActiveDescendant()) {
754 RefPtr
<AccEvent
> event
=
755 new AccStateChangeEvent(accessible
, states::ACTIVE
, false);
756 FireDelayedEvent(event
);
759 RelocateARIAOwnedIfNeeded(aElement
);
762 if (aAttribute
== nsGkAtoms::aria_activedescendant
) {
763 if (LocalAccessible
* activeDescendant
= accessible
->CurrentItem()) {
764 RefPtr
<AccEvent
> event
=
765 new AccStateChangeEvent(activeDescendant
, states::ACTIVE
, false);
766 FireDelayedEvent(event
);
770 // If attribute affects accessible's state, store the old state so we can
771 // later compare it against the state of the accessible after the attribute
773 if (accessible
->AttributeChangesState(aAttribute
)) {
774 mPrevStateBits
= accessible
->State();
780 void DocAccessible::AttributeChanged(dom::Element
* aElement
,
781 int32_t aNameSpaceID
, nsAtom
* aAttribute
,
783 const nsAttrValue
* aOldValue
) {
784 NS_ASSERTION(!IsDefunct(),
785 "Attribute changed called on defunct document accessible!");
787 // Proceed even if the element is not accessible because element may become
788 // accessible if it gets certain attribute.
789 if (UpdateAccessibleOnAttrChange(aElement
, aAttribute
)) return;
791 // Update the accessible tree on aria-hidden change. Make sure to not create
792 // a tree under aria-hidden='true'.
793 if (aAttribute
== nsGkAtoms::aria_hidden
) {
794 if (aria::HasDefinedARIAHidden(aElement
)) {
795 ContentRemoved(aElement
);
797 ContentInserted(aElement
, aElement
->GetNextSibling());
802 LocalAccessible
* accessible
= GetAccessible(aElement
);
804 if (mContent
== aElement
) {
805 // The attribute change occurred on the root content of this
806 // DocAccessible, so handle it as an attribute change on this.
809 if (aModType
== dom::MutationEvent_Binding::ADDITION
&&
810 aria::AttrCharacteristicsFor(aAttribute
) & ATTR_GLOBAL
) {
811 // The element doesn't have an Accessible, but a global ARIA attribute
812 // was just added, which means we should probably create an Accessible.
813 ContentInserted(aElement
, aElement
->GetNextSibling());
816 // The element doesn't have an Accessible, so ignore the attribute
822 MOZ_ASSERT(accessible
->IsBoundToParent() || accessible
->IsDoc(),
823 "DOM attribute change on an accessible detached from the tree");
825 if (aAttribute
== nsGkAtoms::id
) {
826 dom::Element
* elm
= accessible
->Elm();
827 RelocateARIAOwnedIfNeeded(elm
);
828 ARIAActiveDescendantIDMaybeMoved(accessible
);
829 QueueCacheUpdate(accessible
, CacheDomain::DOMNodeIDAndClass
);
830 QueueCacheUpdateForDependentRelations(accessible
);
833 // The activedescendant universal property redirects accessible focus events
834 // to the element with the id that activedescendant points to. Make sure
835 // the tree up to date before processing. In other words, when a node has just
836 // been inserted, the tree won't be up to date yet, so we must always schedule
837 // an async notification so that a newly inserted node will be present in
839 if (aAttribute
== nsGkAtoms::aria_activedescendant
) {
840 mNotificationController
841 ->ScheduleNotification
<DocAccessible
, LocalAccessible
>(
842 this, &DocAccessible::ARIAActiveDescendantChanged
, accessible
);
846 // Defer to accessible any needed actions like changing states or emiting
848 accessible
->DOMAttributeChanged(aNameSpaceID
, aAttribute
, aModType
, aOldValue
,
851 // Update dependent IDs cache. We handle elements with accessibles.
852 // If the accessible or element with the ID doesn't exist yet the cache will
853 // be updated when they are added.
854 if (aModType
== dom::MutationEvent_Binding::MODIFICATION
||
855 aModType
== dom::MutationEvent_Binding::ADDITION
) {
856 AddDependentIDsFor(accessible
, aAttribute
);
860 void DocAccessible::ARIAAttributeDefaultWillChange(dom::Element
* aElement
,
863 NS_ASSERTION(!IsDefunct(),
864 "Attribute changed called on defunct document accessible!");
866 if (aElement
->HasAttr(aAttribute
)) {
870 AttributeWillChange(aElement
, kNameSpaceID_None
, aAttribute
, aModType
);
873 void DocAccessible::ARIAAttributeDefaultChanged(dom::Element
* aElement
,
876 NS_ASSERTION(!IsDefunct(),
877 "Attribute changed called on defunct document accessible!");
879 if (aElement
->HasAttr(aAttribute
)) {
883 AttributeChanged(aElement
, kNameSpaceID_None
, aAttribute
, aModType
, nullptr);
886 void DocAccessible::ARIAActiveDescendantChanged(LocalAccessible
* aAccessible
) {
887 if (dom::Element
* elm
= aAccessible
->Elm()) {
889 if (elm
->GetAttr(nsGkAtoms::aria_activedescendant
, id
)) {
890 dom::Element
* activeDescendantElm
= IDRefsIterator::GetElem(elm
, id
);
891 if (activeDescendantElm
) {
892 LocalAccessible
* activeDescendant
= GetAccessible(activeDescendantElm
);
893 if (activeDescendant
) {
894 RefPtr
<AccEvent
> event
=
895 new AccStateChangeEvent(activeDescendant
, states::ACTIVE
, true);
896 FireDelayedEvent(event
);
897 if (aAccessible
->IsActiveWidget()) {
898 FocusMgr()->ActiveItemChanged(activeDescendant
, false);
900 if (logging::IsEnabled(logging::eFocus
)) {
901 logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
911 // aria-activedescendant was cleared or changed to a non-existent node.
912 // Move focus back to the element itself if it has DOM focus.
913 if (aAccessible
->IsActiveWidget()) {
914 FocusMgr()->ActiveItemChanged(aAccessible
, false);
916 if (logging::IsEnabled(logging::eFocus
)) {
917 logging::ActiveItemChangeCausedBy("ARIA activedescedant cleared",
925 void DocAccessible::ContentAppended(nsIContent
* aFirstNewContent
) {
926 MaybeHandleChangeToHiddenNameOrDescription(aFirstNewContent
);
929 void DocAccessible::ElementStateChanged(dom::Document
* aDocument
,
930 dom::Element
* aElement
,
931 dom::ElementState aStateMask
) {
932 if (aStateMask
.HasState(dom::ElementState::READWRITE
) &&
933 aElement
== mDocumentNode
->GetRootElement()) {
934 // This handles changes to designMode. contentEditable is handled by
935 // LocalAccessible::AttributeChangesState and
936 // LocalAccessible::DOMAttributeChanged.
937 const bool isEditable
=
938 aElement
->State().HasState(dom::ElementState::READWRITE
);
939 RefPtr
<AccEvent
> event
=
940 new AccStateChangeEvent(this, states::EDITABLE
, isEditable
);
941 FireDelayedEvent(event
);
942 event
= new AccStateChangeEvent(this, states::READONLY
, !isEditable
);
943 FireDelayedEvent(event
);
946 LocalAccessible
* accessible
= GetAccessible(aElement
);
947 if (!accessible
) return;
949 if (aStateMask
.HasState(dom::ElementState::CHECKED
)) {
950 LocalAccessible
* widget
= accessible
->ContainerWidget();
951 if (widget
&& widget
->IsSelect()) {
952 // Changing selection here changes what we cache for
954 SetViewportCacheDirty(true);
955 AccSelChangeEvent::SelChangeType selChangeType
=
956 aElement
->State().HasState(dom::ElementState::CHECKED
)
957 ? AccSelChangeEvent::eSelectionAdd
958 : AccSelChangeEvent::eSelectionRemove
;
959 RefPtr
<AccEvent
> event
=
960 new AccSelChangeEvent(widget
, accessible
, selChangeType
);
961 FireDelayedEvent(event
);
965 RefPtr
<AccEvent
> event
= new AccStateChangeEvent(
966 accessible
, states::CHECKED
,
967 aElement
->State().HasState(dom::ElementState::CHECKED
));
968 FireDelayedEvent(event
);
971 if (aStateMask
.HasState(dom::ElementState::INVALID
)) {
972 RefPtr
<AccEvent
> event
=
973 new AccStateChangeEvent(accessible
, states::INVALID
);
974 FireDelayedEvent(event
);
977 if (aStateMask
.HasState(dom::ElementState::REQUIRED
)) {
978 RefPtr
<AccEvent
> event
=
979 new AccStateChangeEvent(accessible
, states::REQUIRED
);
980 FireDelayedEvent(event
);
983 if (aStateMask
.HasState(dom::ElementState::VISITED
)) {
984 RefPtr
<AccEvent
> event
=
985 new AccStateChangeEvent(accessible
, states::TRAVERSED
, true);
986 FireDelayedEvent(event
);
989 // We only expose dom::ElementState::DEFAULT on buttons, but we can get
990 // notifications for other controls like checkboxes.
991 if (aStateMask
.HasState(dom::ElementState::DEFAULT
) &&
992 accessible
->IsButton()) {
993 RefPtr
<AccEvent
> event
=
994 new AccStateChangeEvent(accessible
, states::DEFAULT
);
995 FireDelayedEvent(event
);
999 void DocAccessible::CharacterDataWillChange(nsIContent
* aContent
,
1000 const CharacterDataChangeInfo
&) {}
1002 void DocAccessible::CharacterDataChanged(nsIContent
* aContent
,
1003 const CharacterDataChangeInfo
&) {}
1005 void DocAccessible::ContentInserted(nsIContent
* aChild
) {
1006 MaybeHandleChangeToHiddenNameOrDescription(aChild
);
1009 void DocAccessible::ContentRemoved(nsIContent
* aChildNode
,
1010 nsIContent
* aPreviousSiblingNode
) {
1012 if (logging::IsEnabled(logging::eTree
)) {
1013 logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
1014 logging::Node("container node", aChildNode
->GetParent());
1015 logging::Node("content node", aChildNode
);
1019 ContentRemoved(aChildNode
);
1022 void DocAccessible::ParentChainChanged(nsIContent
* aContent
) {}
1024 ////////////////////////////////////////////////////////////////////////////////
1028 nsresult
DocAccessible::HandleAccEvent(AccEvent
* aEvent
) {
1029 if (logging::IsEnabled(logging::eDocLoad
)) {
1030 logging::DocLoadEventHandled(aEvent
);
1033 return HyperTextAccessible::HandleAccEvent(aEvent
);
1037 ////////////////////////////////////////////////////////////////////////////////
1040 nsPresContext
* DocAccessible::PresContext() const {
1041 return mPresShell
->GetPresContext();
1044 void* DocAccessible::GetNativeWindow() const {
1049 nsViewManager
* vm
= mPresShell
->GetViewManager();
1050 if (!vm
) return nullptr;
1052 nsCOMPtr
<nsIWidget
> widget
= vm
->GetRootWidget();
1053 if (widget
) return widget
->GetNativeData(NS_NATIVE_WINDOW
);
1058 LocalAccessible
* DocAccessible::GetAccessibleByUniqueIDInSubtree(
1060 LocalAccessible
* child
= GetAccessibleByUniqueID(aUniqueID
);
1061 if (child
) return child
;
1063 uint32_t childDocCount
= mChildDocuments
.Length();
1064 for (uint32_t childDocIdx
= 0; childDocIdx
< childDocCount
; childDocIdx
++) {
1065 DocAccessible
* childDocument
= mChildDocuments
.ElementAt(childDocIdx
);
1066 child
= childDocument
->GetAccessibleByUniqueIDInSubtree(aUniqueID
);
1067 if (child
) return child
;
1073 LocalAccessible
* DocAccessible::GetAccessibleOrContainer(
1074 nsINode
* aNode
, bool aNoContainerIfPruned
) const {
1075 if (!aNode
|| !aNode
->GetComposedDoc()) {
1079 nsINode
* start
= aNode
;
1080 if (auto* shadowRoot
= dom::ShadowRoot::FromNode(aNode
)) {
1081 // This can happen, for example, when called within
1082 // SelectionManager::ProcessSelectionChanged due to focusing a direct
1083 // child of a shadow root.
1084 // GetFlattenedTreeParent works on children of a shadow root, but not the
1085 // shadow root itself.
1086 start
= shadowRoot
->GetHost();
1092 for (nsINode
* currNode
: dom::InclusiveFlatTreeAncestors(*start
)) {
1093 // No container if is inside of aria-hidden subtree.
1094 if (aNoContainerIfPruned
&& currNode
->IsElement() &&
1095 aria::HasDefinedARIAHidden(currNode
->AsElement())) {
1099 // Check if node is in zero-sized map
1100 if (aNoContainerIfPruned
&& currNode
->IsHTMLElement(nsGkAtoms::map
)) {
1101 if (nsIFrame
* frame
= currNode
->AsContent()->GetPrimaryFrame()) {
1102 if (nsLayoutUtils::GetAllInFlowRectsUnion(frame
, frame
->GetParent())
1109 if (LocalAccessible
* accessible
= GetAccessible(currNode
)) {
1117 LocalAccessible
* DocAccessible::GetContainerAccessible(nsINode
* aNode
) const {
1118 return aNode
? GetAccessibleOrContainer(aNode
->GetFlattenedTreeParentNode())
1122 LocalAccessible
* DocAccessible::GetAccessibleOrDescendant(
1123 nsINode
* aNode
) const {
1124 LocalAccessible
* acc
= GetAccessible(aNode
);
1125 if (acc
) return acc
;
1127 if (aNode
== mContent
|| aNode
== mDocumentNode
->GetRootElement()) {
1128 // If the node is the doc's body or root element, return the doc accessible.
1129 return const_cast<DocAccessible
*>(this);
1132 acc
= GetContainerAccessible(aNode
);
1134 TreeWalker
walker(acc
, aNode
->AsContent(),
1135 TreeWalker::eWalkCache
| TreeWalker::eScoped
);
1136 return walker
.Next();
1142 void DocAccessible::BindToDocument(LocalAccessible
* aAccessible
,
1143 const nsRoleMapEntry
* aRoleMapEntry
) {
1144 // Put into DOM node cache.
1145 if (aAccessible
->IsNodeMapEntry()) {
1146 mNodeToAccessibleMap
.InsertOrUpdate(aAccessible
->GetNode(), aAccessible
);
1149 // Put into unique ID cache.
1150 mAccessibleCache
.InsertOrUpdate(aAccessible
->UniqueID(), RefPtr
{aAccessible
});
1152 aAccessible
->SetRoleMapEntry(aRoleMapEntry
);
1154 if (aAccessible
->HasOwnContent()) {
1155 AddDependentIDsFor(aAccessible
);
1157 nsIContent
* content
= aAccessible
->GetContent();
1158 if (content
->IsElement() &&
1159 content
->AsElement()->HasAttr(nsGkAtoms::aria_owns
)) {
1160 mNotificationController
->ScheduleRelocation(aAccessible
);
1165 mInsertedAccessibles
.EnsureInserted(aAccessible
);
1168 QueueCacheUpdateForDependentRelations(aAccessible
);
1171 void DocAccessible::UnbindFromDocument(LocalAccessible
* aAccessible
) {
1172 NS_ASSERTION(mAccessibleCache
.GetWeak(aAccessible
->UniqueID()),
1173 "Unbinding the unbound accessible!");
1175 // Fire focus event on accessible having DOM focus if last focus was removed
1177 if (FocusMgr()->WasLastFocused(aAccessible
)) {
1178 FocusMgr()->ActiveItemChanged(nullptr);
1180 if (logging::IsEnabled(logging::eFocus
)) {
1181 logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible
);
1186 // Remove an accessible from node-to-accessible map if it exists there.
1187 if (aAccessible
->IsNodeMapEntry() &&
1188 mNodeToAccessibleMap
.Get(aAccessible
->GetNode()) == aAccessible
) {
1189 mNodeToAccessibleMap
.Remove(aAccessible
->GetNode());
1192 aAccessible
->mStateFlags
|= eIsNotInDocument
;
1194 // Update XPCOM part.
1195 xpcAccessibleDocument
* xpcDoc
= GetAccService()->GetCachedXPCDocument(this);
1196 if (xpcDoc
) xpcDoc
->NotifyOfShutdown(aAccessible
);
1198 void* uniqueID
= aAccessible
->UniqueID();
1200 NS_ASSERTION(!aAccessible
->IsDefunct(), "Shutdown the shutdown accessible!");
1201 aAccessible
->Shutdown();
1203 mAccessibleCache
.Remove(uniqueID
);
1206 void DocAccessible::ContentInserted(nsIContent
* aStartChildNode
,
1207 nsIContent
* aEndChildNode
) {
1208 // Ignore content insertions until we constructed accessible tree. Otherwise
1209 // schedule tree update on content insertion after layout.
1210 if (!mNotificationController
|| !HasLoadState(eTreeConstructed
)) {
1214 // The frame constructor guarantees that only ranges with the same parent
1215 // arrive here in presence of dynamic changes to the page, see
1216 // nsCSSFrameConstructor::IssueSingleInsertNotifications' callers.
1217 nsINode
* parent
= aStartChildNode
->GetFlattenedTreeParentNode();
1222 LocalAccessible
* container
= AccessibleOrTrueContainer(parent
);
1227 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> list
;
1228 for (nsIContent
* node
= aStartChildNode
; node
!= aEndChildNode
;
1229 node
= node
->GetNextSibling()) {
1230 MOZ_ASSERT(parent
== node
->GetFlattenedTreeParentNode());
1231 if (PruneOrInsertSubtree(node
)) {
1232 list
.AppendElement(node
);
1236 mNotificationController
->ScheduleContentInsertion(container
, list
);
1239 void DocAccessible::ScheduleTreeUpdate(nsIContent
* aContent
) {
1240 if (mPendingUpdates
.Contains(aContent
)) {
1243 mPendingUpdates
.AppendElement(aContent
);
1244 mNotificationController
->ScheduleProcessing();
1247 void DocAccessible::ProcessPendingUpdates() {
1248 auto updates
= std::move(mPendingUpdates
);
1249 for (auto update
: updates
) {
1250 if (update
->GetComposedDoc() != mDocumentNode
) {
1253 // The pruning logic will take care of avoiding unnecessary notifications.
1254 ContentInserted(update
, update
->GetNextSibling());
1258 bool DocAccessible::PruneOrInsertSubtree(nsIContent
* aRoot
) {
1259 bool insert
= false;
1261 // In the case that we are, or are in, a shadow host, we need to assure
1262 // some accessibles are removed if they are not rendered anymore.
1263 nsIContent
* shadowHost
=
1264 aRoot
->GetShadowRoot() ? aRoot
: aRoot
->GetContainingShadowHost();
1266 // Check all explicit children in the host, if they are not slotted
1267 // then remove their accessibles and subtrees.
1268 for (nsIContent
* childNode
= shadowHost
->GetFirstChild(); childNode
;
1269 childNode
= childNode
->GetNextSibling()) {
1270 if (!childNode
->GetPrimaryFrame() &&
1271 !nsCoreUtils::CanCreateAccessibleWithoutFrame(childNode
)) {
1272 ContentRemoved(childNode
);
1276 // If this is a slot, check to see if its fallback content is rendered,
1277 // if not - remove it.
1278 if (aRoot
->IsHTMLElement(nsGkAtoms::slot
)) {
1279 for (nsIContent
* childNode
= aRoot
->GetFirstChild(); childNode
;
1280 childNode
= childNode
->GetNextSibling()) {
1281 if (!childNode
->GetPrimaryFrame() &&
1282 !nsCoreUtils::CanCreateAccessibleWithoutFrame(childNode
)) {
1283 ContentRemoved(childNode
);
1289 // If we already have an accessible, check if we need to remove it, recreate
1290 // it, or keep it in place.
1291 LocalAccessible
* acc
= GetAccessible(aRoot
);
1293 MOZ_ASSERT(aRoot
== acc
->GetContent(),
1294 "LocalAccessible has differing content!");
1296 if (logging::IsEnabled(logging::eTree
)) {
1298 "TREE", "inserted content already has accessible; doc: %p", this);
1299 logging::Node("content node", aRoot
);
1300 logging::AccessibleInfo("accessible node", acc
);
1305 nsIFrame
* frame
= acc
->GetFrame();
1307 acc
->MaybeQueueCacheUpdateForStyleChanges();
1310 // LocalAccessible has no frame and it's not display:contents. Remove it.
1311 // As well as removing the a11y subtree, we must also remove Accessibles
1312 // for DOM descendants, since some of these might be relocated Accessibles
1313 // and their DOM nodes are now hidden as well.
1314 if (!frame
&& !nsCoreUtils::CanCreateAccessibleWithoutFrame(aRoot
)) {
1315 ContentRemoved(aRoot
);
1319 // If the frame is hidden because its ancestor is specified with
1320 // `content-visibility: hidden`, remove its Accessible.
1321 if (frame
&& frame
->IsHiddenByContentVisibilityOnAnyAncestor(
1322 nsIFrame::IncludeContentVisibility::Hidden
)) {
1323 ContentRemoved(aRoot
);
1327 // If it's a XULLabel it was probably reframed because a `value` attribute
1328 // was added. The accessible creates its text leaf upon construction, so we
1329 // need to recreate. Remove it, and schedule for reconstruction.
1330 if (acc
->IsXULLabel()) {
1331 ContentRemoved(acc
);
1335 // It is a broken image that is being reframed because it either got
1336 // or lost an `alt` tag that would rerender this node as text.
1337 if (frame
&& (acc
->IsImage() != (frame
->AccessibleType() == eImageType
))) {
1338 ContentRemoved(aRoot
);
1342 // If the frame is an OuterDoc frame but this isn't an OuterDocAccessible,
1343 // we need to recreate the LocalAccessible. This can happen for embed or
1344 // object elements if their embedded content changes to be web content.
1345 if (frame
&& !acc
->IsOuterDoc() &&
1346 frame
->AccessibleType() == eOuterDocType
) {
1347 ContentRemoved(aRoot
);
1351 // If the content is focused, and is being re-framed, reset the selection
1352 // listener for the node because the previous selection listener is on the
1354 if (aRoot
->IsElement() && FocusMgr()->HasDOMFocus(aRoot
)) {
1355 SelectionMgr()->SetControlSelectionListener(aRoot
->AsElement());
1358 // If the accessible is a table, or table part, its layout table
1359 // status may have changed. We need to invalidate the associated
1360 // mac table cache, which listens for the following event. We don't
1361 // use this cache when the core cache is enabled, so to minimise event
1362 // traffic only fire this event when that cache is off.
1363 if (acc
->IsTable() || acc
->IsTableRow() || acc
->IsTableCell()) {
1364 LocalAccessible
* table
= nsAccUtils::TableFor(acc
);
1365 if (table
&& table
->IsTable()) {
1366 QueueCacheUpdate(table
, CacheDomain::Table
);
1370 // The accessible can be reparented or reordered in its parent.
1371 // We schedule it for reinsertion. For example, a slotted element
1372 // can change its slot attribute to a different slot.
1375 // If the frame is invisible, remove it.
1376 // Normally, layout sends explicit a11y notifications for visibility
1377 // changes (see SendA11yNotifications in RestyleManager). However, if a
1378 // visibility change also reconstructs the frame, we must handle it here.
1379 if (frame
&& !frame
->StyleVisibility()->IsVisible()) {
1380 ContentRemoved(aRoot
);
1381 // There might be visible descendants, so we want to walk the subtree.
1382 // However, we know we don't want to reinsert this node, so we set insert
1387 // If there is no current accessible, and the node has a frame, or is
1388 // display:contents, schedule it for insertion.
1389 if (aRoot
->GetPrimaryFrame() ||
1390 nsCoreUtils::CanCreateAccessibleWithoutFrame(aRoot
)) {
1391 // This may be a new subtree, the insertion process will recurse through
1393 if (!GetAccessibleOrDescendant(aRoot
)) {
1397 // Content is not an accessible, but has accessible descendants.
1398 // We schedule this container for insertion strictly for the case where it
1399 // itself now needs an accessible. We will still need to recurse into the
1400 // descendant content to prune accessibles, and in all likelyness to
1401 // insert accessibles since accessible insertions will likeley get missed
1402 // in an existing subtree.
1407 if (LocalAccessible
* container
= AccessibleOrTrueContainer(aRoot
)) {
1408 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> list
;
1409 dom::AllChildrenIterator iter
=
1410 dom::AllChildrenIterator(aRoot
, nsIContent::eAllChildren
, true);
1411 while (nsIContent
* childNode
= iter
.GetNextChild()) {
1412 if (PruneOrInsertSubtree(childNode
)) {
1413 list
.AppendElement(childNode
);
1417 if (!list
.IsEmpty()) {
1418 mNotificationController
->ScheduleContentInsertion(container
, list
);
1425 void DocAccessible::RecreateAccessible(nsIContent
* aContent
) {
1427 if (logging::IsEnabled(logging::eTree
)) {
1428 logging::MsgBegin("TREE", "accessible recreated");
1429 logging::Node("content", aContent
);
1434 // XXX: we shouldn't recreate whole accessible subtree, instead we should
1435 // subclass hide and show events to handle them separately and implement their
1436 // coalescence with normal hide and show events. Note, in this case they
1437 // should be coalesced with normal show/hide events.
1438 ContentRemoved(aContent
);
1439 ContentInserted(aContent
, aContent
->GetNextSibling());
1442 void DocAccessible::ProcessInvalidationList() {
1443 // Invalidate children of container accessible for each element in
1444 // invalidation list. Allow invalidation list insertions while container
1445 // children are recached.
1446 for (uint32_t idx
= 0; idx
< mInvalidationList
.Length(); idx
++) {
1447 nsIContent
* content
= mInvalidationList
[idx
];
1448 if (!HasAccessible(content
) && content
->HasID()) {
1449 LocalAccessible
* container
= GetContainerAccessible(content
);
1451 // Check if the node is a target of aria-owns, and if so, don't process
1452 // it here and let DoARIAOwnsRelocation process it.
1453 AttrRelProviders
* list
= GetRelProviders(
1454 content
->AsElement(), nsDependentAtomString(content
->GetID()));
1455 bool shouldProcess
= !!list
;
1456 if (shouldProcess
) {
1457 for (uint32_t idx
= 0; idx
< list
->Length(); idx
++) {
1458 if (list
->ElementAt(idx
)->mRelAttr
== nsGkAtoms::aria_owns
) {
1459 shouldProcess
= false;
1464 if (shouldProcess
) {
1465 ProcessContentInserted(container
, content
);
1472 mInvalidationList
.Clear();
1475 void DocAccessible::ProcessQueuedCacheUpdates() {
1476 AUTO_PROFILER_MARKER_TEXT("DocAccessible::ProcessQueuedCacheUpdates", A11Y
,
1478 PerfStats::AutoMetricRecording
<
1479 PerfStats::Metric::A11Y_ProcessQueuedCacheUpdate
>
1481 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
1483 nsTArray
<CacheData
> data
;
1484 for (auto [acc
, domain
] : mQueuedCacheUpdatesArray
) {
1485 if (acc
&& acc
->IsInDocument() && !acc
->IsDefunct()) {
1486 RefPtr
<AccAttributes
> fields
=
1487 acc
->BundleFieldsForCache(domain
, CacheUpdateType::Update
);
1489 if (fields
->Count()) {
1490 data
.AppendElement(CacheData(
1491 acc
->IsDoc() ? 0 : reinterpret_cast<uint64_t>(acc
->UniqueID()),
1497 mQueuedCacheUpdatesArray
.Clear();
1498 mQueuedCacheUpdatesHash
.Clear();
1500 if (mViewportCacheDirty
) {
1501 RefPtr
<AccAttributes
> fields
=
1502 BundleFieldsForCache(CacheDomain::Viewport
, CacheUpdateType::Update
);
1503 if (fields
->Count()) {
1504 data
.AppendElement(CacheData(0, fields
));
1506 mViewportCacheDirty
= false;
1509 if (data
.Length()) {
1510 IPCDoc()->SendCache(CacheUpdateType::Update
, data
);
1514 void DocAccessible::SendAccessiblesWillMove() {
1518 nsTArray
<uint64_t> ids
;
1519 for (LocalAccessible
* acc
: mMovedAccessibles
) {
1520 // If acc is defunct or not in a document, it was removed after it was
1522 if (!acc
->IsDefunct() && acc
->IsInDocument()) {
1523 ids
.AppendElement(reinterpret_cast<uintptr_t>(acc
->UniqueID()));
1524 // acc might have been re-parented. Since we cache bounds relative to the
1525 // parent, we need to update the cache.
1526 QueueCacheUpdate(acc
, CacheDomain::Bounds
);
1529 if (!ids
.IsEmpty()) {
1530 mIPCDoc
->SendAccessiblesWillMove(ids
);
1534 LocalAccessible
* DocAccessible::GetAccessibleEvenIfNotInMap(
1535 nsINode
* aNode
) const {
1536 if (!aNode
->IsContent() ||
1537 !aNode
->AsContent()->IsHTMLElement(nsGkAtoms::area
)) {
1538 return GetAccessible(aNode
);
1541 // XXX Bug 135040, incorrect when multiple images use the same map.
1542 nsIFrame
* frame
= aNode
->AsContent()->GetPrimaryFrame();
1543 nsImageFrame
* imageFrame
= do_QueryFrame(frame
);
1545 LocalAccessible
* parent
= GetAccessible(imageFrame
->GetContent());
1547 if (HTMLImageMapAccessible
* imageMap
= parent
->AsImageMap()) {
1548 return imageMap
->GetChildAccessibleFor(aNode
);
1554 return GetAccessible(aNode
);
1557 ////////////////////////////////////////////////////////////////////////////////
1558 // Protected members
1560 void DocAccessible::NotifyOfLoading(bool aIsReloading
) {
1561 // Mark the document accessible as loading, if it stays alive then we'll mark
1562 // it as loaded when we receive proper notification.
1563 mLoadState
&= ~eDOMLoaded
;
1565 if (!IsLoadEventTarget()) return;
1567 if (aIsReloading
&& !mLoadEventType
&&
1568 // We can't fire events on a document whose tree isn't constructed yet.
1569 HasLoadState(eTreeConstructed
)) {
1570 // Fire reload and state busy events on existing document accessible while
1571 // event from user input flag can be calculated properly and accessible
1572 // is alive. When new document gets loaded then this one is destroyed.
1573 RefPtr
<AccEvent
> reloadEvent
=
1574 new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD
, this);
1575 nsEventShell::FireEvent(reloadEvent
);
1578 // Fire state busy change event. Use delayed event since we don't care
1579 // actually if event isn't delivered when the document goes away like a shot.
1580 RefPtr
<AccEvent
> stateEvent
=
1581 new AccStateChangeEvent(this, states::BUSY
, true);
1582 FireDelayedEvent(stateEvent
);
1585 void DocAccessible::DoInitialUpdate() {
1586 AUTO_PROFILER_MARKER_TEXT("DocAccessible::DoInitialUpdate", A11Y
, {}, ""_ns
);
1587 PerfStats::AutoMetricRecording
<PerfStats::Metric::A11Y_DoInitialUpdate
>
1589 // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
1591 if (nsCoreUtils::IsTopLevelContentDocInProcess(mDocumentNode
)) {
1592 mDocFlags
|= eTopLevelContentDocInProcess
;
1593 if (IPCAccessibilityActive()) {
1594 nsIDocShell
* docShell
= mDocumentNode
->GetDocShell();
1595 if (RefPtr
<dom::BrowserChild
> browserChild
=
1596 dom::BrowserChild::GetFrom(docShell
)) {
1597 // In content processes, top level content documents are always
1599 MOZ_ASSERT(IsRoot());
1600 DocAccessibleChild
* ipcDoc
= IPCDoc();
1602 ipcDoc
= new DocAccessibleChild(this, browserChild
);
1603 MOZ_RELEASE_ASSERT(browserChild
->SendPDocAccessibleConstructor(
1604 ipcDoc
, nullptr, 0, mDocumentNode
->GetBrowsingContext()));
1605 // trying to recover from this failing is problematic
1612 mLoadState
|= eTreeConstructed
;
1614 // Set up a root element and ARIA role mapping.
1615 UpdateRootElIfNeeded();
1617 // Build initial tree.
1618 CacheChildrenInSubtree(this);
1620 if (logging::IsEnabled(logging::eVerbose
)) {
1621 logging::Tree("TREE", "Initial subtree", this);
1623 if (logging::IsEnabled(logging::eTreeSize
)) {
1624 logging::TreeSize("TREE SIZE", "Initial subtree", this);
1628 // Fire reorder event after the document tree is constructed. Note, since
1629 // this reorder event is processed by parent document then events targeted to
1630 // this document may be fired prior to this reorder event. If this is
1631 // a problem then consider to keep event processing per tab document.
1633 RefPtr
<AccReorderEvent
> reorderEvent
= new AccReorderEvent(LocalParent());
1634 ParentDocument()->FireDelayedEvent(reorderEvent
);
1637 if (ipc::ProcessChild::ExpectingShutdown()) {
1640 if (IPCAccessibilityActive()) {
1641 DocAccessibleChild
* ipcDoc
= IPCDoc();
1644 // Send an initial update for this document and its attributes. Each acc
1645 // contained in this doc will have its initial update sent in
1646 // `InsertIntoIpcTree`.
1647 SendCache(CacheDomain::All
, CacheUpdateType::Initial
);
1649 for (auto idx
= 0U; idx
< mChildren
.Length(); idx
++) {
1650 ipcDoc
->InsertIntoIpcTree(mChildren
.ElementAt(idx
), true);
1656 void DocAccessible::ProcessLoad() {
1657 mLoadState
|= eCompletelyLoaded
;
1660 if (logging::IsEnabled(logging::eDocLoad
)) {
1661 logging::DocCompleteLoad(this, IsLoadEventTarget());
1665 // Do not fire document complete/stop events for root chrome document
1666 // accessibles and for frame/iframe documents because
1667 // a) screen readers start working on focus event in the case of root chrome
1669 // b) document load event on sub documents causes screen readers to act is if
1670 // entire page is reloaded.
1671 if (!IsLoadEventTarget()) return;
1673 // Fire complete/load stopped if the load event type is given.
1674 if (mLoadEventType
) {
1675 RefPtr
<AccEvent
> loadEvent
= new AccEvent(mLoadEventType
, this);
1676 FireDelayedEvent(loadEvent
);
1681 // Fire busy state change event.
1682 RefPtr
<AccEvent
> stateEvent
=
1683 new AccStateChangeEvent(this, states::BUSY
, false);
1684 FireDelayedEvent(stateEvent
);
1687 void DocAccessible::AddDependentIDsFor(LocalAccessible
* aRelProvider
,
1689 dom::Element
* relProviderEl
= aRelProvider
->Elm();
1690 if (!relProviderEl
) return;
1692 for (uint32_t idx
= 0; idx
< kRelationAttrsLen
; idx
++) {
1693 nsStaticAtom
* relAttr
= kRelationAttrs
[idx
];
1694 if (aRelAttr
&& aRelAttr
!= relAttr
) continue;
1696 if (relAttr
== nsGkAtoms::_for
) {
1697 if (!relProviderEl
->IsAnyOfHTMLElements(nsGkAtoms::label
,
1698 nsGkAtoms::output
)) {
1702 } else if (relAttr
== nsGkAtoms::control
) {
1703 if (!relProviderEl
->IsAnyOfXULElements(nsGkAtoms::label
,
1704 nsGkAtoms::description
)) {
1709 IDRefsIterator
iter(this, relProviderEl
, relAttr
);
1711 const nsDependentSubstring id
= iter
.NextID();
1712 if (id
.IsEmpty()) break;
1714 AttrRelProviders
* providers
= GetOrCreateRelProviders(relProviderEl
, id
);
1716 AttrRelProvider
* provider
= new AttrRelProvider(relAttr
, relProviderEl
);
1718 providers
->AppendElement(provider
);
1720 // We've got here during the children caching. If the referenced
1721 // content is not accessible then store it to pend its container
1722 // children invalidation (this happens immediately after the caching
1724 nsIContent
* dependentContent
= iter
.GetElem(id
);
1725 if (dependentContent
) {
1726 if (!HasAccessible(dependentContent
)) {
1727 mInvalidationList
.AppendElement(dependentContent
);
1734 // If the relation attribute is given then we don't have anything else to
1736 if (aRelAttr
) break;
1739 // Make sure to schedule the tree update if needed.
1740 mNotificationController
->ScheduleProcessing();
1743 void DocAccessible::RemoveDependentIDsFor(LocalAccessible
* aRelProvider
,
1745 dom::Element
* relProviderElm
= aRelProvider
->Elm();
1746 if (!relProviderElm
) return;
1748 for (uint32_t idx
= 0; idx
< kRelationAttrsLen
; idx
++) {
1749 nsStaticAtom
* relAttr
= kRelationAttrs
[idx
];
1750 if (aRelAttr
&& aRelAttr
!= kRelationAttrs
[idx
]) continue;
1752 IDRefsIterator
iter(this, relProviderElm
, relAttr
);
1754 const nsDependentSubstring id
= iter
.NextID();
1755 if (id
.IsEmpty()) break;
1757 AttrRelProviders
* providers
= GetRelProviders(relProviderElm
, id
);
1759 providers
->RemoveElementsBy(
1760 [relAttr
, relProviderElm
](const auto& provider
) {
1761 return provider
->mRelAttr
== relAttr
&&
1762 provider
->mContent
== relProviderElm
;
1765 RemoveRelProvidersIfEmpty(relProviderElm
, id
);
1769 // If the relation attribute is given then we don't have anything else to
1771 if (aRelAttr
) break;
1775 bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element
* aElement
,
1776 nsAtom
* aAttribute
) {
1777 if (aAttribute
== nsGkAtoms::role
) {
1778 // It is common for js libraries to set the role on the body element after
1779 // the document has loaded. In this case we just update the role map entry.
1780 if (mContent
== aElement
) {
1781 SetRoleMapEntryForDoc(aElement
);
1783 mIPCDoc
->SendRoleChangedEvent(Role(), mRoleMapEntryIndex
);
1789 // Recreate the accessible when role is changed because we might require a
1790 // different accessible class for the new role or the accessible may expose
1791 // a different sets of interfaces (COM restriction).
1792 RecreateAccessible(aElement
);
1797 if (aAttribute
== nsGkAtoms::multiple
) {
1798 if (dom::HTMLSelectElement
* select
=
1799 dom::HTMLSelectElement::FromNode(aElement
)) {
1800 if (select
->Size() <= 1) {
1801 // Adding the 'multiple' attribute to a select that has a size of 1
1802 // creates a listbox as opposed to a combobox with a popup combobox
1803 // list. Removing the attribute does the opposite.
1804 RecreateAccessible(aElement
);
1810 if (aAttribute
== nsGkAtoms::size
&&
1811 aElement
->IsHTMLElement(nsGkAtoms::select
)) {
1812 // Changing the size of a select element can potentially change it from a
1813 // combobox button to a listbox with different underlying implementations.
1814 RecreateAccessible(aElement
);
1818 if (aAttribute
== nsGkAtoms::type
) {
1819 // If the input[type] changes, we should recreate the accessible.
1820 RecreateAccessible(aElement
);
1824 if (aAttribute
== nsGkAtoms::href
&&
1825 !nsCoreUtils::HasClickListener(aElement
)) {
1826 // If the href is added or removed for a or area elements without click
1827 // listeners, we need to recreate the accessible since the role might have
1828 // changed. Without an href or click listener, the accessible must be a
1830 if (aElement
->IsHTMLElement(nsGkAtoms::a
)) {
1831 LocalAccessible
* acc
= GetAccessible(aElement
);
1835 if (acc
->IsHTMLLink() != aElement
->HasAttr(nsGkAtoms::href
)) {
1836 RecreateAccessible(aElement
);
1839 } else if (aElement
->IsHTMLElement(nsGkAtoms::area
)) {
1840 // For area accessibles, we have to recreate the entire image map, since
1841 // the image map accessible manages the tree itself.
1842 LocalAccessible
* areaAcc
= GetAccessibleEvenIfNotInMap(aElement
);
1843 if (!areaAcc
|| !areaAcc
->LocalParent()) {
1846 RecreateAccessible(areaAcc
->LocalParent()->GetContent());
1851 if (aElement
->IsHTMLElement(nsGkAtoms::img
) && aAttribute
== nsGkAtoms::alt
) {
1852 // If alt text changes on an img element, we may want to create or remove an
1853 // accessible for that img.
1854 if (nsAccessibilityService::ShouldCreateImgAccessible(aElement
, this)) {
1855 if (GetAccessible(aElement
)) {
1856 // If the accessible already exists, there's no need to create one.
1859 ContentInserted(aElement
, aElement
->GetNextSibling());
1861 ContentRemoved(aElement
);
1869 void DocAccessible::UpdateRootElIfNeeded() {
1870 dom::Element
* rootEl
= mDocumentNode
->GetBodyElement();
1872 rootEl
= mDocumentNode
->GetRootElement();
1874 if (rootEl
!= mContent
) {
1876 SetRoleMapEntryForDoc(rootEl
);
1878 mIPCDoc
->SendRoleChangedEvent(Role(), mRoleMapEntryIndex
);
1884 * Content insertion helper.
1886 class InsertIterator final
{
1888 InsertIterator(LocalAccessible
* aContext
,
1889 const nsTArray
<nsCOMPtr
<nsIContent
>>* aNodes
)
1891 mChildBefore(nullptr),
1895 MOZ_ASSERT(aContext
, "No context");
1896 MOZ_ASSERT(aNodes
, "No nodes to search for accessible elements");
1897 MOZ_COUNT_CTOR(InsertIterator
);
1899 MOZ_COUNTED_DTOR(InsertIterator
)
1901 LocalAccessible
* Context() const { return mWalker
.Context(); }
1902 LocalAccessible
* Child() const { return mChild
; }
1903 LocalAccessible
* ChildBefore() const { return mChildBefore
; }
1904 DocAccessible
* Document() const { return mWalker
.Document(); }
1907 * Iterates to a next accessible within the inserted content.
1913 mChildBefore
= nullptr;
1917 LocalAccessible
* mChild
;
1918 LocalAccessible
* mChildBefore
;
1921 const nsTArray
<nsCOMPtr
<nsIContent
>>* mNodes
;
1922 nsTHashSet
<nsPtrHashKey
<const nsIContent
>> mProcessedNodes
;
1926 bool InsertIterator::Next() {
1927 if (mNodesIdx
> 0) {
1928 // If we already processed the first node in the mNodes list,
1929 // check if we can just use the walker to get its next sibling.
1930 LocalAccessible
* nextChild
= mWalker
.Next();
1932 mChildBefore
= mChild
;
1938 while (mNodesIdx
< mNodes
->Length()) {
1939 nsIContent
* node
= mNodes
->ElementAt(mNodesIdx
++);
1940 // Check to see if we already processed this node with this iterator.
1941 // this can happen if we get two redundant insertions in the case of a
1942 // text and frame insertion.
1943 if (!mProcessedNodes
.EnsureInserted(node
)) {
1947 LocalAccessible
* container
= Document()->AccessibleOrTrueContainer(
1948 node
->GetFlattenedTreeParentNode(), true);
1949 // Ignore nodes that are not contained by the container anymore.
1950 // The container might be changed, for example, because of the subsequent
1951 // overlapping content insertion (i.e. other content was inserted between
1952 // this inserted content and its container or the content was reinserted
1953 // into different container of unrelated part of tree). To avoid a double
1954 // processing of the content insertion ignore this insertion notification.
1955 // Note, the inserted content might be not in tree at all at this point
1956 // what means there's no container. Ignore the insertion too.
1957 if (container
!= Context()) {
1961 // HTML comboboxes have no-content list accessible as an intermediate
1962 // containing all options.
1963 if (container
->IsHTMLCombobox()) {
1964 container
= container
->LocalFirstChild();
1967 if (!container
->IsAcceptableChild(node
)) {
1972 logging::TreeInfo("traversing an inserted node", logging::eVerbose
,
1973 "container", container
, "node", node
);
1976 nsIContent
* prevNode
= mChild
? mChild
->GetContent() : nullptr;
1977 if (prevNode
&& prevNode
->GetNextSibling() == node
) {
1978 // If inserted nodes are siblings then just move the walker next.
1979 LocalAccessible
* nextChild
= mWalker
.Scope(node
);
1981 mChildBefore
= mChild
;
1986 // Otherwise use a new walker to find this node in the container's
1987 // subtree, and retrieve its preceding sibling.
1988 TreeWalker
finder(container
);
1989 if (finder
.Seek(node
)) {
1990 mChild
= mWalker
.Scope(node
);
1992 MOZ_ASSERT(!mChild
->IsRelocated(), "child cannot be aria owned");
1993 mChildBefore
= finder
.Prev();
2003 void DocAccessible::ProcessContentInserted(
2004 LocalAccessible
* aContainer
, const nsTArray
<nsCOMPtr
<nsIContent
>>* aNodes
) {
2005 // Process insertions if the container accessible is still in tree.
2006 if (!aContainer
->IsInDocument()) {
2010 // If new root content has been inserted then update it.
2011 if (aContainer
== this) {
2012 UpdateRootElIfNeeded();
2015 InsertIterator
iter(aContainer
, aNodes
);
2021 logging::TreeInfo("children before insertion", logging::eVerbose
, aContainer
);
2024 TreeMutation
mt(aContainer
);
2025 bool inserted
= false;
2027 LocalAccessible
* parent
= iter
.Child()->LocalParent();
2029 LocalAccessible
* previousSibling
= iter
.ChildBefore();
2030 if (parent
!= aContainer
||
2031 iter
.Child()->LocalPrevSibling() != previousSibling
) {
2032 if (previousSibling
&& previousSibling
->LocalParent() != aContainer
) {
2033 // previousSibling hasn't been moved into aContainer yet.
2034 // previousSibling should be later in the insertion list, so the tree
2035 // will get adjusted when we process it later.
2036 MOZ_DIAGNOSTIC_ASSERT(parent
== aContainer
,
2037 "Child moving to new parent, but previous "
2038 "sibling in wrong parent");
2042 logging::TreeInfo("relocating accessible", 0, "old parent", parent
,
2043 "new parent", aContainer
, "child", iter
.Child(),
2046 MoveChild(iter
.Child(), aContainer
,
2047 previousSibling
? previousSibling
->IndexInParent() + 1 : 0);
2053 if (aContainer
->InsertAfter(iter
.Child(), iter
.ChildBefore())) {
2055 logging::TreeInfo("accessible was inserted", 0, "container", aContainer
,
2056 "child", iter
.Child(), nullptr);
2059 CreateSubtree(iter
.Child());
2060 mt
.AfterInsertion(iter
.Child());
2065 MOZ_ASSERT_UNREACHABLE("accessible was rejected");
2067 } while (iter
.Next());
2072 logging::TreeInfo("children after insertion", logging::eVerbose
, aContainer
);
2075 // We might not have actually inserted anything if layout frame reconstruction
2078 FireEventsOnInsertion(aContainer
);
2082 void DocAccessible::ProcessContentInserted(LocalAccessible
* aContainer
,
2083 nsIContent
* aNode
) {
2084 if (!aContainer
->IsInDocument()) {
2089 logging::TreeInfo("children before insertion", logging::eVerbose
, aContainer
);
2093 logging::TreeInfo("traversing an inserted node", logging::eVerbose
,
2094 "container", aContainer
, "node", aNode
);
2097 TreeWalker
walker(aContainer
);
2098 if (aContainer
->IsAcceptableChild(aNode
) && walker
.Seek(aNode
)) {
2099 LocalAccessible
* child
= GetAccessible(aNode
);
2101 child
= GetAccService()->CreateAccessible(aNode
, aContainer
);
2105 TreeMutation
mt(aContainer
);
2106 if (!aContainer
->InsertAfter(child
, walker
.Prev())) {
2109 CreateSubtree(child
);
2110 mt
.AfterInsertion(child
);
2113 FireEventsOnInsertion(aContainer
);
2118 logging::TreeInfo("children after insertion", logging::eVerbose
, aContainer
);
2122 void DocAccessible::FireEventsOnInsertion(LocalAccessible
* aContainer
) {
2123 // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
2125 if (aContainer
->IsAlert() || aContainer
->IsInsideAlert()) {
2126 LocalAccessible
* ancestor
= aContainer
;
2128 if (ancestor
->IsAlert()) {
2129 FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT
, ancestor
);
2132 } while ((ancestor
= ancestor
->LocalParent()));
2136 void DocAccessible::ContentRemoved(LocalAccessible
* aChild
) {
2137 MOZ_DIAGNOSTIC_ASSERT(aChild
!= this, "Should never be called for the doc");
2138 LocalAccessible
* parent
= aChild
->LocalParent();
2139 MOZ_DIAGNOSTIC_ASSERT(parent
, "Unattached accessible from tree");
2142 logging::TreeInfo("process content removal", 0, "container", parent
, "child",
2146 // XXX: event coalescence may kill us
2147 RefPtr
<LocalAccessible
> kungFuDeathGripChild(aChild
);
2149 TreeMutation
mt(parent
);
2150 mt
.BeforeRemoval(aChild
);
2152 if (aChild
->IsDefunct()) {
2153 MOZ_ASSERT_UNREACHABLE("Event coalescence killed the accessible");
2158 MOZ_DIAGNOSTIC_ASSERT(aChild
->LocalParent(), "Alive but unparented #1");
2160 if (aChild
->IsRelocated()) {
2161 nsTArray
<RefPtr
<LocalAccessible
>>* owned
= mARIAOwnsHash
.Get(parent
);
2162 MOZ_ASSERT(owned
, "IsRelocated flag is out of sync with mARIAOwnsHash");
2163 owned
->RemoveElement(aChild
);
2164 if (owned
->Length() == 0) {
2165 mARIAOwnsHash
.Remove(parent
);
2168 MOZ_DIAGNOSTIC_ASSERT(aChild
->LocalParent(), "Unparented #2");
2169 UncacheChildrenInSubtree(aChild
);
2170 parent
->RemoveChild(aChild
);
2175 void DocAccessible::ContentRemoved(nsIContent
* aContentNode
) {
2176 if (!mRemovedNodes
.EnsureInserted(aContentNode
)) {
2180 // If child node is not accessible then look for its accessible children.
2181 LocalAccessible
* acc
= GetAccessible(aContentNode
);
2183 ContentRemoved(acc
);
2186 dom::AllChildrenIterator iter
=
2187 dom::AllChildrenIterator(aContentNode
, nsIContent::eAllChildren
, true);
2188 while (nsIContent
* childNode
= iter
.GetNextChild()) {
2189 ContentRemoved(childNode
);
2192 // If this node has a shadow root, remove its explicit children too.
2193 // The host node may be removed after the shadow root was attached, and
2194 // before we asynchronously prune the light DOM and construct the shadow DOM.
2195 // If this is a case where the node does not have its own accessible, we will
2196 // not recurse into its current children, so we need to use an
2197 // ExplicitChildIterator in order to get its accessible children in the light
2198 // DOM, since they are not accessible anymore via AllChildrenIterator.
2199 if (aContentNode
->GetShadowRoot()) {
2200 for (nsIContent
* childNode
= aContentNode
->GetFirstChild(); childNode
;
2201 childNode
= childNode
->GetNextSibling()) {
2202 ContentRemoved(childNode
);
2207 bool DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent
* aElement
) {
2208 if (!aElement
->HasID()) return false;
2210 AttrRelProviders
* list
= GetRelProviders(
2211 aElement
->AsElement(), nsDependentAtomString(aElement
->GetID()));
2213 for (uint32_t idx
= 0; idx
< list
->Length(); idx
++) {
2214 if (list
->ElementAt(idx
)->mRelAttr
== nsGkAtoms::aria_owns
) {
2215 LocalAccessible
* owner
= GetAccessible(list
->ElementAt(idx
)->mContent
);
2217 mNotificationController
->ScheduleRelocation(owner
);
2227 void DocAccessible::DoARIAOwnsRelocation(LocalAccessible
* aOwner
) {
2228 MOZ_ASSERT(aOwner
, "aOwner must be a valid pointer");
2229 MOZ_ASSERT(aOwner
->Elm(), "aOwner->Elm() must be a valid pointer");
2232 logging::TreeInfo("aria owns relocation", logging::eVerbose
, aOwner
);
2235 nsTArray
<RefPtr
<LocalAccessible
>>* owned
=
2236 mARIAOwnsHash
.GetOrInsertNew(aOwner
);
2238 IDRefsIterator
iter(this, aOwner
->Elm(), nsGkAtoms::aria_owns
);
2240 while (nsIContent
* childEl
= iter
.NextElem()) {
2241 LocalAccessible
* child
= GetAccessible(childEl
);
2242 auto insertIdx
= aOwner
->ChildCount() - owned
->Length() + idx
;
2244 // Make an attempt to create an accessible if it wasn't created yet.
2246 // An owned child cannot be an ancestor of the owner.
2249 for (LocalAccessible
* parent
= aOwner
; parent
&& !parent
->IsDoc();
2250 parent
= parent
->LocalParent()) {
2252 if (parent
->Elm()->IsInclusiveDescendantOf(childEl
)) {
2257 // We need to do the DOM descendant check again whenever the DOM
2258 // lineage changes. If parent is relocated, that means the next
2259 // ancestor will have a different DOM lineage.
2260 check
= parent
->IsRelocated();
2266 if (aOwner
->IsAcceptableChild(childEl
)) {
2267 child
= GetAccService()->CreateAccessible(childEl
, aOwner
);
2269 TreeMutation
imut(aOwner
);
2270 aOwner
->InsertChildAt(insertIdx
, child
);
2271 imut
.AfterInsertion(child
);
2274 child
->SetRelocated(true);
2275 owned
->InsertElementAt(idx
, child
);
2278 // Create subtree before adjusting the insertion index, since subtree
2279 // creation may alter children in the container.
2280 CreateSubtree(child
);
2281 FireEventsOnInsertion(aOwner
);
2288 logging::TreeInfo("aria owns traversal", logging::eVerbose
, "candidate",
2292 if (owned
->IndexOf(child
) < idx
) {
2293 continue; // ignore second entry of same ID
2296 // Same child on same position, no change.
2297 if (child
->LocalParent() == aOwner
) {
2298 int32_t indexInParent
= child
->IndexInParent();
2300 // The child is being placed in its current index,
2301 // eg. aria-owns='id1 id2 id3' is changed to aria-owns='id3 id2 id1'.
2302 if (indexInParent
== static_cast<int32_t>(insertIdx
)) {
2303 MOZ_ASSERT(child
->IsRelocated(),
2304 "A child, having an index in parent from aria ownded "
2305 "indices range, has to be aria owned");
2306 MOZ_ASSERT(owned
->ElementAt(idx
) == child
,
2307 "Unexpected child in ARIA owned array");
2312 // The child is being inserted directly after its current index,
2313 // resulting in a no-move case. This will happen when a parent aria-owns
2314 // its last ordinal child:
2315 // <ul aria-owns='id2'><li id='id1'></li><li id='id2'></li></ul>
2316 if (indexInParent
== static_cast<int32_t>(insertIdx
) - 1) {
2317 MOZ_ASSERT(!child
->IsRelocated(),
2318 "Child should be in its ordinal position");
2319 child
->SetRelocated(true);
2320 owned
->InsertElementAt(idx
, child
);
2326 MOZ_ASSERT(owned
->SafeElementAt(idx
) != child
, "Already in place!");
2328 // A new child is found, check for loops.
2329 if (child
->LocalParent() != aOwner
) {
2330 // Child is aria-owned by another container, skip.
2331 if (child
->IsRelocated()) {
2335 LocalAccessible
* parent
= aOwner
;
2336 while (parent
&& parent
!= child
&& !parent
->IsDoc()) {
2337 parent
= parent
->LocalParent();
2339 // A referred child cannot be a parent of the owner.
2340 if (parent
== child
) {
2345 if (MoveChild(child
, aOwner
, insertIdx
)) {
2346 child
->SetRelocated(true);
2347 MOZ_ASSERT(owned
== mARIAOwnsHash
.Get(aOwner
));
2348 owned
= mARIAOwnsHash
.GetOrInsertNew(aOwner
);
2349 owned
->InsertElementAt(idx
, child
);
2354 // Put back children that are not seized anymore.
2355 PutChildrenBack(owned
, idx
);
2356 if (owned
->Length() == 0) {
2357 mARIAOwnsHash
.Remove(aOwner
);
2361 void DocAccessible::PutChildrenBack(
2362 nsTArray
<RefPtr
<LocalAccessible
>>* aChildren
, uint32_t aStartIdx
) {
2363 MOZ_ASSERT(aStartIdx
<= aChildren
->Length(), "Wrong removal index");
2365 for (auto idx
= aStartIdx
; idx
< aChildren
->Length(); idx
++) {
2366 LocalAccessible
* child
= aChildren
->ElementAt(idx
);
2367 if (!child
->IsInDocument()) {
2371 // Remove the child from the owner
2372 LocalAccessible
* owner
= child
->LocalParent();
2374 NS_ERROR("Cannot put the child back. No parent, a broken tree.");
2379 logging::TreeInfo("aria owns put child back", 0, "old parent", owner
,
2380 "child", child
, nullptr);
2383 // Unset relocated flag to find an insertion point for the child.
2384 child
->SetRelocated(false);
2386 nsIContent
* content
= child
->GetContent();
2387 int32_t idxInParent
= -1;
2388 LocalAccessible
* origContainer
=
2389 AccessibleOrTrueContainer(content
->GetFlattenedTreeParentNode());
2390 // This node has probably been detached or removed from the DOM, so we have
2391 // nowhere to move it.
2392 if (!origContainer
) {
2396 // If the target container or any of its ancestors aren't in the document,
2397 // there's no need to determine where the child should go for relocation
2398 // since the target tree is going away.
2399 bool origContainerHasOutOfDocAncestor
= false;
2400 LocalAccessible
* ancestor
= origContainer
;
2402 if (ancestor
->IsDoc()) {
2405 if (!ancestor
->IsInDocument()) {
2406 origContainerHasOutOfDocAncestor
= true;
2409 ancestor
= ancestor
->LocalParent();
2411 if (origContainerHasOutOfDocAncestor
) {
2415 TreeWalker
walker(origContainer
);
2416 if (!walker
.Seek(content
)) {
2419 LocalAccessible
* prevChild
= walker
.Prev();
2421 idxInParent
= prevChild
->IndexInParent() + 1;
2422 MOZ_DIAGNOSTIC_ASSERT(origContainer
== prevChild
->LocalParent(),
2424 origContainer
= prevChild
->LocalParent();
2429 // The child may have already be in its ordinal place for 2 reasons:
2430 // 1. It was the last ordinal child, and the first aria-owned child.
2431 // given: <ul id="list" aria-owns="b"><li id="a"></li><li
2432 // id="b"></li></ul> after load: $("list").setAttribute("aria-owns", "");
2433 // 2. The preceding adopted children were just reclaimed, eg:
2434 // given: <ul id="list"><li id="b"></li></ul>
2435 // after load: $("list").setAttribute("aria-owns", "a b");
2436 // later: $("list").setAttribute("aria-owns", "");
2437 if (origContainer
!= owner
|| child
->IndexInParent() != idxInParent
) {
2438 // Only attempt to move the child if the target container would accept it.
2439 // Otherwise, just allow it to be removed from the tree, since it would
2440 // not be allowed in normal tree creation.
2441 if (origContainer
->IsAcceptableChild(child
->GetContent())) {
2442 DebugOnly
<bool> moved
= MoveChild(child
, origContainer
, idxInParent
);
2443 MOZ_ASSERT(moved
, "Failed to put child back.");
2446 MOZ_ASSERT(!child
->LocalPrevSibling() ||
2447 !child
->LocalPrevSibling()->IsRelocated(),
2448 "No relocated child should appear before this one");
2449 MOZ_ASSERT(!child
->LocalNextSibling() ||
2450 child
->LocalNextSibling()->IsRelocated(),
2451 "No ordinal child should appear after this one");
2455 aChildren
->RemoveLastElements(aChildren
->Length() - aStartIdx
);
2458 void DocAccessible::TrackMovedAccessible(LocalAccessible
* aAcc
) {
2459 MOZ_ASSERT(aAcc
->mDoc
== this);
2460 // If an Accessible is inserted and moved during the same tick, don't track
2461 // it as a move because it hasn't been shown yet.
2462 if (!mInsertedAccessibles
.Contains(aAcc
)) {
2463 mMovedAccessibles
.EnsureInserted(aAcc
);
2465 // When we move an Accessible, we're also moving its descendants.
2466 if (aAcc
->IsOuterDoc()) {
2467 // Don't descend into other documents.
2470 for (uint32_t c
= 0, count
= aAcc
->ContentChildCount(); c
< count
; ++c
) {
2471 TrackMovedAccessible(aAcc
->ContentChildAt(c
));
2475 bool DocAccessible::MoveChild(LocalAccessible
* aChild
,
2476 LocalAccessible
* aNewParent
,
2477 int32_t aIdxInParent
) {
2478 MOZ_ASSERT(aChild
, "No child");
2479 MOZ_ASSERT(aChild
->LocalParent(), "No parent");
2480 // We can't guarantee MoveChild works correctly for accessibilities storing
2481 // children outside mChildren.
2483 aIdxInParent
<= static_cast<int32_t>(aNewParent
->mChildren
.Length()),
2484 "Wrong insertion point for a moving child");
2486 LocalAccessible
* curParent
= aChild
->LocalParent();
2488 if (!aNewParent
->IsAcceptableChild(aChild
->GetContent())) {
2493 logging::TreeInfo("move child", 0, "old parent", curParent
, "new parent",
2494 aNewParent
, "child", aChild
, nullptr);
2497 // Forget aria-owns info in case of ARIA owned element. The caller is expected
2498 // to update it if needed.
2499 if (aChild
->IsRelocated()) {
2500 aChild
->SetRelocated(false);
2501 nsTArray
<RefPtr
<LocalAccessible
>>* owned
= mARIAOwnsHash
.Get(curParent
);
2502 MOZ_ASSERT(owned
, "IsRelocated flag is out of sync with mARIAOwnsHash");
2503 owned
->RemoveElement(aChild
);
2504 if (owned
->Length() == 0) {
2505 mARIAOwnsHash
.Remove(curParent
);
2509 if (curParent
== aNewParent
) {
2510 MOZ_ASSERT(aChild
->IndexInParent() != aIdxInParent
, "No move case");
2511 curParent
->RelocateChild(aIdxInParent
, aChild
);
2513 TrackMovedAccessible(aChild
);
2517 logging::TreeInfo("move child: parent tree after", logging::eVerbose
,
2523 // If the child cannot be re-inserted into the tree, then make sure to remove
2524 // it from its present parent and then shutdown it.
2525 bool hasInsertionPoint
=
2526 (aIdxInParent
>= 0) &&
2527 (aIdxInParent
<= static_cast<int32_t>(aNewParent
->mChildren
.Length()));
2529 TreeMutation
rmut(curParent
);
2530 rmut
.BeforeRemoval(aChild
, hasInsertionPoint
&& TreeMutation::kNoShutdown
);
2531 curParent
->RemoveChild(aChild
);
2534 // No insertion point for the child.
2535 if (!hasInsertionPoint
) {
2539 TreeMutation
imut(aNewParent
);
2540 aNewParent
->InsertChildAt(aIdxInParent
, aChild
);
2542 TrackMovedAccessible(aChild
);
2544 imut
.AfterInsertion(aChild
);
2548 logging::TreeInfo("move child: old parent tree after", logging::eVerbose
,
2550 logging::TreeInfo("move child: new parent tree after", logging::eVerbose
,
2557 void DocAccessible::CacheChildrenInSubtree(LocalAccessible
* aRoot
,
2558 LocalAccessible
** aFocusedAcc
) {
2559 // If the accessible is focused then report a focus event after all related
2561 if (aFocusedAcc
&& !*aFocusedAcc
&&
2562 FocusMgr()->HasDOMFocus(aRoot
->GetContent())) {
2563 *aFocusedAcc
= aRoot
;
2566 LocalAccessible
* root
=
2567 aRoot
->IsHTMLCombobox() ? aRoot
->LocalFirstChild() : aRoot
;
2568 if (root
->KidsFromDOM()) {
2569 TreeMutation
mt(root
, TreeMutation::kNoEvents
);
2570 TreeWalker
walker(root
);
2571 while (LocalAccessible
* child
= walker
.Next()) {
2572 if (child
->IsBoundToParent()) {
2573 MoveChild(child
, root
, root
->mChildren
.Length());
2577 root
->AppendChild(child
);
2578 mt
.AfterInsertion(child
);
2580 CacheChildrenInSubtree(child
, aFocusedAcc
);
2585 // Fire events for ARIA elements.
2586 if (!aRoot
->HasARIARole()) {
2590 // XXX: we should delay document load complete event if the ARIA document
2592 roles::Role role
= aRoot
->ARIARole();
2593 if (!aRoot
->IsDoc() &&
2594 (role
== roles::DIALOG
|| role
== roles::NON_NATIVE_DOCUMENT
)) {
2595 FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
, aRoot
);
2599 void DocAccessible::UncacheChildrenInSubtree(LocalAccessible
* aRoot
) {
2600 aRoot
->mStateFlags
|= eIsNotInDocument
;
2601 RemoveDependentIDsFor(aRoot
);
2603 // The parent of the removed subtree is about to be cleared, so we must do
2604 // this here rather than in LocalAccessible::UnbindFromParent because we need
2605 // the ancestry for this to work.
2606 if (aRoot
->IsTable() || aRoot
->IsTableCell()) {
2607 CachedTableAccessible::Invalidate(aRoot
);
2610 // Put relocated children back in their original places instead of removing
2611 // them from the tree.
2612 nsTArray
<RefPtr
<LocalAccessible
>>* owned
= mARIAOwnsHash
.Get(aRoot
);
2614 PutChildrenBack(owned
, 0);
2615 MOZ_ASSERT(owned
->IsEmpty(),
2616 "Owned Accessibles should be cleared after PutChildrenBack.");
2617 mARIAOwnsHash
.Remove(aRoot
);
2621 const uint32_t count
= aRoot
->ContentChildCount();
2622 for (uint32_t idx
= 0; idx
< count
; ++idx
) {
2623 LocalAccessible
* child
= aRoot
->ContentChildAt(idx
);
2625 MOZ_ASSERT(!child
->IsRelocated(),
2626 "No children should be relocated here. They should all have "
2627 "been relocated by PutChildrenBack.");
2629 // Removing this accessible from the document doesn't mean anything about
2630 // accessibles for subdocuments, so skip removing those from the tree.
2631 if (!child
->IsDoc()) {
2632 UncacheChildrenInSubtree(child
);
2636 if (aRoot
->IsNodeMapEntry() &&
2637 mNodeToAccessibleMap
.Get(aRoot
->GetNode()) == aRoot
) {
2638 mNodeToAccessibleMap
.Remove(aRoot
->GetNode());
2642 void DocAccessible::ShutdownChildrenInSubtree(LocalAccessible
* aAccessible
) {
2643 MOZ_ASSERT(!nsAccessibilityService::IsShutdown());
2644 // Traverse through children and shutdown them before this accessible. When
2645 // child gets shutdown then it removes itself from children array of its
2646 // parent. Use jdx index to process the cases if child is not attached to the
2647 // parent and as result doesn't remove itself from its children.
2648 uint32_t count
= aAccessible
->ContentChildCount();
2649 for (uint32_t idx
= 0, jdx
= 0; idx
< count
; idx
++) {
2650 LocalAccessible
* child
= aAccessible
->ContentChildAt(jdx
);
2651 if (!child
->IsBoundToParent()) {
2652 NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
2656 // Don't cross document boundaries. The outerdoc shutdown takes care about
2658 if (!child
->IsDoc()) {
2659 ShutdownChildrenInSubtree(child
);
2660 if (nsAccessibilityService::IsShutdown()) {
2661 // If XPCOM is the only consumer (devtools & mochitests), shutting down
2662 // the child's subtree can cause a11y to shut down because the last
2663 // xpcom accessibles will be removed. In that case, return early, our
2670 UnbindFromDocument(aAccessible
);
2673 bool DocAccessible::IsLoadEventTarget() const {
2674 nsCOMPtr
<nsIDocShellTreeItem
> treeItem
= mDocumentNode
->GetDocShell();
2679 nsCOMPtr
<nsIDocShellTreeItem
> parentTreeItem
;
2680 treeItem
->GetInProcessParent(getter_AddRefs(parentTreeItem
));
2682 // Not a root document.
2683 if (parentTreeItem
) {
2684 // Return true if it's either:
2686 nsCOMPtr
<nsIDocShellTreeItem
> rootTreeItem
;
2687 treeItem
->GetInProcessRootTreeItem(getter_AddRefs(rootTreeItem
));
2688 if (parentTreeItem
== rootTreeItem
) return true;
2690 // b) frame/iframe document and its parent document is not in loading state
2691 // Note: we can get notifications while document is loading (and thus
2692 // while there's no parent document yet).
2693 DocAccessible
* parentDoc
= ParentDocument();
2694 return parentDoc
&& parentDoc
->HasLoadState(eCompletelyLoaded
);
2697 // It's content (not chrome) root document.
2698 return (treeItem
->ItemType() == nsIDocShellTreeItem::typeContent
);
2701 void DocAccessible::SetIPCDoc(DocAccessibleChild
* aIPCDoc
) {
2702 MOZ_ASSERT(!mIPCDoc
|| !aIPCDoc
, "Clobbering an attached IPCDoc!");
2706 void DocAccessible::DispatchScrollingEvent(nsINode
* aTarget
,
2707 uint32_t aEventType
) {
2708 LocalAccessible
* acc
= GetAccessible(aTarget
);
2713 nsIFrame
* frame
= acc
->GetFrame();
2715 // Although the accessible had a frame at scroll time, it may now be gone
2716 // because of display: contents.
2720 auto [scrollPoint
, scrollRange
] = ComputeScrollData(acc
);
2722 int32_t appUnitsPerDevPixel
=
2723 mPresShell
->GetPresContext()->AppUnitsPerDevPixel();
2725 LayoutDeviceIntPoint scrollPointDP
= LayoutDevicePoint::FromAppUnitsToNearest(
2726 scrollPoint
, appUnitsPerDevPixel
);
2727 LayoutDeviceIntRect scrollRangeDP
=
2728 LayoutDeviceRect::FromAppUnitsToNearest(scrollRange
, appUnitsPerDevPixel
);
2730 RefPtr
<AccEvent
> event
=
2731 new AccScrollingEvent(aEventType
, acc
, scrollPointDP
.x
, scrollPointDP
.y
,
2732 scrollRangeDP
.width
, scrollRangeDP
.height
);
2733 nsEventShell::FireEvent(event
);
2736 void DocAccessible::ARIAActiveDescendantIDMaybeMoved(
2737 LocalAccessible
* aAccessible
) {
2738 LocalAccessible
* widget
= nullptr;
2739 if (aAccessible
->IsActiveDescendant(&widget
) && widget
) {
2740 // The active descendant might have just been inserted and may not be in the
2741 // tree yet. Therefore, schedule this async to ensure the tree is up to
2743 mNotificationController
2744 ->ScheduleNotification
<DocAccessible
, LocalAccessible
>(
2745 this, &DocAccessible::ARIAActiveDescendantChanged
, widget
);
2749 void DocAccessible::SetRoleMapEntryForDoc(dom::Element
* aElement
) {
2750 const nsRoleMapEntry
* entry
= aria::GetRoleMap(aElement
);
2751 if (!entry
|| entry
->role
== roles::APPLICATION
||
2752 entry
->role
== roles::DIALOG
||
2753 // Role alert isn't valid on the body element according to the ARIA spec,
2754 // but it's useful for our UI; e.g. the WebRTC sharing indicator.
2755 (entry
->role
== roles::ALERT
&& !mDocumentNode
->IsContentDocument())) {
2756 SetRoleMapEntry(entry
);
2759 // No other ARIA roles are valid on body elements.
2760 SetRoleMapEntry(nullptr);
2763 LocalAccessible
* DocAccessible::GetAccessible(nsINode
* aNode
) const {
2764 return aNode
== mDocumentNode
? const_cast<DocAccessible
*>(this)
2765 : mNodeToAccessibleMap
.Get(aNode
);
2768 bool DocAccessible::HasPrimaryAction() const {
2769 if (HyperTextAccessible::HasPrimaryAction()) {
2772 // mContent is normally the body, but there might be a click listener on the
2774 dom::Element
* root
= mDocumentNode
->GetRootElement();
2775 if (mContent
!= root
) {
2776 return nsCoreUtils::HasClickListener(root
);
2781 void DocAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
2786 if (HasPrimaryAction()) {
2787 aName
.AssignLiteral("click");
2791 void DocAccessible::MaybeHandleChangeToHiddenNameOrDescription(
2792 nsIContent
* aChild
) {
2793 if (!HasLoadState(eTreeConstructed
)) {
2796 for (nsIContent
* content
= aChild
; content
; content
= content
->GetParent()) {
2797 if (HasAccessible(content
)) {
2798 // This node isn't hidden. Events for name/description dependents will be
2802 nsAtom
* id
= content
->GetID();
2807 GetRelProviders(content
->AsElement(), nsDependentAtomString(id
));
2811 for (auto& provider
: *providers
) {
2812 if (provider
->mRelAttr
!= nsGkAtoms::aria_labelledby
&&
2813 provider
->mRelAttr
!= nsGkAtoms::aria_describedby
) {
2816 LocalAccessible
* dependentAcc
= GetAccessible(provider
->mContent
);
2817 if (!dependentAcc
) {
2820 FireDelayedEvent(provider
->mRelAttr
== nsGkAtoms::aria_labelledby
2821 ? nsIAccessibleEvent::EVENT_NAME_CHANGE
2822 : nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE
,