accessible/src/generic/DocAccessible.cpp

branch
TOR_BUG_9701
changeset 3
141e0f1194b1
equal deleted inserted replaced
-1:000000000000 0:095e016a7bbc
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "Accessible-inl.h"
7 #include "AccIterator.h"
8 #include "DocAccessible-inl.h"
9 #include "HTMLImageMapAccessible.h"
10 #include "nsAccCache.h"
11 #include "nsAccessiblePivot.h"
12 #include "nsAccUtils.h"
13 #include "nsEventShell.h"
14 #include "nsTextEquivUtils.h"
15 #include "Role.h"
16 #include "RootAccessible.h"
17 #include "TreeWalker.h"
18
19 #include "nsIMutableArray.h"
20 #include "nsICommandManager.h"
21 #include "nsIDocShell.h"
22 #include "nsIDocument.h"
23 #include "nsIDOMAttr.h"
24 #include "nsIDOMCharacterData.h"
25 #include "nsIDOMDocument.h"
26 #include "nsIDOMXULDocument.h"
27 #include "nsIDOMMutationEvent.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsIDOMXULPopupElement.h"
30 #include "nsIEditingSession.h"
31 #include "nsIFrame.h"
32 #include "nsIInterfaceRequestorUtils.h"
33 #include "nsImageFrame.h"
34 #include "nsIPersistentProperties2.h"
35 #include "nsIPresShell.h"
36 #include "nsIServiceManager.h"
37 #include "nsViewManager.h"
38 #include "nsIScrollableFrame.h"
39 #include "nsUnicharUtils.h"
40 #include "nsIURI.h"
41 #include "nsIWebNavigation.h"
42 #include "nsFocusManager.h"
43 #include "nsNameSpaceManager.h"
44 #include "mozilla/ArrayUtils.h"
45 #include "mozilla/Assertions.h"
46 #include "mozilla/EventStates.h"
47 #include "mozilla/dom/DocumentType.h"
48 #include "mozilla/dom/Element.h"
49
50 #ifdef MOZ_XUL
51 #include "nsIXULDocument.h"
52 #endif
53
54 using namespace mozilla;
55 using namespace mozilla::a11y;
56
57 ////////////////////////////////////////////////////////////////////////////////
58 // Static member initialization
59
60 static nsIAtom** kRelationAttrs[] =
61 {
62 &nsGkAtoms::aria_labelledby,
63 &nsGkAtoms::aria_describedby,
64 &nsGkAtoms::aria_owns,
65 &nsGkAtoms::aria_controls,
66 &nsGkAtoms::aria_flowto,
67 &nsGkAtoms::_for,
68 &nsGkAtoms::control
69 };
70
71 static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
72
73 ////////////////////////////////////////////////////////////////////////////////
74 // Constructor/desctructor
75
76 DocAccessible::
77 DocAccessible(nsIDocument* aDocument, nsIContent* aRootContent,
78 nsIPresShell* aPresShell) :
79 HyperTextAccessibleWrap(aRootContent, this),
80 // XXX aaronl should we use an algorithm for the initial cache size?
81 mAccessibleCache(kDefaultCacheSize),
82 mNodeToAccessibleMap(kDefaultCacheSize),
83 mDocumentNode(aDocument),
84 mScrollPositionChangedTicks(0),
85 mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
86 mVirtualCursor(nullptr),
87 mPresShell(aPresShell)
88 {
89 mGenericTypes |= eDocument;
90 mStateFlags |= eNotNodeMapEntry;
91
92 MOZ_ASSERT(mPresShell, "should have been given a pres shell");
93 mPresShell->SetDocAccessible(this);
94
95 // If this is a XUL Document, it should not implement nsHyperText
96 if (mDocumentNode && mDocumentNode->IsXUL())
97 mGenericTypes &= ~eHyperText;
98 }
99
100 DocAccessible::~DocAccessible()
101 {
102 NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
103 }
104
105
106 ////////////////////////////////////////////////////////////////////////////////
107 // nsISupports
108
109 NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
110
111 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
113 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
115 tmp->mDependentIDsHash.EnumerateRead(CycleCollectorTraverseDepIDsEntry, &cb);
116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
118 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
119
120 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
121 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
122 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
123 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
124 tmp->mDependentIDsHash.Clear();
125 tmp->mNodeToAccessibleMap.Clear();
126 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
127 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
128 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
129
130 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
131 NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
132 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
133 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
134 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
135 NS_INTERFACE_MAP_ENTRY(nsIObserver)
136 NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
137 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument)
138 foundInterface = 0;
139
140 nsresult status;
141 if (!foundInterface) {
142 // HTML document accessible must inherit from HyperTextAccessible to get
143 // support text interfaces. XUL document accessible doesn't need this.
144 // However at some point we may push <body> to implement the interfaces and
145 // return DocAccessible to inherit from AccessibleWrap.
146
147 status = IsHyperText() ?
148 HyperTextAccessible::QueryInterface(aIID, (void**)&foundInterface) :
149 Accessible::QueryInterface(aIID, (void**)&foundInterface);
150 } else {
151 NS_ADDREF(foundInterface);
152 status = NS_OK;
153 }
154
155 *aInstancePtr = foundInterface;
156 return status;
157 }
158
159 NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
160 NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
161
162 ////////////////////////////////////////////////////////////////////////////////
163 // nsIAccessible
164
165 ENameValueFlag
166 DocAccessible::Name(nsString& aName)
167 {
168 aName.Truncate();
169
170 if (mParent) {
171 mParent->Name(aName); // Allow owning iframe to override the name
172 }
173 if (aName.IsEmpty()) {
174 // Allow name via aria-labelledby or title attribute
175 Accessible::Name(aName);
176 }
177 if (aName.IsEmpty()) {
178 GetTitle(aName); // Try title element
179 }
180 if (aName.IsEmpty()) { // Last resort: use URL
181 GetURL(aName);
182 }
183
184 return eNameOK;
185 }
186
187 // Accessible public method
188 role
189 DocAccessible::NativeRole()
190 {
191 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
192 if (docShell) {
193 nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
194 docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
195 int32_t itemType = docShell->ItemType();
196 if (sameTypeRoot == docShell) {
197 // Root of content or chrome tree
198 if (itemType == nsIDocShellTreeItem::typeChrome)
199 return roles::CHROME_WINDOW;
200
201 if (itemType == nsIDocShellTreeItem::typeContent) {
202 #ifdef MOZ_XUL
203 nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
204 if (xulDoc)
205 return roles::APPLICATION;
206 #endif
207 return roles::DOCUMENT;
208 }
209 }
210 else if (itemType == nsIDocShellTreeItem::typeContent) {
211 return roles::DOCUMENT;
212 }
213 }
214
215 return roles::PANE; // Fall back;
216 }
217
218 void
219 DocAccessible::Description(nsString& aDescription)
220 {
221 if (mParent)
222 mParent->Description(aDescription);
223
224 if (HasOwnContent() && aDescription.IsEmpty()) {
225 nsTextEquivUtils::
226 GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
227 aDescription);
228 }
229 }
230
231 // Accessible public method
232 uint64_t
233 DocAccessible::NativeState()
234 {
235 // Document is always focusable.
236 uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
237 if (FocusMgr()->IsFocused(this))
238 state |= states::FOCUSED;
239
240 // Expose stale state until the document is ready (DOM is loaded and tree is
241 // constructed).
242 if (!HasLoadState(eReady))
243 state |= states::STALE;
244
245 // Expose state busy until the document and all its subdocuments is completely
246 // loaded.
247 if (!HasLoadState(eCompletelyLoaded))
248 state |= states::BUSY;
249
250 nsIFrame* frame = GetFrame();
251 if (!frame ||
252 !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
253 state |= states::INVISIBLE | states::OFFSCREEN;
254 }
255
256 nsCOMPtr<nsIEditor> editor = GetEditor();
257 state |= editor ? states::EDITABLE : states::READONLY;
258
259 return state;
260 }
261
262 uint64_t
263 DocAccessible::NativeInteractiveState() const
264 {
265 // Document is always focusable.
266 return states::FOCUSABLE;
267 }
268
269 bool
270 DocAccessible::NativelyUnavailable() const
271 {
272 return false;
273 }
274
275 // Accessible public method
276 void
277 DocAccessible::ApplyARIAState(uint64_t* aState) const
278 {
279 // Grab states from content element.
280 if (mContent)
281 Accessible::ApplyARIAState(aState);
282
283 // Allow iframe/frame etc. to have final state override via ARIA.
284 if (mParent)
285 mParent->ApplyARIAState(aState);
286 }
287
288 already_AddRefed<nsIPersistentProperties>
289 DocAccessible::Attributes()
290 {
291 nsCOMPtr<nsIPersistentProperties> attributes =
292 HyperTextAccessibleWrap::Attributes();
293
294 // No attributes if document is not attached to the tree or if it's a root
295 // document.
296 if (!mParent || IsRoot())
297 return attributes.forget();
298
299 // Override ARIA object attributes from outerdoc.
300 aria::AttrIterator attribIter(mParent->GetContent());
301 nsAutoString name, value, unused;
302 while(attribIter.Next(name, value))
303 attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
304
305 return attributes.forget();
306 }
307
308 Accessible*
309 DocAccessible::FocusedChild()
310 {
311 // Return an accessible for the current global focus, which does not have to
312 // be contained within the current document.
313 return FocusMgr()->FocusedAccessible();
314 }
315
316 NS_IMETHODIMP
317 DocAccessible::TakeFocus()
318 {
319 if (IsDefunct())
320 return NS_ERROR_FAILURE;
321
322 // Focus the document.
323 nsFocusManager* fm = nsFocusManager::GetFocusManager();
324 NS_ENSURE_STATE(fm);
325
326 nsCOMPtr<nsIDOMElement> newFocus;
327 return fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
328 nsIFocusManager::MOVEFOCUS_ROOT, 0,
329 getter_AddRefs(newFocus));
330 }
331
332
333 ////////////////////////////////////////////////////////////////////////////////
334 // nsIAccessibleDocument
335
336 NS_IMETHODIMP
337 DocAccessible::GetURL(nsAString& aURL)
338 {
339 if (IsDefunct())
340 return NS_ERROR_FAILURE;
341
342 nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
343 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
344 nsAutoCString theURL;
345 if (webNav) {
346 nsCOMPtr<nsIURI> pURI;
347 webNav->GetCurrentURI(getter_AddRefs(pURI));
348 if (pURI)
349 pURI->GetSpec(theURL);
350 }
351 CopyUTF8toUTF16(theURL, aURL);
352 return NS_OK;
353 }
354
355 NS_IMETHODIMP
356 DocAccessible::GetTitle(nsAString& aTitle)
357 {
358 if (!mDocumentNode) {
359 return NS_ERROR_FAILURE;
360 }
361 nsString title;
362 mDocumentNode->GetTitle(title);
363 aTitle = title;
364 return NS_OK;
365 }
366
367 NS_IMETHODIMP
368 DocAccessible::GetMimeType(nsAString& aMimeType)
369 {
370 if (!mDocumentNode) {
371 return NS_ERROR_FAILURE;
372 }
373 return mDocumentNode->GetContentType(aMimeType);
374 }
375
376 NS_IMETHODIMP
377 DocAccessible::GetDocType(nsAString& aDocType)
378 {
379 #ifdef MOZ_XUL
380 nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
381 if (xulDoc) {
382 aDocType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
383 return NS_OK;
384 } else
385 #endif
386 if (mDocumentNode) {
387 dom::DocumentType* docType = mDocumentNode->GetDoctype();
388 if (docType) {
389 return docType->GetPublicId(aDocType);
390 }
391 }
392
393 return NS_ERROR_FAILURE;
394 }
395
396 NS_IMETHODIMP
397 DocAccessible::GetNameSpaceURIForID(int16_t aNameSpaceID, nsAString& aNameSpaceURI)
398 {
399 if (mDocumentNode) {
400 nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance();
401 if (nameSpaceManager)
402 return nameSpaceManager->GetNameSpaceURI(aNameSpaceID, aNameSpaceURI);
403 }
404 return NS_ERROR_FAILURE;
405 }
406
407 NS_IMETHODIMP
408 DocAccessible::GetWindowHandle(void** aWindow)
409 {
410 NS_ENSURE_ARG_POINTER(aWindow);
411 *aWindow = GetNativeWindow();
412 return NS_OK;
413 }
414
415 NS_IMETHODIMP
416 DocAccessible::GetWindow(nsIDOMWindow** aDOMWin)
417 {
418 *aDOMWin = nullptr;
419 if (!mDocumentNode) {
420 return NS_ERROR_FAILURE; // Accessible is Shutdown()
421 }
422 *aDOMWin = mDocumentNode->GetWindow();
423
424 if (!*aDOMWin)
425 return NS_ERROR_FAILURE; // No DOM Window
426
427 NS_ADDREF(*aDOMWin);
428
429 return NS_OK;
430 }
431
432 NS_IMETHODIMP
433 DocAccessible::GetDOMDocument(nsIDOMDocument** aDOMDocument)
434 {
435 NS_ENSURE_ARG_POINTER(aDOMDocument);
436 *aDOMDocument = nullptr;
437
438 if (mDocumentNode)
439 CallQueryInterface(mDocumentNode, aDOMDocument);
440
441 return NS_OK;
442 }
443
444 NS_IMETHODIMP
445 DocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument)
446 {
447 NS_ENSURE_ARG_POINTER(aDocument);
448 *aDocument = nullptr;
449
450 if (!IsDefunct())
451 NS_IF_ADDREF(*aDocument = ParentDocument());
452
453 return NS_OK;
454 }
455
456 NS_IMETHODIMP
457 DocAccessible::GetChildDocumentCount(uint32_t* aCount)
458 {
459 NS_ENSURE_ARG_POINTER(aCount);
460 *aCount = 0;
461
462 if (!IsDefunct())
463 *aCount = ChildDocumentCount();
464
465 return NS_OK;
466 }
467
468 NS_IMETHODIMP
469 DocAccessible::GetChildDocumentAt(uint32_t aIndex,
470 nsIAccessibleDocument** aDocument)
471 {
472 NS_ENSURE_ARG_POINTER(aDocument);
473 *aDocument = nullptr;
474
475 if (IsDefunct())
476 return NS_OK;
477
478 NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex));
479 return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
480 }
481
482 NS_IMETHODIMP
483 DocAccessible::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
484 {
485 NS_ENSURE_ARG_POINTER(aVirtualCursor);
486 *aVirtualCursor = nullptr;
487
488 if (IsDefunct())
489 return NS_ERROR_FAILURE;
490
491 if (!mVirtualCursor) {
492 mVirtualCursor = new nsAccessiblePivot(this);
493 mVirtualCursor->AddObserver(this);
494 }
495
496 NS_ADDREF(*aVirtualCursor = mVirtualCursor);
497 return NS_OK;
498 }
499
500 // HyperTextAccessible method
501 already_AddRefed<nsIEditor>
502 DocAccessible::GetEditor() const
503 {
504 // Check if document is editable (designMode="on" case). Otherwise check if
505 // the html:body (for HTML document case) or document element is editable.
506 if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
507 (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
508 return nullptr;
509
510 nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
511 nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container));
512 if (!editingSession)
513 return nullptr; // No editing session interface
514
515 nsCOMPtr<nsIEditor> editor;
516 editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
517 if (!editor)
518 return nullptr;
519
520 bool isEditable = false;
521 editor->GetIsDocumentEditable(&isEditable);
522 if (isEditable)
523 return editor.forget();
524
525 return nullptr;
526 }
527
528 // DocAccessible public method
529 Accessible*
530 DocAccessible::GetAccessible(nsINode* aNode) const
531 {
532 Accessible* accessible = mNodeToAccessibleMap.Get(aNode);
533
534 // No accessible in the cache, check if the given ID is unique ID of this
535 // document accessible.
536 if (!accessible) {
537 if (GetNode() != aNode)
538 return nullptr;
539
540 accessible = const_cast<DocAccessible*>(this);
541 }
542
543 #ifdef DEBUG
544 // All cached accessible nodes should be in the parent
545 // It will assert if not all the children were created
546 // when they were first cached, and no invalidation
547 // ever corrected parent accessible's child cache.
548 Accessible* parent = accessible->Parent();
549 if (parent)
550 parent->TestChildCache(accessible);
551 #endif
552
553 return accessible;
554 }
555
556 ////////////////////////////////////////////////////////////////////////////////
557 // Accessible
558
559 void
560 DocAccessible::Init()
561 {
562 #ifdef A11Y_LOG
563 if (logging::IsEnabled(logging::eDocCreate))
564 logging::DocCreate("document initialize", mDocumentNode, this);
565 #endif
566
567 // Initialize notification controller.
568 mNotificationController = new NotificationController(this, mPresShell);
569
570 // Mark the document accessible as loaded if its DOM document was loaded at
571 // this point (this can happen because a11y is started late or DOM document
572 // having no container was loaded.
573 if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
574 mLoadState |= eDOMLoaded;
575
576 AddEventListeners();
577 }
578
579 void
580 DocAccessible::Shutdown()
581 {
582 if (!mPresShell) // already shutdown
583 return;
584
585 #ifdef A11Y_LOG
586 if (logging::IsEnabled(logging::eDocDestroy))
587 logging::DocDestroy("document shutdown", mDocumentNode, this);
588 #endif
589
590 if (mNotificationController) {
591 mNotificationController->Shutdown();
592 mNotificationController = nullptr;
593 }
594
595 RemoveEventListeners();
596
597 // Mark the document as shutdown before AT is notified about the document
598 // removal from its container (valid for root documents on ATK and due to
599 // some reason for MSAA, refer to bug 757392 for details).
600 mStateFlags |= eIsDefunct;
601 nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
602 mDocumentNode = nullptr;
603
604 if (mParent) {
605 DocAccessible* parentDocument = mParent->Document();
606 if (parentDocument)
607 parentDocument->RemoveChildDocument(this);
608
609 mParent->RemoveChild(this);
610 }
611
612 // Walk the array backwards because child documents remove themselves from the
613 // array as they are shutdown.
614 int32_t childDocCount = mChildDocuments.Length();
615 for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
616 mChildDocuments[idx]->Shutdown();
617
618 mChildDocuments.Clear();
619
620 if (mVirtualCursor) {
621 mVirtualCursor->RemoveObserver(this);
622 mVirtualCursor = nullptr;
623 }
624
625 mPresShell->SetDocAccessible(nullptr);
626 mPresShell = nullptr; // Avoid reentrancy
627
628 mDependentIDsHash.Clear();
629 mNodeToAccessibleMap.Clear();
630 ClearCache(mAccessibleCache);
631
632 HyperTextAccessibleWrap::Shutdown();
633
634 GetAccService()->NotifyOfDocumentShutdown(kungFuDeathGripDoc);
635 }
636
637 nsIFrame*
638 DocAccessible::GetFrame() const
639 {
640 nsIFrame* root = nullptr;
641 if (mPresShell)
642 root = mPresShell->GetRootFrame();
643
644 return root;
645 }
646
647 // DocAccessible protected member
648 void
649 DocAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aRelativeFrame)
650 {
651 *aRelativeFrame = GetFrame();
652
653 nsIDocument *document = mDocumentNode;
654 nsIDocument *parentDoc = nullptr;
655
656 while (document) {
657 nsIPresShell *presShell = document->GetShell();
658 if (!presShell) {
659 return;
660 }
661
662 nsRect scrollPort;
663 nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
664 if (sf) {
665 scrollPort = sf->GetScrollPortRect();
666 } else {
667 nsIFrame* rootFrame = presShell->GetRootFrame();
668 if (!rootFrame) {
669 return;
670 }
671 scrollPort = rootFrame->GetRect();
672 }
673
674 if (parentDoc) { // After first time thru loop
675 // XXXroc bogus code! scrollPort is relative to the viewport of
676 // this document, but we're intersecting rectangles derived from
677 // multiple documents and assuming they're all in the same coordinate
678 // system. See bug 514117.
679 aBounds.IntersectRect(scrollPort, aBounds);
680 }
681 else { // First time through loop
682 aBounds = scrollPort;
683 }
684
685 document = parentDoc = document->GetParentDocument();
686 }
687 }
688
689 // DocAccessible protected member
690 nsresult
691 DocAccessible::AddEventListeners()
692 {
693 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell());
694
695 // We want to add a command observer only if the document is content and has
696 // an editor.
697 if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) {
698 nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
699 if (commandManager)
700 commandManager->AddCommandObserver(this, "obs_documentCreated");
701 }
702
703 SelectionMgr()->AddDocSelectionListener(mPresShell);
704
705 // Add document observer.
706 mDocumentNode->AddObserver(this);
707 return NS_OK;
708 }
709
710 // DocAccessible protected member
711 nsresult
712 DocAccessible::RemoveEventListeners()
713 {
714 // Remove listeners associated with content documents
715 // Remove scroll position listener
716 RemoveScrollListener();
717
718 NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
719
720 if (mDocumentNode) {
721 mDocumentNode->RemoveObserver(this);
722
723 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(mDocumentNode->GetDocShell());
724 NS_ASSERTION(docShellTreeItem, "doc should support nsIDocShellTreeItem.");
725
726 if (docShellTreeItem) {
727 if (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent) {
728 nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
729 if (commandManager) {
730 commandManager->RemoveCommandObserver(this, "obs_documentCreated");
731 }
732 }
733 }
734 }
735
736 if (mScrollWatchTimer) {
737 mScrollWatchTimer->Cancel();
738 mScrollWatchTimer = nullptr;
739 NS_RELEASE_THIS(); // Kung fu death grip
740 }
741
742 SelectionMgr()->RemoveDocSelectionListener(mPresShell);
743 return NS_OK;
744 }
745
746 void
747 DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
748 {
749 DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
750
751 if (docAcc && docAcc->mScrollPositionChangedTicks &&
752 ++docAcc->mScrollPositionChangedTicks > 2) {
753 // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
754 // We only want to fire accessibilty scroll event when scrolling stops or pauses
755 // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
756 // That indicates a pause in scrolling, so we fire the accessibilty scroll event
757 nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
758
759 docAcc->mScrollPositionChangedTicks = 0;
760 if (docAcc->mScrollWatchTimer) {
761 docAcc->mScrollWatchTimer->Cancel();
762 docAcc->mScrollWatchTimer = nullptr;
763 NS_RELEASE(docAcc); // Release kung fu death grip
764 }
765 }
766 }
767
768 ////////////////////////////////////////////////////////////////////////////////
769 // nsIScrollPositionListener
770
771 void
772 DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
773 {
774 // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
775 // then the ::Notify() method will fire the accessibility event for scroll position changes
776 const uint32_t kScrollPosCheckWait = 50;
777 if (mScrollWatchTimer) {
778 mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
779 }
780 else {
781 mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
782 if (mScrollWatchTimer) {
783 NS_ADDREF_THIS(); // Kung fu death grip
784 mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
785 kScrollPosCheckWait,
786 nsITimer::TYPE_REPEATING_SLACK);
787 }
788 }
789 mScrollPositionChangedTicks = 1;
790 }
791
792 ////////////////////////////////////////////////////////////////////////////////
793 // nsIObserver
794
795 NS_IMETHODIMP
796 DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
797 const char16_t* aData)
798 {
799 if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
800 // State editable will now be set, readonly is now clear
801 // Normally we only fire delayed events created from the node, not an
802 // accessible object. See the AccStateChangeEvent constructor for details
803 // about this exceptional case.
804 nsRefPtr<AccEvent> event =
805 new AccStateChangeEvent(this, states::EDITABLE, true);
806 FireDelayedEvent(event);
807 }
808
809 return NS_OK;
810 }
811
812 ////////////////////////////////////////////////////////////////////////////////
813 // nsIAccessiblePivotObserver
814
815 NS_IMETHODIMP
816 DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
817 nsIAccessible* aOldAccessible,
818 int32_t aOldStart, int32_t aOldEnd,
819 PivotMoveReason aReason)
820 {
821 nsRefPtr<AccEvent> event = new AccVCChangeEvent(this, aOldAccessible,
822 aOldStart, aOldEnd,
823 aReason);
824 nsEventShell::FireEvent(event);
825
826 return NS_OK;
827 }
828
829 ////////////////////////////////////////////////////////////////////////////////
830 // nsIDocumentObserver
831
832 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
833 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
834 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
835
836 void
837 DocAccessible::AttributeWillChange(nsIDocument* aDocument,
838 dom::Element* aElement,
839 int32_t aNameSpaceID,
840 nsIAtom* aAttribute, int32_t aModType)
841 {
842 Accessible* accessible = GetAccessible(aElement);
843 if (!accessible) {
844 if (aElement != mContent)
845 return;
846
847 accessible = this;
848 }
849
850 // Update dependent IDs cache. Take care of elements that are accessible
851 // because dependent IDs cache doesn't contain IDs from non accessible
852 // elements.
853 if (aModType != nsIDOMMutationEvent::ADDITION)
854 RemoveDependentIDsFor(aElement, aAttribute);
855
856 // Store the ARIA attribute old value so that it can be used after
857 // attribute change. Note, we assume there's no nested ARIA attribute
858 // changes. If this happens then we should end up with keeping a stack of
859 // old values.
860
861 // XXX TODO: bugs 472142, 472143.
862 // Here we will want to cache whatever attribute values we are interested
863 // in, such as the existence of aria-pressed for button (so we know if we
864 // need to newly expose it as a toggle button) etc.
865 if (aAttribute == nsGkAtoms::aria_checked ||
866 aAttribute == nsGkAtoms::aria_pressed) {
867 mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
868 nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
869 return;
870 }
871
872 if (aAttribute == nsGkAtoms::aria_disabled ||
873 aAttribute == nsGkAtoms::disabled)
874 mStateBitWasOn = accessible->Unavailable();
875 }
876
877 void
878 DocAccessible::AttributeChanged(nsIDocument* aDocument,
879 dom::Element* aElement,
880 int32_t aNameSpaceID, nsIAtom* aAttribute,
881 int32_t aModType)
882 {
883 NS_ASSERTION(!IsDefunct(),
884 "Attribute changed called on defunct document accessible!");
885
886 // Proceed even if the element is not accessible because element may become
887 // accessible if it gets certain attribute.
888 if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
889 return;
890
891 // Ignore attribute change if the element doesn't have an accessible (at all
892 // or still) iff the element is not a root content of this document accessible
893 // (which is treated as attribute change on this document accessible).
894 // Note: we don't bail if all the content hasn't finished loading because
895 // these attributes are changing for a loaded part of the content.
896 Accessible* accessible = GetAccessible(aElement);
897 if (!accessible) {
898 if (mContent != aElement)
899 return;
900
901 accessible = this;
902 }
903
904 // Fire accessible events iff there's an accessible, otherwise we consider
905 // the accessible state wasn't changed, i.e. its state is initial state.
906 AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
907
908 // Update dependent IDs cache. Take care of accessible elements because no
909 // accessible element means either the element is not accessible at all or
910 // its accessible will be created later. It doesn't make sense to keep
911 // dependent IDs for non accessible elements. For the second case we'll update
912 // dependent IDs cache when its accessible is created.
913 if (aModType == nsIDOMMutationEvent::MODIFICATION ||
914 aModType == nsIDOMMutationEvent::ADDITION) {
915 AddDependentIDsFor(aElement, aAttribute);
916 }
917 }
918
919 // DocAccessible protected member
920 void
921 DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
922 int32_t aNameSpaceID, nsIAtom* aAttribute)
923 {
924 // Fire accessible event after short timer, because we need to wait for
925 // DOM attribute & resulting layout to actually change. Otherwise,
926 // assistive technology will retrieve the wrong state/value/selection info.
927
928 // XXX todo
929 // We still need to handle special HTML cases here
930 // For example, if an <img>'s usemap attribute is modified
931 // Otherwise it may just be a state change, for example an object changing
932 // its visibility
933 //
934 // XXX todo: report aria state changes for "undefined" literal value changes
935 // filed as bug 472142
936 //
937 // XXX todo: invalidate accessible when aria state changes affect exposed role
938 // filed as bug 472143
939
940 // Universal boolean properties that don't require a role. Fire the state
941 // change when disabled or aria-disabled attribute is set.
942 // Note. Checking the XUL or HTML namespace would not seem to gain us
943 // anything, because disabled attribute really is going to mean the same
944 // thing in any namespace.
945 // Note. We use the attribute instead of the disabled state bit because
946 // ARIA's aria-disabled does not affect the disabled state bit.
947 if (aAttribute == nsGkAtoms::disabled ||
948 aAttribute == nsGkAtoms::aria_disabled) {
949 // Do nothing if state wasn't changed (like @aria-disabled was removed but
950 // @disabled is still presented).
951 if (aAccessible->Unavailable() == mStateBitWasOn)
952 return;
953
954 nsRefPtr<AccEvent> enabledChangeEvent =
955 new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
956 FireDelayedEvent(enabledChangeEvent);
957
958 nsRefPtr<AccEvent> sensitiveChangeEvent =
959 new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
960 FireDelayedEvent(sensitiveChangeEvent);
961 return;
962 }
963
964 // Check for namespaced ARIA attribute
965 if (aNameSpaceID == kNameSpaceID_None) {
966 // Check for hyphenated aria-foo property?
967 if (StringBeginsWith(nsDependentAtomString(aAttribute),
968 NS_LITERAL_STRING("aria-"))) {
969 ARIAAttributeChanged(aAccessible, aAttribute);
970 }
971 }
972
973 // Fire name change and description change events. XXX: it's not complete and
974 // dupes the code logic of accessible name and description calculation, we do
975 // that for performance reasons.
976 if (aAttribute == nsGkAtoms::aria_label) {
977 FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
978 return;
979 }
980
981 if (aAttribute == nsGkAtoms::aria_describedby) {
982 FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
983 return;
984 }
985
986 nsIContent* elm = aAccessible->GetContent();
987 if (aAttribute == nsGkAtoms::aria_labelledby &&
988 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
989 FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
990 return;
991 }
992
993 if (aAttribute == nsGkAtoms::alt &&
994 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
995 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
996 FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
997 return;
998 }
999
1000 if (aAttribute == nsGkAtoms::title) {
1001 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
1002 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
1003 !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
1004 FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
1005 return;
1006 }
1007
1008 if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
1009 FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
1010
1011 return;
1012 }
1013
1014 if (aAttribute == nsGkAtoms::aria_busy) {
1015 bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
1016 eCaseMatters);
1017 nsRefPtr<AccEvent> event =
1018 new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
1019 FireDelayedEvent(event);
1020 return;
1021 }
1022
1023 // ARIA or XUL selection
1024 if ((aAccessible->GetContent()->IsXUL() && aAttribute == nsGkAtoms::selected) ||
1025 aAttribute == nsGkAtoms::aria_selected) {
1026 Accessible* widget =
1027 nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
1028 if (widget) {
1029 AccSelChangeEvent::SelChangeType selChangeType =
1030 elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
1031 AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
1032
1033 nsRefPtr<AccEvent> event =
1034 new AccSelChangeEvent(widget, aAccessible, selChangeType);
1035 FireDelayedEvent(event);
1036 }
1037
1038 return;
1039 }
1040
1041 if (aAttribute == nsGkAtoms::contenteditable) {
1042 nsRefPtr<AccEvent> editableChangeEvent =
1043 new AccStateChangeEvent(aAccessible, states::EDITABLE);
1044 FireDelayedEvent(editableChangeEvent);
1045 return;
1046 }
1047
1048 if (aAttribute == nsGkAtoms::value) {
1049 if (aAccessible->IsProgress())
1050 FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
1051 }
1052 }
1053
1054 // DocAccessible protected member
1055 void
1056 DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
1057 {
1058 // Note: For universal/global ARIA states and properties we don't care if
1059 // there is an ARIA role present or not.
1060
1061 if (aAttribute == nsGkAtoms::aria_required) {
1062 nsRefPtr<AccEvent> event =
1063 new AccStateChangeEvent(aAccessible, states::REQUIRED);
1064 FireDelayedEvent(event);
1065 return;
1066 }
1067
1068 if (aAttribute == nsGkAtoms::aria_invalid) {
1069 nsRefPtr<AccEvent> event =
1070 new AccStateChangeEvent(aAccessible, states::INVALID);
1071 FireDelayedEvent(event);
1072 return;
1073 }
1074
1075 // The activedescendant universal property redirects accessible focus events
1076 // to the element with the id that activedescendant points to. Make sure
1077 // the tree up to date before processing.
1078 if (aAttribute == nsGkAtoms::aria_activedescendant) {
1079 mNotificationController->HandleNotification<DocAccessible, Accessible>
1080 (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
1081
1082 return;
1083 }
1084
1085 // We treat aria-expanded as a global ARIA state for historical reasons
1086 if (aAttribute == nsGkAtoms::aria_expanded) {
1087 nsRefPtr<AccEvent> event =
1088 new AccStateChangeEvent(aAccessible, states::EXPANDED);
1089 FireDelayedEvent(event);
1090 return;
1091 }
1092
1093 // For aria attributes like drag and drop changes we fire a generic attribute
1094 // change event; at least until native API comes up with a more meaningful event.
1095 uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
1096 if (!(attrFlags & ATTR_BYPASSOBJ))
1097 FireDelayedEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
1098 aAccessible);
1099
1100 nsIContent* elm = aAccessible->GetContent();
1101
1102 if (aAttribute == nsGkAtoms::aria_checked ||
1103 (aAccessible->IsButton() &&
1104 aAttribute == nsGkAtoms::aria_pressed)) {
1105 const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
1106 states::CHECKED : states::PRESSED;
1107 nsRefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
1108 FireDelayedEvent(event);
1109
1110 bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
1111 bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
1112 nsGkAtoms::mixed, eCaseMatters);
1113 if (isMixed != wasMixed) {
1114 nsRefPtr<AccEvent> event =
1115 new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
1116 FireDelayedEvent(event);
1117 }
1118 return;
1119 }
1120
1121 if (aAttribute == nsGkAtoms::aria_readonly) {
1122 nsRefPtr<AccEvent> event =
1123 new AccStateChangeEvent(aAccessible, states::READONLY);
1124 FireDelayedEvent(event);
1125 return;
1126 }
1127
1128 // Fire value change event whenever aria-valuetext is changed, or
1129 // when aria-valuenow is changed and aria-valuetext is empty
1130 if (aAttribute == nsGkAtoms::aria_valuetext ||
1131 (aAttribute == nsGkAtoms::aria_valuenow &&
1132 (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
1133 elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
1134 nsGkAtoms::_empty, eCaseMatters)))) {
1135 FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
1136 return;
1137 }
1138 }
1139
1140 void
1141 DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
1142 {
1143 nsIContent* elm = aAccessible->GetContent();
1144 if (elm && aAccessible->IsActiveWidget()) {
1145 nsAutoString id;
1146 if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
1147 dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
1148 if (activeDescendantElm) {
1149 Accessible* activeDescendant = GetAccessible(activeDescendantElm);
1150 if (activeDescendant) {
1151 FocusMgr()->ActiveItemChanged(activeDescendant, false);
1152 #ifdef A11Y_LOG
1153 if (logging::IsEnabled(logging::eFocus))
1154 logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
1155 activeDescendant);
1156 #endif
1157 }
1158 }
1159 }
1160 }
1161 }
1162
1163 void
1164 DocAccessible::ContentAppended(nsIDocument* aDocument,
1165 nsIContent* aContainer,
1166 nsIContent* aFirstNewContent,
1167 int32_t /* unused */)
1168 {
1169 }
1170
1171 void
1172 DocAccessible::ContentStateChanged(nsIDocument* aDocument,
1173 nsIContent* aContent,
1174 EventStates aStateMask)
1175 {
1176 Accessible* accessible = GetAccessible(aContent);
1177 if (!accessible)
1178 return;
1179
1180 if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
1181 Accessible* widget = accessible->ContainerWidget();
1182 if (widget && widget->IsSelect()) {
1183 AccSelChangeEvent::SelChangeType selChangeType =
1184 aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
1185 AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
1186 nsRefPtr<AccEvent> event =
1187 new AccSelChangeEvent(widget, accessible, selChangeType);
1188 FireDelayedEvent(event);
1189 return;
1190 }
1191
1192 nsRefPtr<AccEvent> event =
1193 new AccStateChangeEvent(accessible, states::CHECKED,
1194 aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
1195 FireDelayedEvent(event);
1196 }
1197
1198 if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
1199 nsRefPtr<AccEvent> event =
1200 new AccStateChangeEvent(accessible, states::INVALID, true);
1201 FireDelayedEvent(event);
1202 }
1203
1204 if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
1205 nsRefPtr<AccEvent> event =
1206 new AccStateChangeEvent(accessible, states::TRAVERSED, true);
1207 FireDelayedEvent(event);
1208 }
1209 }
1210
1211 void
1212 DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
1213 EventStates aStateMask)
1214 {
1215 }
1216
1217 void
1218 DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
1219 nsIContent* aContent,
1220 CharacterDataChangeInfo* aInfo)
1221 {
1222 }
1223
1224 void
1225 DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
1226 nsIContent* aContent,
1227 CharacterDataChangeInfo* aInfo)
1228 {
1229 }
1230
1231 void
1232 DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
1233 nsIContent* aChild, int32_t /* unused */)
1234 {
1235 }
1236
1237 void
1238 DocAccessible::ContentRemoved(nsIDocument* aDocument, nsIContent* aContainer,
1239 nsIContent* aChild, int32_t /* unused */,
1240 nsIContent* aPreviousSibling)
1241 {
1242 }
1243
1244 void
1245 DocAccessible::ParentChainChanged(nsIContent* aContent)
1246 {
1247 }
1248
1249
1250 ////////////////////////////////////////////////////////////////////////////////
1251 // Accessible
1252
1253 #ifdef A11Y_LOG
1254 nsresult
1255 DocAccessible::HandleAccEvent(AccEvent* aEvent)
1256 {
1257 if (logging::IsEnabled(logging::eDocLoad))
1258 logging::DocLoadEventHandled(aEvent);
1259
1260 return HyperTextAccessible::HandleAccEvent(aEvent);
1261 }
1262 #endif
1263
1264 ////////////////////////////////////////////////////////////////////////////////
1265 // Public members
1266
1267 void*
1268 DocAccessible::GetNativeWindow() const
1269 {
1270 if (!mPresShell)
1271 return nullptr;
1272
1273 nsViewManager* vm = mPresShell->GetViewManager();
1274 if (!vm)
1275 return nullptr;
1276
1277 nsCOMPtr<nsIWidget> widget;
1278 vm->GetRootWidget(getter_AddRefs(widget));
1279 if (widget)
1280 return widget->GetNativeData(NS_NATIVE_WINDOW);
1281
1282 return nullptr;
1283 }
1284
1285 Accessible*
1286 DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
1287 {
1288 Accessible* child = GetAccessibleByUniqueID(aUniqueID);
1289 if (child)
1290 return child;
1291
1292 uint32_t childDocCount = mChildDocuments.Length();
1293 for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
1294 DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
1295 child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
1296 if (child)
1297 return child;
1298 }
1299
1300 return nullptr;
1301 }
1302
1303 Accessible*
1304 DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
1305 {
1306 if (!aNode || !aNode->IsInDoc())
1307 return nullptr;
1308
1309 nsINode* currNode = aNode;
1310 Accessible* accessible = nullptr;
1311 while (!(accessible = GetAccessible(currNode)) &&
1312 (currNode = currNode->GetParentNode()));
1313
1314 return accessible;
1315 }
1316
1317 Accessible*
1318 DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
1319 {
1320 Accessible* acc = GetAccessible(aNode);
1321 if (acc)
1322 return acc;
1323
1324 acc = GetContainerAccessible(aNode);
1325 if (acc) {
1326 uint32_t childCnt = acc->ChildCount();
1327 for (uint32_t idx = 0; idx < childCnt; idx++) {
1328 Accessible* child = acc->GetChildAt(idx);
1329 for (nsIContent* elm = child->GetContent();
1330 elm && elm != acc->GetContent();
1331 elm = elm->GetFlattenedTreeParent()) {
1332 if (elm == aNode)
1333 return child;
1334 }
1335 }
1336 }
1337
1338 return nullptr;
1339 }
1340
1341 void
1342 DocAccessible::BindToDocument(Accessible* aAccessible,
1343 nsRoleMapEntry* aRoleMapEntry)
1344 {
1345 // Put into DOM node cache.
1346 if (aAccessible->IsNodeMapEntry())
1347 mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
1348
1349 // Put into unique ID cache.
1350 mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
1351
1352 aAccessible->SetRoleMapEntry(aRoleMapEntry);
1353
1354 nsIContent* content = aAccessible->GetContent();
1355 if (content && content->IsElement())
1356 AddDependentIDsFor(content->AsElement());
1357 }
1358
1359 void
1360 DocAccessible::UnbindFromDocument(Accessible* aAccessible)
1361 {
1362 NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
1363 "Unbinding the unbound accessible!");
1364
1365 // Fire focus event on accessible having DOM focus if active item was removed
1366 // from the tree.
1367 if (FocusMgr()->IsActiveItem(aAccessible)) {
1368 FocusMgr()->ActiveItemChanged(nullptr);
1369 #ifdef A11Y_LOG
1370 if (logging::IsEnabled(logging::eFocus))
1371 logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
1372 #endif
1373 }
1374
1375 // Remove an accessible from node-to-accessible map if it exists there.
1376 if (aAccessible->IsNodeMapEntry() &&
1377 mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
1378 mNodeToAccessibleMap.Remove(aAccessible->GetNode());
1379
1380 void* uniqueID = aAccessible->UniqueID();
1381
1382 NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
1383 aAccessible->Shutdown();
1384
1385 mAccessibleCache.Remove(uniqueID);
1386 }
1387
1388 void
1389 DocAccessible::ContentInserted(nsIContent* aContainerNode,
1390 nsIContent* aStartChildNode,
1391 nsIContent* aEndChildNode)
1392 {
1393 // Ignore content insertions until we constructed accessible tree. Otherwise
1394 // schedule tree update on content insertion after layout.
1395 if (mNotificationController && HasLoadState(eTreeConstructed)) {
1396 // Update the whole tree of this document accessible when the container is
1397 // null (document element is inserted or removed).
1398 Accessible* container = aContainerNode ?
1399 GetAccessibleOrContainer(aContainerNode) : this;
1400
1401 mNotificationController->ScheduleContentInsertion(container,
1402 aStartChildNode,
1403 aEndChildNode);
1404 }
1405 }
1406
1407 void
1408 DocAccessible::ContentRemoved(nsIContent* aContainerNode,
1409 nsIContent* aChildNode)
1410 {
1411 // Update the whole tree of this document accessible when the container is
1412 // null (document element is removed).
1413 Accessible* container = aContainerNode ?
1414 GetAccessibleOrContainer(aContainerNode) : this;
1415
1416 UpdateTree(container, aChildNode, false);
1417 }
1418
1419 void
1420 DocAccessible::RecreateAccessible(nsIContent* aContent)
1421 {
1422 #ifdef A11Y_LOG
1423 if (logging::IsEnabled(logging::eTree)) {
1424 logging::MsgBegin("TREE", "accessible recreated");
1425 logging::Node("content", aContent);
1426 logging::MsgEnd();
1427 }
1428 #endif
1429
1430 // XXX: we shouldn't recreate whole accessible subtree, instead we should
1431 // subclass hide and show events to handle them separately and implement their
1432 // coalescence with normal hide and show events. Note, in this case they
1433 // should be coalesced with normal show/hide events.
1434
1435 nsIContent* parent = aContent->GetFlattenedTreeParent();
1436 ContentRemoved(parent, aContent);
1437 ContentInserted(parent, aContent, aContent->GetNextSibling());
1438 }
1439
1440 void
1441 DocAccessible::ProcessInvalidationList()
1442 {
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 Accessible* accessible = GetAccessible(content);
1449 if (!accessible) {
1450 Accessible* container = GetContainerAccessible(content);
1451 if (container) {
1452 container->UpdateChildren();
1453 accessible = GetAccessible(content);
1454 }
1455 }
1456
1457 // Make sure the subtree is created.
1458 if (accessible)
1459 CacheChildrenInSubtree(accessible);
1460 }
1461
1462 mInvalidationList.Clear();
1463 }
1464
1465 Accessible*
1466 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
1467 {
1468 if (!aNode->IsContent() || !aNode->AsContent()->IsHTML(nsGkAtoms::area))
1469 return GetAccessible(aNode);
1470
1471 // XXX Bug 135040, incorrect when multiple images use the same map.
1472 nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
1473 nsImageFrame* imageFrame = do_QueryFrame(frame);
1474 if (imageFrame) {
1475 Accessible* parent = GetAccessible(imageFrame->GetContent());
1476 if (parent) {
1477 Accessible* area =
1478 parent->AsImageMap()->GetChildAccessibleFor(aNode);
1479 if (area)
1480 return area;
1481
1482 return nullptr;
1483 }
1484 }
1485
1486 return GetAccessible(aNode);
1487 }
1488
1489 ////////////////////////////////////////////////////////////////////////////////
1490 // Accessible protected
1491
1492 void
1493 DocAccessible::CacheChildren()
1494 {
1495 // Search for accessible children starting from the document element since
1496 // some web pages tend to insert elements under it rather than document body.
1497 dom::Element* rootElm = mDocumentNode->GetRootElement();
1498 if (!rootElm)
1499 return;
1500
1501 // Ignore last HTML:br, copied from HyperTextAccessible.
1502 TreeWalker walker(this, rootElm);
1503 Accessible* lastChild = nullptr;
1504 while (Accessible* child = walker.NextChild()) {
1505 if (lastChild)
1506 AppendChild(lastChild);
1507
1508 lastChild = child;
1509 }
1510
1511 if (lastChild) {
1512 if (lastChild->IsHTMLBr())
1513 Document()->UnbindFromDocument(lastChild);
1514 else
1515 AppendChild(lastChild);
1516 }
1517 }
1518
1519 ////////////////////////////////////////////////////////////////////////////////
1520 // Protected members
1521
1522 void
1523 DocAccessible::NotifyOfLoading(bool aIsReloading)
1524 {
1525 // Mark the document accessible as loading, if it stays alive then we'll mark
1526 // it as loaded when we receive proper notification.
1527 mLoadState &= ~eDOMLoaded;
1528
1529 if (!IsLoadEventTarget())
1530 return;
1531
1532 if (aIsReloading) {
1533 // Fire reload and state busy events on existing document accessible while
1534 // event from user input flag can be calculated properly and accessible
1535 // is alive. When new document gets loaded then this one is destroyed.
1536 nsRefPtr<AccEvent> reloadEvent =
1537 new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
1538 nsEventShell::FireEvent(reloadEvent);
1539 }
1540
1541 // Fire state busy change event. Use delayed event since we don't care
1542 // actually if event isn't delivered when the document goes away like a shot.
1543 nsRefPtr<AccEvent> stateEvent =
1544 new AccStateChangeEvent(this, states::BUSY, true);
1545 FireDelayedEvent(stateEvent);
1546 }
1547
1548 void
1549 DocAccessible::DoInitialUpdate()
1550 {
1551 if (nsCoreUtils::IsTabDocument(mDocumentNode))
1552 mDocFlags |= eTabDocument;
1553
1554 mLoadState |= eTreeConstructed;
1555
1556 // The content element may be changed before the initial update and then we
1557 // miss the notification (since content tree change notifications are ignored
1558 // prior to initial update). Make sure the content element is valid.
1559 nsIContent* contentElm = nsCoreUtils::GetRoleContent(mDocumentNode);
1560 if (mContent != contentElm) {
1561 mContent = contentElm;
1562 SetRoleMapEntry(aria::GetRoleMap(mContent));
1563 }
1564
1565 // Build initial tree.
1566 CacheChildrenInSubtree(this);
1567
1568 // Fire reorder event after the document tree is constructed. Note, since
1569 // this reorder event is processed by parent document then events targeted to
1570 // this document may be fired prior to this reorder event. If this is
1571 // a problem then consider to keep event processing per tab document.
1572 if (!IsRoot()) {
1573 nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
1574 ParentDocument()->FireDelayedEvent(reorderEvent);
1575 }
1576 }
1577
1578 void
1579 DocAccessible::ProcessLoad()
1580 {
1581 mLoadState |= eCompletelyLoaded;
1582
1583 #ifdef A11Y_LOG
1584 if (logging::IsEnabled(logging::eDocLoad))
1585 logging::DocCompleteLoad(this, IsLoadEventTarget());
1586 #endif
1587
1588 // Do not fire document complete/stop events for root chrome document
1589 // accessibles and for frame/iframe documents because
1590 // a) screen readers start working on focus event in the case of root chrome
1591 // documents
1592 // b) document load event on sub documents causes screen readers to act is if
1593 // entire page is reloaded.
1594 if (!IsLoadEventTarget())
1595 return;
1596
1597 // Fire complete/load stopped if the load event type is given.
1598 if (mLoadEventType) {
1599 nsRefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
1600 FireDelayedEvent(loadEvent);
1601
1602 mLoadEventType = 0;
1603 }
1604
1605 // Fire busy state change event.
1606 nsRefPtr<AccEvent> stateEvent =
1607 new AccStateChangeEvent(this, states::BUSY, false);
1608 FireDelayedEvent(stateEvent);
1609 }
1610
1611 void
1612 DocAccessible::AddDependentIDsFor(dom::Element* aRelProviderElm,
1613 nsIAtom* aRelAttr)
1614 {
1615 for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1616 nsIAtom* relAttr = *kRelationAttrs[idx];
1617 if (aRelAttr && aRelAttr != relAttr)
1618 continue;
1619
1620 if (relAttr == nsGkAtoms::_for) {
1621 if (!aRelProviderElm->IsHTML() ||
1622 (aRelProviderElm->Tag() != nsGkAtoms::label &&
1623 aRelProviderElm->Tag() != nsGkAtoms::output))
1624 continue;
1625
1626 } else if (relAttr == nsGkAtoms::control) {
1627 if (!aRelProviderElm->IsXUL() ||
1628 (aRelProviderElm->Tag() != nsGkAtoms::label &&
1629 aRelProviderElm->Tag() != nsGkAtoms::description))
1630 continue;
1631 }
1632
1633 IDRefsIterator iter(this, aRelProviderElm, relAttr);
1634 while (true) {
1635 const nsDependentSubstring id = iter.NextID();
1636 if (id.IsEmpty())
1637 break;
1638
1639 AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1640 if (!providers) {
1641 providers = new AttrRelProviderArray();
1642 if (providers) {
1643 mDependentIDsHash.Put(id, providers);
1644 }
1645 }
1646
1647 if (providers) {
1648 AttrRelProvider* provider =
1649 new AttrRelProvider(relAttr, aRelProviderElm);
1650 if (provider) {
1651 providers->AppendElement(provider);
1652
1653 // We've got here during the children caching. If the referenced
1654 // content is not accessible then store it to pend its container
1655 // children invalidation (this happens immediately after the caching
1656 // is finished).
1657 nsIContent* dependentContent = iter.GetElem(id);
1658 if (dependentContent && !HasAccessible(dependentContent)) {
1659 mInvalidationList.AppendElement(dependentContent);
1660 }
1661 }
1662 }
1663 }
1664
1665 // If the relation attribute is given then we don't have anything else to
1666 // check.
1667 if (aRelAttr)
1668 break;
1669 }
1670 }
1671
1672 void
1673 DocAccessible::RemoveDependentIDsFor(dom::Element* aRelProviderElm,
1674 nsIAtom* aRelAttr)
1675 {
1676 for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
1677 nsIAtom* relAttr = *kRelationAttrs[idx];
1678 if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
1679 continue;
1680
1681 IDRefsIterator iter(this, aRelProviderElm, relAttr);
1682 while (true) {
1683 const nsDependentSubstring id = iter.NextID();
1684 if (id.IsEmpty())
1685 break;
1686
1687 AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
1688 if (providers) {
1689 for (uint32_t jdx = 0; jdx < providers->Length(); ) {
1690 AttrRelProvider* provider = (*providers)[jdx];
1691 if (provider->mRelAttr == relAttr &&
1692 provider->mContent == aRelProviderElm)
1693 providers->RemoveElement(provider);
1694 else
1695 jdx++;
1696 }
1697 if (providers->Length() == 0)
1698 mDependentIDsHash.Remove(id);
1699 }
1700 }
1701
1702 // If the relation attribute is given then we don't have anything else to
1703 // check.
1704 if (aRelAttr)
1705 break;
1706 }
1707 }
1708
1709 bool
1710 DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
1711 nsIAtom* aAttribute)
1712 {
1713 if (aAttribute == nsGkAtoms::role) {
1714 // It is common for js libraries to set the role on the body element after
1715 // the document has loaded. In this case we just update the role map entry.
1716 if (mContent == aElement) {
1717 SetRoleMapEntry(aria::GetRoleMap(aElement));
1718 return true;
1719 }
1720
1721 // Recreate the accessible when role is changed because we might require a
1722 // different accessible class for the new role or the accessible may expose
1723 // a different sets of interfaces (COM restriction).
1724 RecreateAccessible(aElement);
1725
1726 return true;
1727 }
1728
1729 if (aAttribute == nsGkAtoms::href ||
1730 aAttribute == nsGkAtoms::onclick) {
1731 // Not worth the expense to ensure which namespace these are in. It doesn't
1732 // kill use to recreate the accessible even if the attribute was used in
1733 // the wrong namespace or an element that doesn't support it.
1734
1735 // Make sure the accessible is recreated asynchronously to allow the content
1736 // to handle the attribute change.
1737 RecreateAccessible(aElement);
1738 return true;
1739 }
1740
1741 if (aAttribute == nsGkAtoms::aria_multiselectable &&
1742 aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
1743 // This affects whether the accessible supports SelectAccessible.
1744 // COM says we cannot change what interfaces are supported on-the-fly,
1745 // so invalidate this object. A new one will be created on demand.
1746 RecreateAccessible(aElement);
1747
1748 return true;
1749 }
1750
1751 return false;
1752 }
1753
1754 void
1755 DocAccessible::ProcessContentInserted(Accessible* aContainer,
1756 const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent)
1757 {
1758 // Process insertions if the container accessible is still in tree.
1759 if (!HasAccessible(aContainer->GetNode()))
1760 return;
1761
1762 bool containerNotUpdated = true;
1763
1764 for (uint32_t idx = 0; idx < aInsertedContent->Length(); idx++) {
1765 // The container might be changed, for example, because of the subsequent
1766 // overlapping content insertion (i.e. other content was inserted between
1767 // this inserted content and its container or the content was reinserted
1768 // into different container of unrelated part of tree). To avoid a double
1769 // processing of the content insertion ignore this insertion notification.
1770 // Note, the inserted content might be not in tree at all at this point what
1771 // means there's no container. Ignore the insertion too.
1772
1773 Accessible* presentContainer =
1774 GetContainerAccessible(aInsertedContent->ElementAt(idx));
1775 if (presentContainer != aContainer)
1776 continue;
1777
1778 if (containerNotUpdated) {
1779 containerNotUpdated = false;
1780
1781 if (aContainer == this) {
1782 // If new root content has been inserted then update it.
1783 nsIContent* rootContent = nsCoreUtils::GetRoleContent(mDocumentNode);
1784 if (rootContent != mContent) {
1785 mContent = rootContent;
1786 SetRoleMapEntry(aria::GetRoleMap(mContent));
1787 }
1788
1789 // Continue to update the tree even if we don't have root content.
1790 // For example, elements may be inserted under the document element while
1791 // there is no HTML body element.
1792 }
1793
1794 // XXX: Invalidate parent-child relations for container accessible and its
1795 // children because there's no good way to find insertion point of new child
1796 // accessibles into accessible tree. We need to invalidate children even
1797 // there's no inserted accessibles in the end because accessible children
1798 // are created while parent recaches child accessibles.
1799 aContainer->InvalidateChildren();
1800 CacheChildrenInSubtree(aContainer);
1801 }
1802
1803 UpdateTree(aContainer, aInsertedContent->ElementAt(idx), true);
1804 }
1805 }
1806
1807 void
1808 DocAccessible::UpdateTree(Accessible* aContainer, nsIContent* aChildNode,
1809 bool aIsInsert)
1810 {
1811 uint32_t updateFlags = eNoAccessible;
1812
1813 // If child node is not accessible then look for its accessible children.
1814 Accessible* child = GetAccessible(aChildNode);
1815 #ifdef A11Y_LOG
1816 if (logging::IsEnabled(logging::eTree)) {
1817 logging::MsgBegin("TREE", "process content %s",
1818 (aIsInsert ? "insertion" : "removal"));
1819 logging::Node("container", aContainer->GetNode());
1820 logging::Node("child", aChildNode);
1821 if (child)
1822 logging::Address("child", child);
1823 else
1824 logging::MsgEntry("child accessible: null");
1825
1826 logging::MsgEnd();
1827 }
1828 #endif
1829
1830 nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(aContainer);
1831
1832 if (child) {
1833 updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
1834 } else {
1835 if (aIsInsert) {
1836 TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
1837
1838 while ((child = walker.NextChild()))
1839 updateFlags |= UpdateTreeInternal(child, aIsInsert, reorderEvent);
1840 } else {
1841 // aChildNode may not coorespond to a particular accessible, to handle
1842 // this we go through all the children of aContainer. Then if a child
1843 // has aChildNode as an ancestor, or does not have the node for
1844 // aContainer as an ancestor remove that child of aContainer. Note that
1845 // when we are called aChildNode may already have been removed
1846 // from the DOM so we can't expect it to have a parent or what was it's
1847 // parent to have it as a child.
1848 nsINode* containerNode = aContainer->GetNode();
1849 for (uint32_t idx = 0; idx < aContainer->ContentChildCount();) {
1850 Accessible* child = aContainer->ContentChildAt(idx);
1851
1852 // If accessible doesn't have its own content then we assume parent
1853 // will handle its update. If child is DocAccessible then we don't
1854 // handle updating it here either.
1855 if (!child->HasOwnContent() || child->IsDoc()) {
1856 idx++;
1857 continue;
1858 }
1859
1860 nsINode* childNode = child->GetContent();
1861 while (childNode != aChildNode && childNode != containerNode &&
1862 (childNode = childNode->GetParentNode()));
1863
1864 if (childNode != containerNode) {
1865 updateFlags |= UpdateTreeInternal(child, false, reorderEvent);
1866 } else {
1867 idx++;
1868 }
1869 }
1870 }
1871 }
1872
1873 // Content insertion/removal is not cause of accessible tree change.
1874 if (updateFlags == eNoAccessible)
1875 return;
1876
1877 // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
1878 // if it did.
1879 if (aIsInsert && !(updateFlags & eAlertAccessible)) {
1880 // XXX: tree traversal is perf issue, accessible should know if they are
1881 // children of alert accessible to avoid this.
1882 Accessible* ancestor = aContainer;
1883 while (ancestor) {
1884 if (ancestor->ARIARole() == roles::ALERT) {
1885 FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
1886 break;
1887 }
1888
1889 // Don't climb above this document.
1890 if (ancestor == this)
1891 break;
1892
1893 ancestor = ancestor->Parent();
1894 }
1895 }
1896
1897 MaybeNotifyOfValueChange(aContainer);
1898
1899 // Fire reorder event so the MSAA clients know the children have changed. Also
1900 // the event is used internally by MSAA layer.
1901 FireDelayedEvent(reorderEvent);
1902 }
1903
1904 uint32_t
1905 DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert,
1906 AccReorderEvent* aReorderEvent)
1907 {
1908 uint32_t updateFlags = eAccessible;
1909
1910 // If a focused node has been shown then it could mean its frame was recreated
1911 // while the node stays focused and we need to fire focus event on
1912 // the accessible we just created. If the queue contains a focus event for
1913 // this node already then it will be suppressed by this one.
1914 Accessible* focusedAcc = nullptr;
1915
1916 nsINode* node = aChild->GetNode();
1917 if (aIsInsert) {
1918 // Create accessible tree for shown accessible.
1919 CacheChildrenInSubtree(aChild, &focusedAcc);
1920
1921 } else {
1922 // Fire menupopup end event before hide event if a menu goes away.
1923
1924 // XXX: We don't look into children of hidden subtree to find hiding
1925 // menupopup (as we did prior bug 570275) because we don't do that when
1926 // menu is showing (and that's impossible until bug 606924 is fixed).
1927 // Nevertheless we should do this at least because layout coalesces
1928 // the changes before our processing and we may miss some menupopup
1929 // events. Now we just want to be consistent in content insertion/removal
1930 // handling.
1931 if (aChild->ARIARole() == roles::MENUPOPUP)
1932 FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, aChild);
1933 }
1934
1935 // Fire show/hide event.
1936 nsRefPtr<AccMutationEvent> event;
1937 if (aIsInsert)
1938 event = new AccShowEvent(aChild, node);
1939 else
1940 event = new AccHideEvent(aChild, node);
1941
1942 FireDelayedEvent(event);
1943 aReorderEvent->AddSubMutationEvent(event);
1944
1945 if (aIsInsert) {
1946 roles::Role ariaRole = aChild->ARIARole();
1947 if (ariaRole == roles::MENUPOPUP) {
1948 // Fire EVENT_MENUPOPUP_START if ARIA menu appears.
1949 FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
1950
1951 } else if (ariaRole == roles::ALERT) {
1952 // Fire EVENT_ALERT if ARIA alert appears.
1953 updateFlags = eAlertAccessible;
1954 FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
1955 }
1956 } else {
1957 // Update the tree for content removal.
1958 // The accessible parent may differ from container accessible if
1959 // the parent doesn't have own DOM node like list accessible for HTML
1960 // selects.
1961 Accessible* parent = aChild->Parent();
1962 NS_ASSERTION(parent, "No accessible parent?!");
1963 if (parent)
1964 parent->RemoveChild(aChild);
1965
1966 UncacheChildrenInSubtree(aChild);
1967 }
1968
1969 // XXX: do we really want to send focus to focused DOM node not taking into
1970 // account active item?
1971 if (focusedAcc) {
1972 FocusMgr()->DispatchFocusEvent(this, focusedAcc);
1973 SelectionMgr()->SetControlSelectionListener(focusedAcc->GetNode()->AsElement());
1974 }
1975
1976 return updateFlags;
1977 }
1978
1979 void
1980 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
1981 Accessible** aFocusedAcc)
1982 {
1983 // If the accessible is focused then report a focus event after all related
1984 // mutation events.
1985 if (aFocusedAcc && !*aFocusedAcc &&
1986 FocusMgr()->HasDOMFocus(aRoot->GetContent()))
1987 *aFocusedAcc = aRoot;
1988
1989 aRoot->EnsureChildren();
1990
1991 // Make sure we create accessible tree defined in DOM only, i.e. if accessible
1992 // provides specific tree (like XUL trees) then tree creation is handled by
1993 // this accessible.
1994 uint32_t count = aRoot->ContentChildCount();
1995 for (uint32_t idx = 0; idx < count; idx++) {
1996 Accessible* child = aRoot->ContentChildAt(idx);
1997 NS_ASSERTION(child, "Illicit tree change while tree is created!");
1998 // Don't cross document boundaries.
1999 if (child && child->IsContent())
2000 CacheChildrenInSubtree(child, aFocusedAcc);
2001 }
2002
2003 // Fire document load complete on ARIA documents.
2004 // XXX: we should delay an event if the ARIA document has aria-busy.
2005 if (aRoot->HasARIARole() && !aRoot->IsDoc()) {
2006 a11y::role role = aRoot->ARIARole();
2007 if (role == roles::DIALOG || role == roles::DOCUMENT)
2008 FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
2009 }
2010 }
2011
2012 void
2013 DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
2014 {
2015 aRoot->mStateFlags |= eIsNotInDocument;
2016
2017 nsIContent* rootContent = aRoot->GetContent();
2018 if (rootContent && rootContent->IsElement())
2019 RemoveDependentIDsFor(rootContent->AsElement());
2020
2021 uint32_t count = aRoot->ContentChildCount();
2022 for (uint32_t idx = 0; idx < count; idx++)
2023 UncacheChildrenInSubtree(aRoot->ContentChildAt(idx));
2024
2025 if (aRoot->IsNodeMapEntry() &&
2026 mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
2027 mNodeToAccessibleMap.Remove(aRoot->GetNode());
2028 }
2029
2030 void
2031 DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
2032 {
2033 // Traverse through children and shutdown them before this accessible. When
2034 // child gets shutdown then it removes itself from children array of its
2035 //parent. Use jdx index to process the cases if child is not attached to the
2036 // parent and as result doesn't remove itself from its children.
2037 uint32_t count = aAccessible->ContentChildCount();
2038 for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
2039 Accessible* child = aAccessible->ContentChildAt(jdx);
2040 if (!child->IsBoundToParent()) {
2041 NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
2042 jdx++;
2043 }
2044
2045 // Don't cross document boundaries. The outerdoc shutdown takes care about
2046 // its subdocument.
2047 if (!child->IsDoc())
2048 ShutdownChildrenInSubtree(child);
2049 }
2050
2051 UnbindFromDocument(aAccessible);
2052 }
2053
2054 bool
2055 DocAccessible::IsLoadEventTarget() const
2056 {
2057 nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
2058 NS_ASSERTION(treeItem, "No document shell for document!");
2059
2060 nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
2061 treeItem->GetParent(getter_AddRefs(parentTreeItem));
2062
2063 // Not a root document.
2064 if (parentTreeItem) {
2065 // Return true if it's either:
2066 // a) tab document;
2067 nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
2068 treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
2069 if (parentTreeItem == rootTreeItem)
2070 return true;
2071
2072 // b) frame/iframe document and its parent document is not in loading state
2073 // Note: we can get notifications while document is loading (and thus
2074 // while there's no parent document yet).
2075 DocAccessible* parentDoc = ParentDocument();
2076 return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
2077 }
2078
2079 // It's content (not chrome) root document.
2080 return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
2081 }
2082
2083 PLDHashOperator
2084 DocAccessible::CycleCollectorTraverseDepIDsEntry(const nsAString& aKey,
2085 AttrRelProviderArray* aProviders,
2086 void* aUserArg)
2087 {
2088 nsCycleCollectionTraversalCallback* cb =
2089 static_cast<nsCycleCollectionTraversalCallback*>(aUserArg);
2090
2091 for (int32_t jdx = aProviders->Length() - 1; jdx >= 0; jdx--) {
2092 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
2093 "content of dependent ids hash entry of document accessible");
2094
2095 AttrRelProvider* provider = (*aProviders)[jdx];
2096 cb->NoteXPCOMChild(provider->mContent);
2097
2098 NS_ASSERTION(provider->mContent->IsInDoc(),
2099 "Referred content is not in document!");
2100 }
2101
2102 return PL_DHASH_NEXT;
2103 }
2104

mercurial