Bug 496271, automation config for Tb2.0.0.22 build1, p=joduinn, r=me
[mozilla-1.9.git] / accessible / src / base / nsDocAccessible.cpp
blob4752e25e604232678a3f093f9476d85b9cbbb19b
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 2003
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Original Author: Aaron Leventhal (aaronl@netscape.com)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsRootAccessible.h"
40 #include "nsAccessibilityAtoms.h"
41 #include "nsAccessibleEventData.h"
42 #include "nsIAccessibilityService.h"
43 #include "nsIMutableArray.h"
44 #include "nsICommandManager.h"
45 #include "nsIDocShell.h"
46 #include "nsIDocShellTreeItem.h"
47 #include "nsIDocument.h"
48 #include "nsIDOMAttr.h"
49 #include "nsIDOMCharacterData.h"
50 #include "nsIDOMDocument.h"
51 #include "nsIDOMDocumentType.h"
52 #include "nsIDOMNSDocument.h"
53 #include "nsIDOMNSHTMLDocument.h"
54 #include "nsIDOMMutationEvent.h"
55 #include "nsPIDOMWindow.h"
56 #include "nsIDOMXULPopupElement.h"
57 #include "nsIEditingSession.h"
58 #include "nsIEventStateManager.h"
59 #include "nsIFrame.h"
60 #include "nsHTMLSelectAccessible.h"
61 #include "nsIInterfaceRequestorUtils.h"
62 #include "nsINameSpaceManager.h"
63 #include "nsIPresShell.h"
64 #include "nsIServiceManager.h"
65 #include "nsIScrollableView.h"
66 #include "nsIViewManager.h"
67 #include "nsIView.h"
68 #include "nsUnicharUtils.h"
69 #include "nsIURI.h"
70 #include "nsIWebNavigation.h"
71 #include "nsIFocusController.h"
72 #ifdef MOZ_XUL
73 #include "nsIXULDocument.h"
74 #endif
76 //=============================//
77 // nsDocAccessible //
78 //=============================//
80 PRUint32 nsDocAccessible::gLastFocusedAccessiblesState = 0;
81 nsIAtom *nsDocAccessible::gLastFocusedFrameType = nsnull;
83 //-----------------------------------------------------
84 // construction
85 //-----------------------------------------------------
86 nsDocAccessible::nsDocAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell):
87 nsHyperTextAccessibleWrap(aDOMNode, aShell), mWnd(nsnull),
88 mScrollPositionChangedTicks(0), mIsContentLoaded(PR_FALSE),
89 mIsLoadCompleteFired(PR_FALSE), mInFlushPendingEvents(PR_FALSE)
91 // For GTK+ native window, we do nothing here.
92 if (!mDOMNode)
93 return;
95 // Because of the way document loading happens, the new nsIWidget is created before
96 // the old one is removed. Since it creates the nsDocAccessible, for a brief moment
97 // there can be 2 nsDocAccessible's for the content area, although for 2 different
98 // pres shells.
100 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(mWeakShell));
101 if (shell) {
102 // Find mDocument
103 mDocument = shell->GetDocument();
105 // Find mWnd
106 nsIViewManager* vm = shell->GetViewManager();
107 if (vm) {
108 nsCOMPtr<nsIWidget> widget;
109 vm->GetWidget(getter_AddRefs(widget));
110 if (widget) {
111 mWnd = widget->GetNativeData(NS_NATIVE_WINDOW);
116 // XXX aaronl should we use an algorithm for the initial cache size?
117 mAccessNodeCache.Init(kDefaultCacheSize);
119 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
120 nsAccUtils::GetDocShellTreeItemFor(mDOMNode);
121 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(docShellTreeItem);
122 if (docShell) {
123 PRUint32 busyFlags;
124 docShell->GetBusyFlags(&busyFlags);
125 if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) {
126 mIsContentLoaded = PR_TRUE;
131 //-----------------------------------------------------
132 // destruction
133 //-----------------------------------------------------
134 nsDocAccessible::~nsDocAccessible()
138 NS_INTERFACE_MAP_BEGIN(nsDocAccessible)
139 NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
140 NS_INTERFACE_MAP_ENTRY(nsPIAccessibleDocument)
141 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
142 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
143 NS_INTERFACE_MAP_ENTRY(nsIScrollPositionListener)
144 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
145 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleDocument)
146 NS_INTERFACE_MAP_ENTRY(nsIObserver)
147 NS_INTERFACE_MAP_END_INHERITING(nsHyperTextAccessible)
149 NS_IMPL_ADDREF_INHERITED(nsDocAccessible, nsHyperTextAccessible)
150 NS_IMPL_RELEASE_INHERITED(nsDocAccessible, nsHyperTextAccessible)
152 NS_IMETHODIMP nsDocAccessible::GetName(nsAString& aName)
154 nsresult rv = NS_OK;
155 aName.Truncate();
156 if (mParent) {
157 rv = mParent->GetName(aName); // Allow owning iframe to override the name
159 if (aName.IsEmpty()) {
160 rv = nsAccessible::GetName(aName); // Allow name via aria-labelledby or title attribute
162 if (aName.IsEmpty()) {
163 rv = GetTitle(aName); // Try title element
165 if (aName.IsEmpty()) { // Last resort: use URL
166 rv = GetURL(aName);
169 return rv;
172 NS_IMETHODIMP nsDocAccessible::GetRole(PRUint32 *aRole)
174 *aRole = nsIAccessibleRole::ROLE_PANE; // Fall back
176 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem =
177 nsAccUtils::GetDocShellTreeItemFor(mDOMNode);
178 if (docShellTreeItem) {
179 nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
180 docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
181 PRInt32 itemType;
182 docShellTreeItem->GetItemType(&itemType);
183 if (sameTypeRoot == docShellTreeItem) {
184 // Root of content or chrome tree
185 if (itemType == nsIDocShellTreeItem::typeChrome) {
186 *aRole = nsIAccessibleRole::ROLE_CHROME_WINDOW;
188 else if (itemType == nsIDocShellTreeItem::typeContent) {
189 #ifdef MOZ_XUL
190 nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
191 if (xulDoc) {
192 *aRole = nsIAccessibleRole::ROLE_APPLICATION;
193 } else {
194 *aRole = nsIAccessibleRole::ROLE_DOCUMENT;
196 #else
197 *aRole = nsIAccessibleRole::ROLE_DOCUMENT;
198 #endif
201 else if (itemType == nsIDocShellTreeItem::typeContent) {
202 *aRole = nsIAccessibleRole::ROLE_DOCUMENT;
206 return NS_OK;
209 NS_IMETHODIMP nsDocAccessible::SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry)
211 NS_ENSURE_STATE(mDocument);
213 mRoleMapEntry = aRoleMapEntry;
215 // Allow use of ARIA role from outer to override
216 nsIDocument *parentDoc = mDocument->GetParentDocument();
217 NS_ENSURE_TRUE(parentDoc, NS_ERROR_FAILURE);
218 nsIContent *ownerContent = parentDoc->FindContentForSubDocument(mDocument);
219 nsCOMPtr<nsIDOMNode> ownerNode(do_QueryInterface(ownerContent));
220 if (ownerNode) {
221 nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(ownerNode);
222 if (roleMapEntry)
223 mRoleMapEntry = roleMapEntry; // Override
226 return NS_OK;
229 NS_IMETHODIMP
230 nsDocAccessible::GetDescription(nsAString& aDescription)
232 if (mParent)
233 mParent->GetDescription(aDescription);
235 if (aDescription.IsEmpty()) {
236 nsAutoString description;
237 GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby, description);
238 aDescription = description;
241 return NS_OK;
244 NS_IMETHODIMP
245 nsDocAccessible::GetState(PRUint32 *aState, PRUint32 *aExtraState)
247 // nsAccessible::GetState() always fail for document accessible.
248 nsAccessible::GetState(aState, aExtraState);
249 if (!mDOMNode)
250 return NS_OK;
252 #ifdef MOZ_XUL
253 nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
254 if (!xulDoc)
255 #endif
257 // XXX Need to invent better check to see if doc is focusable,
258 // which it should be if it is scrollable. A XUL document could be focusable.
259 // See bug 376803.
260 *aState |= nsIAccessibleStates::STATE_FOCUSABLE;
261 if (gLastFocusedNode == mDOMNode) {
262 *aState |= nsIAccessibleStates::STATE_FOCUSED;
266 if (!mIsContentLoaded) {
267 *aState |= nsIAccessibleStates::STATE_BUSY;
268 if (aExtraState) {
269 *aExtraState |= nsIAccessibleStates::EXT_STATE_STALE;
273 nsIFrame* frame = GetFrame();
274 while (frame != nsnull && !frame->HasView()) {
275 frame = frame->GetParent();
278 if (frame == nsnull ||
279 !CheckVisibilityInParentChain(mDocument, frame->GetViewExternal())) {
280 *aState |= nsIAccessibleStates::STATE_INVISIBLE |
281 nsIAccessibleStates::STATE_OFFSCREEN;
284 nsCOMPtr<nsIEditor> editor;
285 GetAssociatedEditor(getter_AddRefs(editor));
286 if (!editor) {
287 *aState |= nsIAccessibleStates::STATE_READONLY;
289 else if (aExtraState) {
290 *aExtraState |= nsIAccessibleStates::EXT_STATE_EDITABLE;
293 return NS_OK;
296 NS_IMETHODIMP
297 nsDocAccessible::GetARIAState(PRUint32 *aState)
299 // Combine with states from outer doc
300 NS_ENSURE_ARG_POINTER(aState);
301 nsresult rv = nsAccessible::GetARIAState(aState);
302 NS_ENSURE_SUCCESS(rv, rv);
304 nsCOMPtr<nsPIAccessible> privateParentAccessible = do_QueryInterface(mParent);
305 if (privateParentAccessible) // Allow iframe/frame etc. to have final state override via ARIA
306 return privateParentAccessible->GetARIAState(aState);
308 return rv;
311 NS_IMETHODIMP
312 nsDocAccessible::GetAttributes(nsIPersistentProperties **aAttributes)
314 nsAccessible::GetAttributes(aAttributes);
315 if (mParent) {
316 mParent->GetAttributes(aAttributes); // Add parent attributes (override inner)
318 return NS_OK;
321 NS_IMETHODIMP nsDocAccessible::GetFocusedChild(nsIAccessible **aFocusedChild)
323 if (!gLastFocusedNode) {
324 *aFocusedChild = nsnull;
325 return NS_OK;
328 // Return an accessible for the current global focus, which does not have to
329 // be contained within the current document.
330 nsCOMPtr<nsIAccessibilityService> accService =
331 do_GetService("@mozilla.org/accessibilityService;1");
332 return accService->GetAccessibleFor(gLastFocusedNode, aFocusedChild);
335 NS_IMETHODIMP nsDocAccessible::TakeFocus()
337 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
338 PRUint32 state;
339 GetState(&state, nsnull);
340 if (0 == (state & nsIAccessibleStates::STATE_FOCUSABLE)) {
341 return NS_ERROR_FAILURE; // Not focusable
344 nsCOMPtr<nsIDocShellTreeItem> treeItem =
345 nsAccUtils::GetDocShellTreeItemFor(mDOMNode);
346 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
347 NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
349 nsCOMPtr<nsIPresShell> shell(GetPresShell());
350 if (!shell) {
351 NS_WARNING("Was not shutdown properly via InvalidateCacheSubtree()");
352 return NS_ERROR_FAILURE;
354 nsIEventStateManager *esm = shell->GetPresContext()->EventStateManager();
355 NS_ENSURE_TRUE(esm, NS_ERROR_FAILURE);
357 // Focus the document
358 nsresult rv = docShell->SetHasFocus(PR_TRUE);
359 NS_ENSURE_SUCCESS(rv, rv);
361 // Clear out any existing focus state
362 return esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
365 // ------- nsIAccessibleDocument Methods (5) ---------------
367 NS_IMETHODIMP nsDocAccessible::GetURL(nsAString& aURL)
369 if (!mDocument) {
370 return NS_ERROR_FAILURE; // Document has been shut down
372 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
373 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
374 nsCAutoString theURL;
375 if (webNav) {
376 nsCOMPtr<nsIURI> pURI;
377 webNav->GetCurrentURI(getter_AddRefs(pURI));
378 if (pURI)
379 pURI->GetSpec(theURL);
381 CopyUTF8toUTF16(theURL, aURL);
382 return NS_OK;
385 NS_IMETHODIMP nsDocAccessible::GetTitle(nsAString& aTitle)
387 if (mDocument) {
388 aTitle = mDocument->GetDocumentTitle();
389 return NS_OK;
392 return NS_ERROR_FAILURE;
395 NS_IMETHODIMP nsDocAccessible::GetMimeType(nsAString& aMimeType)
397 nsCOMPtr<nsIDOMNSDocument> domnsDocument(do_QueryInterface(mDocument));
398 if (domnsDocument) {
399 return domnsDocument->GetContentType(aMimeType);
401 return NS_ERROR_FAILURE;
404 NS_IMETHODIMP nsDocAccessible::GetDocType(nsAString& aDocType)
406 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mDocument));
407 nsCOMPtr<nsIDOMDocumentType> docType;
409 #ifdef MOZ_XUL
410 nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocument));
411 if (xulDoc) {
412 aDocType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
413 return NS_OK;
414 } else
415 #endif
416 if (domDoc && NS_SUCCEEDED(domDoc->GetDoctype(getter_AddRefs(docType))) && docType) {
417 return docType->GetPublicId(aDocType);
420 return NS_ERROR_FAILURE;
423 NS_IMETHODIMP nsDocAccessible::GetNameSpaceURIForID(PRInt16 aNameSpaceID, nsAString& aNameSpaceURI)
425 if (mDocument) {
426 nsCOMPtr<nsINameSpaceManager> nameSpaceManager =
427 do_GetService(NS_NAMESPACEMANAGER_CONTRACTID);
428 if (nameSpaceManager)
429 return nameSpaceManager->GetNameSpaceURI(aNameSpaceID, aNameSpaceURI);
431 return NS_ERROR_FAILURE;
434 NS_IMETHODIMP nsDocAccessible::GetWindowHandle(void **aWindow)
436 *aWindow = mWnd;
437 return NS_OK;
440 NS_IMETHODIMP nsDocAccessible::GetWindow(nsIDOMWindow **aDOMWin)
442 *aDOMWin = nsnull;
443 if (!mDocument) {
444 return NS_ERROR_FAILURE; // Accessible is Shutdown()
446 *aDOMWin = mDocument->GetWindow();
448 if (!*aDOMWin)
449 return NS_ERROR_FAILURE; // No DOM Window
451 NS_ADDREF(*aDOMWin);
453 return NS_OK;
456 NS_IMETHODIMP nsDocAccessible::GetDocument(nsIDOMDocument **aDOMDoc)
458 nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mDocument));
459 *aDOMDoc = domDoc;
461 if (domDoc) {
462 NS_ADDREF(*aDOMDoc);
463 return NS_OK;
466 return NS_ERROR_FAILURE;
469 NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
471 NS_ENSURE_ARG_POINTER(aEditor);
473 *aEditor = nsnull;
474 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
476 if (!mDocument->HasFlag(NODE_IS_EDITABLE)) {
477 return NS_OK; // Document not editable
480 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
481 nsCOMPtr<nsIEditingSession> editingSession(do_GetInterface(container));
482 if (!editingSession)
483 return NS_OK; // No editing session interface
485 nsCOMPtr<nsIEditor> editor;
486 editingSession->GetEditorForWindow(mDocument->GetWindow(), getter_AddRefs(editor));
487 if (!editor) {
488 return NS_OK;
490 PRBool isEditable;
491 editor->GetIsDocumentEditable(&isEditable);
492 if (isEditable) {
493 NS_ADDREF(*aEditor = editor);
495 return NS_OK;
498 NS_IMETHODIMP nsDocAccessible::GetCachedAccessNode(void *aUniqueID, nsIAccessNode **aAccessNode)
500 GetCacheEntry(mAccessNodeCache, aUniqueID, aAccessNode); // Addrefs for us
501 #ifdef DEBUG_A11Y
502 // All cached accessible nodes should be in the parent
503 // It will assert if not all the children were created
504 // when they were first cached, and no invalidation
505 // ever corrected parent accessible's child cache.
506 nsCOMPtr<nsIAccessible> accessible = do_QueryInterface(*aAccessNode);
507 nsCOMPtr<nsPIAccessible> privateAccessible = do_QueryInterface(accessible);
508 if (privateAccessible) {
509 nsCOMPtr<nsIAccessible> parent;
510 privateAccessible->GetCachedParent(getter_AddRefs(parent));
511 nsCOMPtr<nsPIAccessible> privateParent(do_QueryInterface(parent));
512 if (privateParent) {
513 privateParent->TestChildCache(accessible);
516 #endif
517 return NS_OK;
520 NS_IMETHODIMP
521 nsDocAccessible::CacheAccessNode(void *aUniqueID, nsIAccessNode *aAccessNode)
523 // If there is an access node for the given unique ID then let's shutdown it.
524 // The unique ID may be presented in the cache if originally we created
525 // access node object and then we want to create accessible object when
526 // DOM node is changed.
527 nsCOMPtr<nsIAccessNode> accessNode;
528 GetCacheEntry(mAccessNodeCache, aUniqueID, getter_AddRefs(accessNode));
529 if (accessNode) {
530 nsCOMPtr<nsPIAccessNode> prAccessNode = do_QueryInterface(accessNode);
531 prAccessNode->Shutdown();
534 PutCacheEntry(mAccessNodeCache, aUniqueID, aAccessNode);
535 return NS_OK;
538 NS_IMETHODIMP nsDocAccessible::GetParent(nsIAccessible **aParent)
540 // Hook up our new accessible with our parent
541 *aParent = nsnull;
542 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
543 if (!mParent) {
544 nsIDocument *parentDoc = mDocument->GetParentDocument();
545 NS_ENSURE_TRUE(parentDoc, NS_ERROR_FAILURE);
546 nsIContent *ownerContent = parentDoc->FindContentForSubDocument(mDocument);
547 nsCOMPtr<nsIDOMNode> ownerNode(do_QueryInterface(ownerContent));
548 if (ownerNode) {
549 nsCOMPtr<nsIAccessibilityService> accService =
550 do_GetService("@mozilla.org/accessibilityService;1");
551 if (accService) {
552 // XXX aaronl: ideally we would traverse the presshell chain
553 // Since there's no easy way to do that, we cheat and use
554 // the document hierarchy. GetAccessibleFor() is bad because
555 // it doesn't support our concept of multiple presshells per doc.
556 // It should be changed to use GetAccessibleInWeakShell()
557 accService->GetAccessibleFor(ownerNode, getter_AddRefs(mParent));
561 return mParent ? nsAccessible::GetParent(aParent) : NS_ERROR_FAILURE;
564 NS_IMETHODIMP nsDocAccessible::Init()
566 PutCacheEntry(gGlobalDocAccessibleCache, mDocument, this);
568 AddEventListeners();
570 nsCOMPtr<nsIAccessible> parentAccessible; // Ensure outer doc mParent accessible
571 GetParent(getter_AddRefs(parentAccessible));
573 return nsHyperTextAccessibleWrap::Init();
576 NS_IMETHODIMP nsDocAccessible::Shutdown()
578 if (!mWeakShell) {
579 return NS_OK; // Already shutdown
582 nsCOMPtr<nsIDocShellTreeItem> treeItem =
583 nsAccUtils::GetDocShellTreeItemFor(mDOMNode);
584 ShutdownChildDocuments(treeItem);
586 RemoveEventListeners();
588 mWeakShell = nsnull; // Avoid reentrancy
590 ClearCache(mAccessNodeCache);
592 nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocument;
593 mDocument = nsnull;
595 nsHyperTextAccessibleWrap::Shutdown();
597 if (mFireEventTimer) {
598 // Doc being shut down before events fired,
599 mFireEventTimer->Cancel();
600 mFireEventTimer = nsnull;
601 if (mEventsToFire.Count() > 0 ) {
602 mEventsToFire.Clear();
603 // Make sure we release the kung fu death grip which is always
604 // there when there are still events left to be fired
605 // If FlushPendingEvents() is in call stack,
606 // kung fu death grip will be released there.
607 if (!mInFlushPendingEvents)
608 NS_RELEASE_THIS();
612 // Remove from the cache after other parts of Shutdown(), so that Shutdown() procedures
613 // can find the doc or root accessible in the cache if they need it.
614 // We don't do this during ShutdownAccessibility() because that is already clearing the cache
615 if (!gIsShuttingDownApp)
616 gGlobalDocAccessibleCache.Remove(static_cast<void*>(kungFuDeathGripDoc));
618 return NS_OK;
621 void nsDocAccessible::ShutdownChildDocuments(nsIDocShellTreeItem *aStart)
623 nsCOMPtr<nsIDocShellTreeNode> treeNode(do_QueryInterface(aStart));
624 if (treeNode) {
625 PRInt32 subDocuments;
626 treeNode->GetChildCount(&subDocuments);
627 for (PRInt32 count = 0; count < subDocuments; count ++) {
628 nsCOMPtr<nsIDocShellTreeItem> treeItemChild;
629 treeNode->GetChildAt(count, getter_AddRefs(treeItemChild));
630 NS_ASSERTION(treeItemChild, "No tree item when there should be");
631 if (!treeItemChild) {
632 continue;
634 nsCOMPtr<nsIAccessibleDocument> docAccessible =
635 GetDocAccessibleFor(treeItemChild);
636 nsCOMPtr<nsPIAccessNode> accessNode = do_QueryInterface(docAccessible);
637 if (accessNode) {
638 accessNode->Shutdown();
644 nsIFrame* nsDocAccessible::GetFrame()
646 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(mWeakShell));
648 nsIFrame* root = nsnull;
649 if (shell)
650 root = shell->GetRootFrame();
652 return root;
655 void nsDocAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aRelativeFrame)
657 *aRelativeFrame = GetFrame();
659 nsIDocument *document = mDocument;
660 nsIDocument *parentDoc = nsnull;
662 while (document) {
663 nsIPresShell *presShell = document->GetPrimaryShell();
664 if (!presShell) {
665 return;
667 nsIViewManager* vm = presShell->GetViewManager();
668 if (!vm) {
669 return;
672 nsIScrollableView* scrollableView = nsnull;
673 vm->GetRootScrollableView(&scrollableView);
675 nsRect viewBounds(0, 0, 0, 0);
676 if (scrollableView) {
677 viewBounds = scrollableView->View()->GetBounds();
679 else {
680 nsIView *view;
681 vm->GetRootView(view);
682 if (view) {
683 viewBounds = view->GetBounds();
687 if (parentDoc) { // After first time thru loop
688 aBounds.IntersectRect(viewBounds, aBounds);
690 else { // First time through loop
691 aBounds = viewBounds;
694 document = parentDoc = document->GetParentDocument();
699 nsresult nsDocAccessible::AddEventListeners()
701 // 1) Set up scroll position listener
702 // 2) Check for editor and listen for changes to editor
704 nsCOMPtr<nsIPresShell> presShell(GetPresShell());
705 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
707 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
708 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(container));
709 NS_ENSURE_TRUE(docShellTreeItem, NS_ERROR_FAILURE);
711 // Make sure we're a content docshell
712 // We don't want to listen to chrome progress
713 PRInt32 itemType;
714 docShellTreeItem->GetItemType(&itemType);
716 PRBool isContent = (itemType == nsIDocShellTreeItem::typeContent);
718 if (isContent) {
719 // We're not an editor yet, but we might become one
720 nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
721 if (commandManager) {
722 commandManager->AddCommandObserver(this, "obs_documentCreated");
726 nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
727 docShellTreeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
728 if (rootTreeItem) {
729 nsCOMPtr<nsIAccessibleDocument> rootAccDoc =
730 GetDocAccessibleFor(rootTreeItem, PR_TRUE); // Ensure root accessible is created;
731 nsRefPtr<nsRootAccessible> rootAccessible = GetRootAccessible(); // Then get it as ref ptr
732 NS_ENSURE_TRUE(rootAccessible, NS_ERROR_FAILURE);
733 nsRefPtr<nsCaretAccessible> caretAccessible = rootAccessible->GetCaretAccessible();
734 if (caretAccessible) {
735 caretAccessible->AddDocSelectionListener(presShell);
739 // add document observer
740 mDocument->AddObserver(this);
741 return NS_OK;
744 nsresult nsDocAccessible::RemoveEventListeners()
746 // Remove listeners associated with content documents
747 // Remove scroll position listener
748 RemoveScrollListener();
750 // Remove document observer
751 mDocument->RemoveObserver(this);
753 if (mScrollWatchTimer) {
754 mScrollWatchTimer->Cancel();
755 mScrollWatchTimer = nsnull;
756 NS_RELEASE_THIS(); // Kung fu death grip
759 nsRefPtr<nsRootAccessible> rootAccessible(GetRootAccessible());
760 if (rootAccessible) {
761 nsRefPtr<nsCaretAccessible> caretAccessible = rootAccessible->GetCaretAccessible();
762 if (caretAccessible) {
763 // Don't use GetPresShell() which can call Shutdown() if it sees dead pres shell
764 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
765 caretAccessible->RemoveDocSelectionListener(presShell);
769 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
770 nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(container));
771 NS_ENSURE_TRUE(docShellTreeItem, NS_ERROR_FAILURE);
773 PRInt32 itemType;
774 docShellTreeItem->GetItemType(&itemType);
775 if (itemType == nsIDocShellTreeItem::typeContent) {
776 nsCOMPtr<nsICommandManager> commandManager = do_GetInterface(docShellTreeItem);
777 if (commandManager) {
778 commandManager->RemoveCommandObserver(this, "obs_documentCreated");
782 return NS_OK;
785 NS_IMETHODIMP nsDocAccessible::FireAnchorJumpEvent()
787 if (!mIsContentLoaded || !mDocument) {
788 return NS_OK;
790 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
791 nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
792 nsCAutoString theURL;
793 if (webNav) {
794 nsCOMPtr<nsIURI> pURI;
795 webNav->GetCurrentURI(getter_AddRefs(pURI));
796 if (pURI) {
797 pURI->GetSpec(theURL);
800 static nsCAutoString lastAnchor;
801 const char kHash = '#';
802 nsCAutoString currentAnchor;
803 PRInt32 hasPosition = theURL.FindChar(kHash);
804 if (hasPosition > 0 && hasPosition < (PRInt32)theURL.Length() - 1) {
805 mIsAnchor = PR_TRUE;
806 currentAnchor.Assign(Substring(theURL,
807 hasPosition+1,
808 (PRInt32)theURL.Length()-hasPosition-1));
811 if (currentAnchor.Equals(lastAnchor)) {
812 mIsAnchorJumped = PR_FALSE;
813 } else {
814 mIsAnchorJumped = PR_TRUE;
815 lastAnchor.Assign(currentAnchor);
818 return NS_OK;
821 NS_IMETHODIMP nsDocAccessible::FireDocLoadEvents(PRUint32 aEventType)
823 if (!mDocument || !mWeakShell) {
824 return NS_OK; // Document has been shut down
827 PRBool isFinished =
828 (aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE ||
829 aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED);
831 mIsContentLoaded = isFinished;
832 if (isFinished) {
833 if (mIsLoadCompleteFired)
834 return NS_OK;
835 mIsLoadCompleteFired = PR_TRUE;
838 nsCOMPtr<nsIDocShellTreeItem> treeItem =
839 nsAccUtils::GetDocShellTreeItemFor(mDOMNode);
840 if (!treeItem) {
841 return NS_OK;
843 nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
844 treeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
846 if (isFinished) {
847 // Need to wait until scrollable view is available
848 AddScrollListener();
849 nsCOMPtr<nsIAccessible> parent(nsAccessible::GetParent());
850 nsCOMPtr<nsPIAccessible> privateAccessible(do_QueryInterface(parent));
851 if (privateAccessible) {
852 // Make the parent forget about the old document as a child
853 privateAccessible->InvalidateChildren();
855 if (sameTypeRoot != treeItem) {
856 // Fire show/hide events to indicate frame/iframe content is new, rather than
857 // doc load event which causes screen readers to act is if entire page is reloaded
858 InvalidateCacheSubtree(nsnull, nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE);
860 // Fire STATE_CHANGE event for doc load finish if focus is in same doc tree
861 if (gLastFocusedNode) {
862 nsCOMPtr<nsIDocShellTreeItem> focusedTreeItem =
863 nsAccUtils::GetDocShellTreeItemFor(gLastFocusedNode);
864 if (focusedTreeItem) {
865 nsCOMPtr<nsIDocShellTreeItem> sameTypeRootOfFocus;
866 focusedTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRootOfFocus));
867 if (sameTypeRoot == sameTypeRootOfFocus) {
868 nsCOMPtr<nsIAccessibleStateChangeEvent> accEvent =
869 new nsAccStateChangeEvent(this, nsIAccessibleStates::STATE_BUSY, PR_FALSE, PR_FALSE);
870 FireAccessibleEvent(accEvent);
871 FireAnchorJumpEvent();
877 if (sameTypeRoot == treeItem) {
878 // Not a frame or iframe
879 if (!isFinished) {
880 // Fire state change event to set STATE_BUSY when document is loading. For
881 // example, Window-Eyes expects to get it.
882 nsCOMPtr<nsIAccessibleStateChangeEvent> accEvent =
883 new nsAccStateChangeEvent(this, nsIAccessibleStates::STATE_BUSY,
884 PR_FALSE, PR_TRUE);
885 FireAccessibleEvent(accEvent);
888 nsAccUtils::FireAccEvent(aEventType, this);
890 return NS_OK;
893 void nsDocAccessible::ScrollTimerCallback(nsITimer *aTimer, void *aClosure)
895 nsDocAccessible *docAcc = reinterpret_cast<nsDocAccessible*>(aClosure);
897 if (docAcc && docAcc->mScrollPositionChangedTicks &&
898 ++docAcc->mScrollPositionChangedTicks > 2) {
899 // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
900 // We only want to fire accessibilty scroll event when scrolling stops or pauses
901 // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
902 // That indicates a pause in scrolling, so we fire the accessibilty scroll event
903 nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
905 docAcc->mScrollPositionChangedTicks = 0;
906 if (docAcc->mScrollWatchTimer) {
907 docAcc->mScrollWatchTimer->Cancel();
908 docAcc->mScrollWatchTimer = nsnull;
909 NS_RELEASE(docAcc); // Release kung fu death grip
914 void nsDocAccessible::AddScrollListener()
916 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
918 nsIViewManager* vm = nsnull;
919 if (presShell)
920 vm = presShell->GetViewManager();
922 nsIScrollableView* scrollableView = nsnull;
923 if (vm)
924 vm->GetRootScrollableView(&scrollableView);
926 if (scrollableView)
927 scrollableView->AddScrollPositionListener(this);
930 void nsDocAccessible::RemoveScrollListener()
932 nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
934 nsIViewManager* vm = nsnull;
935 if (presShell)
936 vm = presShell->GetViewManager();
938 nsIScrollableView* scrollableView = nsnull;
939 if (vm)
940 vm->GetRootScrollableView(&scrollableView);
942 if (scrollableView)
943 scrollableView->RemoveScrollPositionListener(this);
946 NS_IMETHODIMP nsDocAccessible::ScrollPositionWillChange(nsIScrollableView *aView, nscoord aX, nscoord aY)
948 return NS_OK;
951 NS_IMETHODIMP nsDocAccessible::ScrollPositionDidChange(nsIScrollableView *aScrollableView, nscoord aX, nscoord aY)
953 // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
954 // then the ::Notify() method will fire the accessibility event for scroll position changes
955 const PRUint32 kScrollPosCheckWait = 50;
956 if (mScrollWatchTimer) {
957 mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
959 else {
960 mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
961 if (mScrollWatchTimer) {
962 NS_ADDREF_THIS(); // Kung fu death grip
963 mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
964 kScrollPosCheckWait,
965 nsITimer::TYPE_REPEATING_SLACK);
968 mScrollPositionChangedTicks = 1;
969 return NS_OK;
972 NS_IMETHODIMP nsDocAccessible::Observe(nsISupports *aSubject, const char *aTopic,
973 const PRUnichar *aData)
975 if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
976 // State editable will now be set, readonly is now clear
977 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
978 new nsAccStateChangeEvent(this, nsIAccessibleStates::EXT_STATE_EDITABLE,
979 PR_TRUE, PR_TRUE);
980 FireAccessibleEvent(event);
983 return NS_OK;
986 ///////////////////////////////////////////////////////////////////////
987 // nsIDocumentObserver
989 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(nsDocAccessible)
990 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(nsDocAccessible)
991 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(nsDocAccessible)
993 void
994 nsDocAccessible::AttributeChanged(nsIDocument *aDocument, nsIContent* aContent,
995 PRInt32 aNameSpaceID, nsIAtom* aAttribute,
996 PRInt32 aModType, PRUint32 aStateMask)
998 AttributeChangedImpl(aContent, aNameSpaceID, aAttribute);
1000 // If it was the focused node, cache the new state
1001 nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(aContent);
1002 if (targetNode == gLastFocusedNode) {
1003 nsCOMPtr<nsIAccessible> focusedAccessible;
1004 GetAccService()->GetAccessibleFor(targetNode, getter_AddRefs(focusedAccessible));
1005 if (focusedAccessible) {
1006 gLastFocusedAccessiblesState = State(focusedAccessible);
1012 void
1013 nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID, nsIAtom* aAttribute)
1015 // Fire accessible event after short timer, because we need to wait for
1016 // DOM attribute & resulting layout to actually change. Otherwise,
1017 // assistive technology will retrieve the wrong state/value/selection info.
1019 // XXX todo
1020 // We still need to handle special HTML cases here
1021 // For example, if an <img>'s usemap attribute is modified
1022 // Otherwise it may just be a state change, for example an object changing
1023 // its visibility
1025 nsCOMPtr<nsISupports> container = mDocument->GetContainer();
1026 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
1027 if (!docShell) {
1028 return;
1031 PRUint32 busyFlags;
1032 docShell->GetBusyFlags(&busyFlags);
1033 if (busyFlags) {
1034 return; // Still loading, ignore setting of initial attributes
1037 nsCOMPtr<nsIPresShell> shell = GetPresShell();
1038 if (!shell) {
1039 return; // Document has been shut down
1042 nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(aContent));
1043 NS_ASSERTION(targetNode, "No node for attr modified");
1044 if (!targetNode || !IsNodeRelevant(targetNode)) {
1045 return;
1048 // Since we're in synchronous code, we can store whether the current attribute
1049 // change is from user input or not. If the attribute change causes an asynchronous
1050 // layout change, that event can use the last known user input state
1051 nsAccEvent::PrepareForEvent(targetNode);
1053 // Universal boolean properties that don't require a role.
1054 if (aAttribute == nsAccessibilityAtoms::disabled ||
1055 aAttribute == nsAccessibilityAtoms::aria_disabled) {
1056 // Fire the state change whether disabled attribute is
1057 // set for XUL, HTML or ARIA namespace.
1058 // Checking the namespace would not seem to gain us anything, because
1059 // disabled really is going to mean the same thing in any namespace.
1060 // We use the attribute instead of the disabled state bit because
1061 // ARIA's aria-disabled does not affect the disabled state bit
1062 nsCOMPtr<nsIAccessibleStateChangeEvent> enabledChangeEvent =
1063 new nsAccStateChangeEvent(targetNode,
1064 nsIAccessibleStates::EXT_STATE_ENABLED,
1065 PR_TRUE);
1066 FireDelayedAccessibleEvent(enabledChangeEvent);
1067 nsCOMPtr<nsIAccessibleStateChangeEvent> sensitiveChangeEvent =
1068 new nsAccStateChangeEvent(targetNode,
1069 nsIAccessibleStates::EXT_STATE_SENSITIVE,
1070 PR_TRUE);
1071 FireDelayedAccessibleEvent(sensitiveChangeEvent);
1072 return;
1075 // Check for namespaced ARIA attribute
1076 if (aNameSpaceID == kNameSpaceID_None) {
1077 // Check for hyphenated aria-foo property?
1078 const char* attributeName;
1079 aAttribute->GetUTF8String(&attributeName);
1080 if (!PL_strncmp("aria-", attributeName, 5)) {
1081 ARIAAttributeChanged(aContent, aAttribute);
1085 if (aAttribute == nsAccessibilityAtoms::role ||
1086 aAttribute == nsAccessibilityAtoms::href ||
1087 aAttribute == nsAccessibilityAtoms::onclick ||
1088 aAttribute == nsAccessibilityAtoms::aria_droppable) {
1089 // Not worth the expense to ensure which namespace these are in
1090 // It doesn't kill use to recreate the accessible even if the attribute was used
1091 // in the wrong namespace or an element that doesn't support it
1092 InvalidateCacheSubtree(aContent, nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE);
1093 return;
1096 if (aAttribute == nsAccessibilityAtoms::alt ||
1097 aAttribute == nsAccessibilityAtoms::title) {
1098 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
1099 targetNode);
1100 return;
1103 if (aAttribute == nsAccessibilityAtoms::selected ||
1104 aAttribute == nsAccessibilityAtoms::aria_selected) {
1105 // ARIA or XUL selection
1106 nsCOMPtr<nsIAccessible> multiSelect = GetMultiSelectFor(targetNode);
1107 // Multi selects use selection_add and selection_remove
1108 // Single select widgets just mirror event_selection for
1109 // whatever gets event_focus, which is done in
1110 // nsRootAccessible::FireAccessibleFocusEvent()
1111 // So right here we make sure only to deal with multi selects
1112 if (multiSelect) {
1113 // Need to find the right event to use here, SELECTION_WITHIN would
1114 // seem right but we had started using it for something else
1115 nsCOMPtr<nsIAccessNode> multiSelectAccessNode =
1116 do_QueryInterface(multiSelect);
1117 nsCOMPtr<nsIDOMNode> multiSelectDOMNode;
1118 multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode));
1119 NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!");
1120 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
1121 multiSelectDOMNode,
1122 nsAccEvent::eAllowDupes);
1124 static nsIContent::AttrValuesArray strings[] =
1125 {&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull};
1126 if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
1127 strings, eCaseMatters) >= 0) {
1128 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
1129 targetNode);
1130 return;
1133 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
1134 targetNode);
1138 if (aAttribute == nsAccessibilityAtoms::contenteditable) {
1139 nsCOMPtr<nsIAccessibleStateChangeEvent> editableChangeEvent =
1140 new nsAccStateChangeEvent(targetNode,
1141 nsIAccessibleStates::EXT_STATE_EDITABLE,
1142 PR_TRUE);
1143 FireDelayedAccessibleEvent(editableChangeEvent);
1144 return;
1148 void
1149 nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
1151 nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(aContent));
1152 if (!targetNode)
1153 return;
1155 if (aAttribute == nsAccessibilityAtoms::aria_required) {
1156 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1157 new nsAccStateChangeEvent(targetNode,
1158 nsIAccessibleStates::STATE_REQUIRED,
1159 PR_FALSE);
1160 FireDelayedAccessibleEvent(event);
1161 return;
1164 if (aAttribute == nsAccessibilityAtoms::aria_invalid) {
1165 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1166 new nsAccStateChangeEvent(targetNode,
1167 nsIAccessibleStates::STATE_INVALID,
1168 PR_FALSE);
1169 FireDelayedAccessibleEvent(event);
1170 return;
1173 if (aAttribute == nsAccessibilityAtoms::aria_activedescendant) {
1174 // The activedescendant universal property redirects accessible focus events
1175 // to the element with the id that activedescendant points to
1176 nsCOMPtr<nsIDOMNode> currentFocus = GetCurrentFocus();
1177 if (SameCOMIdentity(GetRoleContent(currentFocus), targetNode)) {
1178 nsRefPtr<nsRootAccessible> rootAcc = GetRootAccessible();
1179 if (rootAcc)
1180 rootAcc->FireAccessibleFocusEvent(nsnull, currentFocus, nsnull, PR_TRUE);
1182 return;
1185 if (!aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) {
1186 // We don't care about these other ARIA attribute changes unless there is
1187 // an ARIA role set for the element
1188 // XXX: we should check the role map to see if the changed property is
1189 // relevant for that particular role.
1190 return;
1193 // The following ARIA attributes only take affect when dynamic content role is present
1194 if (aAttribute == nsAccessibilityAtoms::aria_checked ||
1195 aAttribute == nsAccessibilityAtoms::aria_pressed) {
1196 const PRUint32 kState = (aAttribute == nsAccessibilityAtoms::aria_checked) ?
1197 nsIAccessibleStates::STATE_CHECKED :
1198 nsIAccessibleStates::STATE_PRESSED;
1199 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1200 new nsAccStateChangeEvent(targetNode, kState, PR_FALSE);
1201 FireDelayedAccessibleEvent(event);
1202 if (targetNode == gLastFocusedNode) {
1203 // State changes for MIXED state currently only supported for focused item, because
1204 // otherwise we would need access to the old attribute value in this listener.
1205 // This is because we don't know if the previous value of aria-checked or aria-pressed was "mixed"
1206 // without caching that info.
1207 nsCOMPtr<nsIAccessible> accessible;
1208 event->GetAccessible(getter_AddRefs(accessible));
1209 if (accessible) {
1210 PRBool wasMixed = (gLastFocusedAccessiblesState & nsIAccessibleStates::STATE_MIXED) != 0;
1211 PRBool isMixed = (State(accessible) & nsIAccessibleStates::STATE_MIXED) != 0;
1212 if (wasMixed != isMixed) {
1213 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1214 new nsAccStateChangeEvent(targetNode,
1215 nsIAccessibleStates::STATE_MIXED,
1216 PR_FALSE, isMixed);
1217 FireDelayedAccessibleEvent(event);
1221 return;
1224 if (aAttribute == nsAccessibilityAtoms::aria_expanded) {
1225 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1226 new nsAccStateChangeEvent(targetNode,
1227 nsIAccessibleStates::STATE_EXPANDED,
1228 PR_FALSE);
1229 FireDelayedAccessibleEvent(event);
1230 return;
1233 if (aAttribute == nsAccessibilityAtoms::aria_readonly) {
1234 nsCOMPtr<nsIAccessibleStateChangeEvent> event =
1235 new nsAccStateChangeEvent(targetNode,
1236 nsIAccessibleStates::STATE_READONLY,
1237 PR_FALSE);
1238 FireDelayedAccessibleEvent(event);
1239 return;
1242 if (aAttribute == nsAccessibilityAtoms::aria_valuenow) {
1243 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
1244 targetNode);
1245 return;
1248 if (aAttribute == nsAccessibilityAtoms::aria_multiselectable &&
1249 aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) {
1250 // This affects whether the accessible supports nsIAccessibleSelectable.
1251 // COM says we cannot change what interfaces are supported on-the-fly,
1252 // so invalidate this object. A new one will be created on demand.
1253 InvalidateCacheSubtree(aContent, nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE);
1257 void nsDocAccessible::ContentAppended(nsIDocument *aDocument,
1258 nsIContent* aContainer,
1259 PRInt32 aNewIndexInContainer)
1261 if ((!mIsContentLoaded || !mDocument) && mAccessNodeCache.Count() <= 1) {
1262 // See comments in nsDocAccessible::InvalidateCacheSubtree
1263 InvalidateChildren();
1264 return;
1267 PRUint32 childCount = aContainer->GetChildCount();
1268 for (PRUint32 index = aNewIndexInContainer; index < childCount; index ++) {
1269 nsCOMPtr<nsIContent> child(aContainer->GetChildAt(index));
1270 // InvalidateCacheSubtree will not fire the EVENT_SHOW for the new node
1271 // unless an accessible can be created for the passed in node, which it
1272 // can't do unless the node is visible. The right thing happens there so
1273 // no need for an extra visibility check here.
1274 InvalidateCacheSubtree(child, nsIAccessibleEvent::EVENT_DOM_CREATE);
1278 void nsDocAccessible::ContentStatesChanged(nsIDocument* aDocument,
1279 nsIContent* aContent1,
1280 nsIContent* aContent2,
1281 PRInt32 aStateMask)
1283 if (0 == (aStateMask & NS_EVENT_STATE_CHECKED)) {
1284 return;
1287 nsHTMLSelectOptionAccessible::SelectionChangedIfOption(aContent1);
1288 nsHTMLSelectOptionAccessible::SelectionChangedIfOption(aContent2);
1291 void nsDocAccessible::CharacterDataWillChange(nsIDocument *aDocument,
1292 nsIContent* aContent,
1293 CharacterDataChangeInfo* aInfo)
1295 FireTextChangeEventForText(aContent, aInfo, PR_FALSE);
1298 void nsDocAccessible::CharacterDataChanged(nsIDocument *aDocument,
1299 nsIContent* aContent,
1300 CharacterDataChangeInfo* aInfo)
1302 FireTextChangeEventForText(aContent, aInfo, PR_TRUE);
1305 void
1306 nsDocAccessible::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
1307 nsIContent* aChild, PRInt32 aIndexInContainer)
1309 // InvalidateCacheSubtree will not fire the EVENT_SHOW for the new node
1310 // unless an accessible can be created for the passed in node, which it
1311 // can't do unless the node is visible. The right thing happens there so
1312 // no need for an extra visibility check here.
1313 InvalidateCacheSubtree(aChild, nsIAccessibleEvent::EVENT_DOM_CREATE);
1316 void
1317 nsDocAccessible::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer,
1318 nsIContent* aChild, PRInt32 aIndexInContainer)
1320 // Invalidate the subtree of the removed element.
1321 // InvalidateCacheSubtree(aChild, nsIAccessibleEvent::EVENT_DOM_DESTROY);
1322 // This is no longer needed, we get our notifications directly from content
1323 // *before* the frame for the content is destroyed, or any other side effects occur.
1324 // That allows us to correctly calculate the TEXT_REMOVED event if there is one.
1327 void
1328 nsDocAccessible::ParentChainChanged(nsIContent *aContent)
1332 void
1333 nsDocAccessible::FireValueChangeForTextFields(nsIAccessible *aPossibleTextFieldAccessible)
1335 if (Role(aPossibleTextFieldAccessible) != nsIAccessibleRole::ROLE_ENTRY)
1336 return;
1338 // Dependent value change event for text changes in textfields
1339 nsCOMPtr<nsIAccessibleEvent> valueChangeEvent =
1340 new nsAccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aPossibleTextFieldAccessible,
1341 PR_FALSE, nsAccEvent::eRemoveDupes);
1342 FireDelayedAccessibleEvent(valueChangeEvent );
1345 void
1346 nsDocAccessible::FireTextChangeEventForText(nsIContent *aContent,
1347 CharacterDataChangeInfo* aInfo,
1348 PRBool aIsInserted)
1350 if (!mIsContentLoaded || !mDocument) {
1351 return;
1354 nsCOMPtr<nsIDOMNode> node(do_QueryInterface(aContent));
1355 if (!node)
1356 return;
1358 nsCOMPtr<nsIAccessible> accessible;
1359 nsresult rv = GetAccessibleInParentChain(node, PR_TRUE, getter_AddRefs(accessible));
1360 if (NS_FAILED(rv) || !accessible)
1361 return;
1363 nsRefPtr<nsHyperTextAccessible> textAccessible;
1364 rv = accessible->QueryInterface(NS_GET_IID(nsHyperTextAccessible),
1365 getter_AddRefs(textAccessible));
1366 if (NS_FAILED(rv) || !textAccessible)
1367 return;
1369 PRInt32 start = aInfo->mChangeStart;
1371 PRInt32 offset = 0;
1372 rv = textAccessible->DOMPointToHypertextOffset(node, start, &offset);
1373 if (NS_FAILED(rv))
1374 return;
1376 PRInt32 length = aIsInserted ?
1377 aInfo->mReplaceLength: // text has been added
1378 aInfo->mChangeEnd - start; // text has been removed
1380 if (length > 0) {
1381 nsCOMPtr<nsIPresShell> shell(do_QueryReferent(mWeakShell));
1382 if (!shell)
1383 return;
1385 PRUint32 renderedStartOffset, renderedEndOffset;
1386 nsIFrame* frame = shell->GetPrimaryFrameFor(aContent);
1387 if (!frame)
1388 return;
1390 rv = textAccessible->ContentToRenderedOffset(frame, start,
1391 &renderedStartOffset);
1392 if (NS_FAILED(rv))
1393 return;
1395 rv = textAccessible->ContentToRenderedOffset(frame, start + length,
1396 &renderedEndOffset);
1397 if (NS_FAILED(rv))
1398 return;
1400 nsCOMPtr<nsIAccessibleTextChangeEvent> event =
1401 new nsAccTextChangeEvent(accessible, offset,
1402 renderedEndOffset - renderedStartOffset,
1403 aIsInserted, PR_FALSE);
1404 textAccessible->FireAccessibleEvent(event);
1406 FireValueChangeForTextFields(accessible);
1410 already_AddRefed<nsIAccessibleTextChangeEvent>
1411 nsDocAccessible::CreateTextChangeEventForNode(nsIAccessible *aContainerAccessible,
1412 nsIDOMNode *aChangeNode,
1413 nsIAccessible *aAccessibleForChangeNode,
1414 PRBool aIsInserting,
1415 PRBool aIsAsynch)
1417 nsRefPtr<nsHyperTextAccessible> textAccessible;
1418 aContainerAccessible->QueryInterface(NS_GET_IID(nsHyperTextAccessible),
1419 getter_AddRefs(textAccessible));
1420 if (!textAccessible) {
1421 return nsnull;
1424 PRInt32 offset;
1425 PRInt32 length = 0;
1426 nsCOMPtr<nsIAccessible> changeAccessible;
1427 nsresult rv = textAccessible->DOMPointToHypertextOffset(aChangeNode, -1, &offset,
1428 getter_AddRefs(changeAccessible));
1429 NS_ENSURE_SUCCESS(rv, nsnull);
1431 if (!aAccessibleForChangeNode) {
1432 // A span-level object or something else without an accessible is being removed, where
1433 // it has no accessible but it has descendant content which is aggregated as text
1434 // into the parent hypertext.
1435 // In this case, accessibleToBeRemoved may just be the first
1436 // accessible that is removed, which affects the text in the hypertext container
1437 if (!changeAccessible) {
1438 return nsnull; // No descendant content that represents any text in the hypertext parent
1440 nsCOMPtr<nsIAccessible> child = changeAccessible;
1441 while (PR_TRUE) {
1442 nsCOMPtr<nsIAccessNode> childAccessNode =
1443 do_QueryInterface(changeAccessible);
1444 nsCOMPtr<nsIDOMNode> childNode;
1445 childAccessNode->GetDOMNode(getter_AddRefs(childNode));
1446 if (!nsAccUtils::IsAncestorOf(aChangeNode, childNode)) {
1447 break; // We only want accessibles with DOM nodes as children of this node
1449 length += TextLength(child);
1450 child->GetNextSibling(getter_AddRefs(changeAccessible));
1451 if (!changeAccessible) {
1452 break;
1454 child.swap(changeAccessible);
1457 else {
1458 NS_ASSERTION(!changeAccessible || changeAccessible == aAccessibleForChangeNode,
1459 "Hypertext is reporting a different accessible for this node");
1460 length = TextLength(aAccessibleForChangeNode);
1461 if (Role(aAccessibleForChangeNode) == nsIAccessibleRole::ROLE_WHITESPACE) { // newline
1462 // Don't fire event for the first html:br in an editor.
1463 nsCOMPtr<nsIEditor> editor;
1464 textAccessible->GetAssociatedEditor(getter_AddRefs(editor));
1465 if (editor) {
1466 PRBool isEmpty = PR_FALSE;
1467 editor->GetDocumentIsEmpty(&isEmpty);
1468 if (isEmpty) {
1469 return nsnull;
1475 if (length <= 0) {
1476 return nsnull;
1479 nsIAccessibleTextChangeEvent *event =
1480 new nsAccTextChangeEvent(aContainerAccessible, offset, length, aIsInserting, aIsAsynch);
1481 NS_IF_ADDREF(event);
1483 return event;
1486 nsresult nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent,
1487 nsIDOMNode *aDOMNode,
1488 nsAccEvent::EEventRule aAllowDupes,
1489 PRBool aIsAsynch)
1491 nsCOMPtr<nsIAccessibleEvent> event =
1492 new nsAccEvent(aEvent, aDOMNode, aIsAsynch, aAllowDupes);
1493 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
1495 return FireDelayedAccessibleEvent(event);
1498 nsresult
1499 nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent)
1501 NS_ENSURE_TRUE(aEvent, NS_ERROR_FAILURE);
1503 if (!mFireEventTimer) {
1504 // Do not yet have a timer going for firing another event.
1505 mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1");
1506 NS_ENSURE_TRUE(mFireEventTimer, NS_ERROR_OUT_OF_MEMORY);
1509 mEventsToFire.AppendObject(aEvent);
1510 if (mEventsToFire.Count() == 1) {
1511 // This is be the first delayed event in queue, start timer
1512 // so that event gets fired via FlushEventsCallback
1513 NS_ADDREF_THIS(); // Kung fu death grip to prevent crash in callback
1514 mFireEventTimer->InitWithFuncCallback(FlushEventsCallback,
1515 static_cast<nsPIAccessibleDocument*>(this),
1516 0, nsITimer::TYPE_ONE_SHOT);
1519 return NS_OK;
1522 NS_IMETHODIMP nsDocAccessible::FlushPendingEvents()
1524 mInFlushPendingEvents = PR_TRUE;
1525 PRUint32 length = mEventsToFire.Count();
1526 NS_ASSERTION(length, "How did we get here without events to fire?");
1527 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
1528 if (!presShell)
1529 length = 0; // The doc is now shut down, don't fire events in it anymore
1530 else
1531 nsAccEvent::ApplyEventRules(mEventsToFire);
1533 for (PRUint32 index = 0; index < length; index ++) {
1534 nsCOMPtr<nsIAccessibleEvent> accessibleEvent(
1535 do_QueryInterface(mEventsToFire[index]));
1537 if (nsAccEvent::EventRule(accessibleEvent) == nsAccEvent::eDoNotEmit)
1538 continue;
1540 nsCOMPtr<nsIAccessible> accessible;
1541 accessibleEvent->GetAccessible(getter_AddRefs(accessible));
1542 nsCOMPtr<nsIDOMNode> domNode;
1543 accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
1544 PRUint32 eventType = nsAccEvent::EventType(accessibleEvent);
1545 PRBool isFromUserInput = nsAccEvent::IsFromUserInput(accessibleEvent);
1547 if (domNode == gLastFocusedNode &&
1548 (eventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
1549 eventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW)) {
1550 // If frame type didn't change for this event, then we don't actually need to invalidate
1551 // However, we only keep track of the old frame type for the focus, where it's very
1552 // important not to destroy and recreate the accessible for minor style changes,
1553 // such as a:focus { overflow: scroll; }
1554 nsCOMPtr<nsIContent> focusContent(do_QueryInterface(domNode));
1555 if (focusContent) {
1556 nsIFrame *focusFrame = presShell->GetRealPrimaryFrameFor(focusContent);
1557 nsIAtom *newFrameType =
1558 (focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ?
1559 focusFrame->GetType() : nsnull;
1561 if (newFrameType == gLastFocusedFrameType) {
1562 // Don't need to invalidate this current accessible, but can
1563 // just invalidate the children instead
1564 FireShowHideEvents(domNode, PR_TRUE, eventType, PR_FALSE, isFromUserInput);
1565 continue;
1567 gLastFocusedFrameType = newFrameType;
1571 if (eventType == nsIAccessibleEvent::EVENT_DOM_CREATE ||
1572 eventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW) {
1573 nsCOMPtr<nsIAccessible> containerAccessible;
1574 if (eventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW) {
1575 if (accessible) {
1576 // For asynch show, delayed invalidatation of parent's children
1577 accessible->GetParent(getter_AddRefs(containerAccessible));
1578 nsCOMPtr<nsPIAccessible> privateContainerAccessible =
1579 do_QueryInterface(containerAccessible);
1580 if (privateContainerAccessible)
1581 privateContainerAccessible->InvalidateChildren();
1583 // Some show events in the subtree may have been removed to
1584 // avoid firing redundant events. But, we still need to make sure any
1585 // accessibles parenting those shown nodes lose their child references.
1586 InvalidateChildrenInSubtree(domNode);
1589 // Also fire text changes if the node being created could affect the text in an nsIAccessibleText parent.
1590 // When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters
1591 // At this point we now have the frame and accessible for this node if there is one. That is why we
1592 // wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate
1593 // the offset, length and text for the text change.
1594 if (domNode && domNode != mDOMNode) {
1595 if (!containerAccessible) {
1596 GetAccessibleInParentChain(domNode, PR_TRUE,
1597 getter_AddRefs(containerAccessible));
1598 if (!containerAccessible)
1599 containerAccessible = this;
1602 nsCOMPtr<nsIAccessibleTextChangeEvent> textChangeEvent =
1603 CreateTextChangeEventForNode(containerAccessible, domNode, accessible, PR_TRUE, PR_TRUE);
1604 if (textChangeEvent) {
1605 nsAccEvent::PrepareForEvent(textChangeEvent, isFromUserInput);
1606 // XXX Queue them up and merge the text change events
1607 // XXX We need a way to ignore SplitNode and JoinNode() when they
1608 // do not affect the text within the hypertext
1609 FireAccessibleEvent(textChangeEvent);
1613 // Fire show/create events for this node or first accessible descendants of it
1614 FireShowHideEvents(domNode, PR_FALSE, eventType, PR_FALSE, isFromUserInput);
1615 continue;
1618 if (accessible) {
1619 if (eventType == nsIAccessibleEvent::EVENT_INTERNAL_LOAD) {
1620 nsCOMPtr<nsPIAccessibleDocument> docAccessible =
1621 do_QueryInterface(accessible);
1622 NS_ASSERTION(docAccessible, "No doc accessible for doc load event");
1623 if (docAccessible) {
1624 docAccessible->FireDocLoadEvents(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
1627 else if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) {
1628 nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryInterface(accessible);
1629 PRInt32 caretOffset;
1630 if (accessibleText && NS_SUCCEEDED(accessibleText->GetCaretOffset(&caretOffset))) {
1631 #ifdef DEBUG_A11Y
1632 PRUnichar chAtOffset;
1633 accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset);
1634 printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset);
1635 #endif
1636 #ifdef DEBUG_CARET
1637 // Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the
1638 // line-number object attribute on it
1639 nsCOMPtr<nsIAccessible> accForFocus;
1640 GetAccService()->GetAccessibleFor(gLastFocusedNode, getter_AddRefs(accForFocus));
1641 nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_ALERT, accForFocus);
1642 #endif
1643 nsCOMPtr<nsIAccessibleCaretMoveEvent> caretMoveEvent =
1644 new nsAccCaretMoveEvent(accessible, caretOffset);
1645 if (!caretMoveEvent)
1646 break; // Out of memory, break out to release kung fu death grip
1648 FireAccessibleEvent(caretMoveEvent);
1650 PRInt32 selectionCount;
1651 accessibleText->GetSelectionCount(&selectionCount);
1652 if (selectionCount) { // There's a selection so fire selection change as well
1653 nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
1654 accessible, PR_TRUE);
1658 else {
1659 // The input state was previously stored with the nsIAccessibleEvent,
1660 // so use that state now when firing the event
1661 nsAccEvent::PrepareForEvent(accessibleEvent);
1662 FireAccessibleEvent(accessibleEvent);
1663 // Post event processing
1664 if (eventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
1665 eventType == nsIAccessibleEvent::EVENT_DOM_DESTROY) {
1666 // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in this subtree
1667 nsCOMPtr<nsIDOMNode> hidingNode;
1668 accessibleEvent->GetDOMNode(getter_AddRefs(hidingNode));
1669 if (hidingNode) {
1670 RefreshNodes(hidingNode); // Will this bite us with asynch events
1676 mEventsToFire.Clear(); // Clear out array
1677 NS_RELEASE_THIS(); // Release kung fu death grip
1679 // After a flood of events, reset so that user input flag is off
1680 nsAccEvent::ResetLastInputState();
1682 mInFlushPendingEvents = PR_FALSE;
1683 return NS_OK;
1686 void nsDocAccessible::FlushEventsCallback(nsITimer *aTimer, void *aClosure)
1688 nsPIAccessibleDocument *accessibleDoc = static_cast<nsPIAccessibleDocument*>(aClosure);
1689 NS_ASSERTION(accessibleDoc, "How did we get here without an accessible document?");
1690 if (accessibleDoc) {
1691 // A lot of crashes were happening here, so now we're reffing the doc
1692 // now until the events are flushed
1693 accessibleDoc->FlushPendingEvents();
1697 void nsDocAccessible::InvalidateChildrenInSubtree(nsIDOMNode *aStartNode)
1699 nsCOMPtr<nsIAccessNode> accessNode;
1700 GetCachedAccessNode(aStartNode, getter_AddRefs(accessNode));
1701 nsCOMPtr<nsPIAccessible> accessible(do_QueryInterface(accessNode));
1702 if (accessible)
1703 accessible->InvalidateChildren();
1705 // Invalidate accessible children in the DOM subtree
1706 nsCOMPtr<nsINode> node = do_QueryInterface(aStartNode);
1707 PRInt32 index, numChildren = node->GetChildCount();
1708 for (index = 0; index < numChildren; index ++) {
1709 nsCOMPtr<nsIDOMNode> childNode = do_QueryInterface(node->GetChildAt(index));
1710 if (childNode)
1711 InvalidateChildrenInSubtree(childNode);
1715 void nsDocAccessible::RefreshNodes(nsIDOMNode *aStartNode)
1717 if (mAccessNodeCache.Count() <= 1) {
1718 return; // All we have is a doc accessible. There is nothing to invalidate, quit early
1721 nsCOMPtr<nsIAccessNode> accessNode;
1722 GetCachedAccessNode(aStartNode, getter_AddRefs(accessNode));
1724 // Shut down accessible subtree, which may have been created for
1725 // anonymous content subtree
1726 nsCOMPtr<nsIAccessible> accessible(do_QueryInterface(accessNode));
1727 if (accessible) {
1728 // Fire menupopup end if a menu goes away
1729 PRUint32 role = Role(accessible);
1730 if (role == nsIAccessibleRole::ROLE_MENUPOPUP) {
1731 nsCOMPtr<nsIDOMNode> domNode;
1732 accessNode->GetDOMNode(getter_AddRefs(domNode));
1733 nsCOMPtr<nsIDOMXULPopupElement> popup(do_QueryInterface(domNode));
1734 if (!popup) {
1735 // Popup elements already fire these via DOMMenuInactive
1736 // handling in nsRootAccessible::HandleEvent
1737 nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
1738 accessible);
1741 nsCOMPtr<nsPIAccessible> privateAccessible = do_QueryInterface(accessible);
1742 NS_ASSERTION(privateAccessible, "No nsPIAccessible for nsIAccessible");
1744 nsCOMPtr<nsIAccessible> childAccessible;
1745 // we only need to shutdown the accessibles here if one of them has been created
1746 privateAccessible->GetCachedFirstChild(getter_AddRefs(childAccessible));
1747 if (childAccessible) {
1748 nsCOMPtr<nsIArray> children;
1749 // use GetChildren() to fetch children at one time, instead of using
1750 // GetNextSibling(), because after we shutdown the first child,
1751 // mNextSibling will be set null.
1752 accessible->GetChildren(getter_AddRefs(children));
1753 PRUint32 childCount =0;
1754 if (children)
1755 children->GetLength(&childCount);
1756 nsCOMPtr<nsIDOMNode> possibleAnonNode;
1757 for (PRUint32 index = 0; index < childCount; index++) {
1758 nsCOMPtr<nsIAccessNode> childAccessNode;
1759 children->QueryElementAt(index, NS_GET_IID(nsIAccessNode),
1760 getter_AddRefs(childAccessNode));
1761 childAccessNode->GetDOMNode(getter_AddRefs(possibleAnonNode));
1762 nsCOMPtr<nsIContent> iterContent = do_QueryInterface(possibleAnonNode);
1763 if (iterContent && (iterContent->IsNativeAnonymous() ||
1764 iterContent->GetBindingParent())) {
1765 // GetBindingParent() check is a perf win -- make sure we don't
1766 // shut down the same subtree twice since we'll reach non-anon content via
1767 // DOM traversal later in this method
1768 RefreshNodes(possibleAnonNode);
1774 // Shutdown ordinary content subtree as well -- there may be
1775 // access node children which are not full accessible objects
1776 nsCOMPtr<nsIDOMNode> nextNode, iterNode;
1777 aStartNode->GetFirstChild(getter_AddRefs(nextNode));
1778 while (nextNode) {
1779 nextNode.swap(iterNode);
1780 RefreshNodes(iterNode);
1781 iterNode->GetNextSibling(getter_AddRefs(nextNode));
1784 if (!accessNode)
1785 return;
1787 if (accessNode == this) {
1788 // Don't shutdown our doc object -- this may just be from the finished loading.
1789 // We will completely shut it down when the pagehide event is received
1790 // However, we must invalidate the doc accessible's children in order to be sure
1791 // all pointers to them are correct
1792 InvalidateChildren();
1793 return;
1796 // Shut down the actual accessible or access node
1797 void *uniqueID;
1798 accessNode->GetUniqueID(&uniqueID);
1799 nsCOMPtr<nsPIAccessNode> privateAccessNode(do_QueryInterface(accessNode));
1800 privateAccessNode->Shutdown();
1802 // Remove from hash table as well
1803 mAccessNodeCache.Remove(uniqueID);
1806 NS_IMETHODIMP nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
1807 PRUint32 aChangeEventType)
1809 PRBool isHiding =
1810 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
1811 aChangeEventType == nsIAccessibleEvent::EVENT_DOM_DESTROY;
1813 PRBool isShowing =
1814 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW ||
1815 aChangeEventType == nsIAccessibleEvent::EVENT_DOM_CREATE;
1817 PRBool isChanging =
1818 aChangeEventType == nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE ||
1819 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SIGNIFICANT_CHANGE;
1821 NS_ASSERTION(isChanging || isHiding || isShowing,
1822 "Incorrect aChangeEventType passed in");
1824 PRBool isAsynch =
1825 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
1826 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW ||
1827 aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SIGNIFICANT_CHANGE;
1829 // Invalidate cache subtree
1830 // We have to check for accessibles for each dom node by traversing DOM tree
1831 // instead of just the accessible tree, although that would be faster
1832 // Otherwise we might miss the nsAccessNode's that are not nsAccessible's.
1834 NS_ENSURE_TRUE(mDOMNode, NS_ERROR_FAILURE);
1835 nsCOMPtr<nsIDOMNode> childNode = aChild ? do_QueryInterface(aChild) : mDOMNode;
1837 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
1838 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
1840 if (!mIsContentLoaded) {
1841 // Still loading document
1842 if (mAccessNodeCache.Count() <= 1) {
1843 // Still loading and no accessibles has yet been created other than this
1844 // doc accessible. In this case we optimize
1845 // by not firing SHOW/HIDE/REORDER events for every document mutation
1846 // caused by page load, since AT is not going to want to grab the
1847 // document and listen to these changes until after the page is first loaded
1848 // Leave early, and ensure mAccChildCount stays uninitialized instead of 0,
1849 // which it is if anyone asks for its children right now.
1850 return InvalidateChildren();
1852 nsIEventStateManager *esm = presShell->GetPresContext()->EventStateManager();
1853 NS_ENSURE_TRUE(esm, NS_ERROR_FAILURE);
1854 if (!esm->IsHandlingUserInputExternal()) {
1855 // Changes during page load, but not caused by user input
1856 // Just invalidate accessible hierarchy and return,
1857 // otherwise the page load time slows down way too much
1858 nsCOMPtr<nsIAccessible> containerAccessible;
1859 GetAccessibleInParentChain(childNode, PR_FALSE, getter_AddRefs(containerAccessible));
1860 if (!containerAccessible) {
1861 containerAccessible = this;
1863 nsCOMPtr<nsPIAccessible> privateContainer = do_QueryInterface(containerAccessible);
1864 return privateContainer->InvalidateChildren();
1866 // else: user input, so we must fall through and for full handling,
1867 // e.g. fire the mutation events. Note: user input could cause DOM_CREATE
1868 // during page load if user typed into an input field or contentEditable area
1871 // Update last change state information
1872 nsCOMPtr<nsIAccessNode> childAccessNode;
1873 GetCachedAccessNode(childNode, getter_AddRefs(childAccessNode));
1874 nsCOMPtr<nsIAccessible> childAccessible = do_QueryInterface(childAccessNode);
1876 #ifdef DEBUG_A11Y
1877 nsAutoString localName;
1878 childNode->GetLocalName(localName);
1879 const char *hasAccessible = childAccessible ? " (acc)" : "";
1880 if (aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE) {
1881 printf("[Hide %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1883 else if (aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW) {
1884 printf("[Show %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1886 else if (aChangeEventType == nsIAccessibleEvent::EVENT_ASYNCH_SIGNIFICANT_CHANGE) {
1887 printf("[Layout change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1889 else if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_CREATE) {
1890 printf("[Create %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1892 else if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_DESTROY) {
1893 printf("[Destroy %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1895 else if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE) {
1896 printf("[Type change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
1898 #endif
1900 nsCOMPtr<nsIAccessible> containerAccessible;
1901 GetAccessibleInParentChain(childNode, PR_TRUE, getter_AddRefs(containerAccessible));
1902 if (!containerAccessible) {
1903 containerAccessible = this;
1906 if (!isShowing) {
1907 // Fire EVENT_ASYNCH_HIDE or EVENT_DOM_DESTROY
1908 if (isHiding) {
1909 nsCOMPtr<nsIContent> content(do_QueryInterface(childNode));
1910 if (content) {
1911 nsIFrame *frame = presShell->GetPrimaryFrameFor(content);
1912 if (frame) {
1913 nsIFrame *frameParent = frame->GetParent();
1914 if (!frameParent || !frameParent->GetStyleVisibility()->IsVisible()) {
1915 // Ancestor already hidden or being hidden at the same time:
1916 // don't process redundant hide event
1917 // This often happens when visibility is cleared for node,
1918 // which hides an entire subtree -- we get notified for each
1919 // node in the subtree and need to collate the hide events ourselves.
1920 return NS_OK;
1926 PRUint32 removalEventType = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_HIDE :
1927 nsIAccessibleEvent::EVENT_DOM_DESTROY;
1929 // Fire an event if the accessible existed for node being hidden, otherwise
1930 // for the first line accessible descendants. Fire before the accessible(s) away.
1931 nsresult rv = FireShowHideEvents(childNode, PR_FALSE, removalEventType, PR_TRUE, PR_FALSE);
1932 NS_ENSURE_SUCCESS(rv, rv);
1933 if (childNode != mDOMNode) { // Fire text change unless the node being removed is for this doc
1934 // When a node is hidden or removed, the text in an ancestor hyper text will lose characters
1935 // At this point we still have the frame and accessible for this node if there was one
1936 // XXX Collate events when a range is deleted
1937 // XXX We need a way to ignore SplitNode and JoinNode() when they
1938 // do not affect the text within the hypertext
1939 nsCOMPtr<nsIAccessibleTextChangeEvent> textChangeEvent =
1940 CreateTextChangeEventForNode(containerAccessible, childNode, childAccessible,
1941 PR_FALSE, isAsynch);
1942 if (textChangeEvent) {
1943 FireAccessibleEvent(textChangeEvent);
1948 // We need to get an accessible for the mutation event's container node
1949 // If there is no accessible for that node, we need to keep moving up the parent
1950 // chain so there is some accessible.
1951 // We will use this accessible to fire the accessible mutation event.
1952 // We're guaranteed success, because we will eventually end up at the doc accessible,
1953 // and there is always one of those.
1955 if (aChild && !isHiding) {
1956 if (!isAsynch) {
1957 // DOM already updated with new objects -- invalidate parent's children now
1958 // For asynch we must wait until layout updates before we invalidate the children
1959 nsCOMPtr<nsPIAccessible> privateContainerAccessible =
1960 do_QueryInterface(containerAccessible);
1961 if (privateContainerAccessible) {
1962 privateContainerAccessible->InvalidateChildren();
1965 // Fire EVENT_SHOW, EVENT_MENUPOPUP_START for newly visible content.
1966 // Fire after a short timer, because we want to make sure the view has been
1967 // updated to make this accessible content visible. If we don't wait,
1968 // the assistive technology may receive the event and then retrieve
1969 // nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object.
1970 PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW :
1971 nsIAccessibleEvent::EVENT_DOM_CREATE;
1972 FireDelayedToolkitEvent(additionEvent, childNode,
1973 nsAccEvent::eCoalesceFromSameSubtree,
1974 isAsynch);
1976 // Check to see change occured in an ARIA menu, and fire
1977 // an EVENT_MENUPOPUP_START if it did.
1978 nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(childNode);
1979 if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) {
1980 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
1981 childNode, nsAccEvent::eRemoveDupes,
1982 isAsynch);
1985 // Check to see if change occured inside an alert, and fire an EVENT_ALERT if it did
1986 nsIContent *ancestor = aChild;
1987 while (PR_TRUE) {
1988 if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) {
1989 nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor));
1990 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode,
1991 nsAccEvent::eRemoveDupes, isAsynch);
1992 break;
1994 ancestor = ancestor->GetParent();
1995 nsCOMPtr<nsIDOMNode> ancestorNode = do_QueryInterface(ancestor);
1996 if (!ancestorNode) {
1997 break;
1999 roleMapEntry = nsAccUtils::GetRoleMapEntry(ancestorNode);
2003 FireValueChangeForTextFields(containerAccessible);
2005 if (childAccessible) {
2006 // Fire an event so the MSAA clients know the children have changed. Also
2007 // the event is used internally by MSAA part.
2008 nsCOMPtr<nsIAccessibleEvent> reorderEvent =
2009 new nsAccEvent(nsIAccessibleEvent::EVENT_REORDER, containerAccessible,
2010 isAsynch, nsAccEvent::eCoalesceFromSameSubtree);
2011 NS_ENSURE_TRUE(reorderEvent, NS_ERROR_OUT_OF_MEMORY);
2012 FireDelayedAccessibleEvent(reorderEvent);
2015 return NS_OK;
2018 NS_IMETHODIMP
2019 nsDocAccessible::GetAccessibleInParentChain(nsIDOMNode *aNode,
2020 PRBool aCanCreate,
2021 nsIAccessible **aAccessible)
2023 // Find accessible in parent chain of DOM nodes, or return null
2024 *aAccessible = nsnull;
2025 nsCOMPtr<nsIDOMNode> currentNode(aNode), parentNode;
2026 nsCOMPtr<nsIAccessNode> accessNode;
2028 nsIAccessibilityService *accService = GetAccService();
2029 NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
2031 do {
2032 currentNode->GetParentNode(getter_AddRefs(parentNode));
2033 currentNode = parentNode;
2034 if (!currentNode) {
2035 NS_ADDREF_THIS();
2036 *aAccessible = this;
2037 break;
2040 nsCOMPtr<nsIDOMNode> relevantNode;
2041 if (NS_SUCCEEDED(accService->GetRelevantContentNodeFor(currentNode, getter_AddRefs(relevantNode))) && relevantNode) {
2042 currentNode = relevantNode;
2044 if (aCanCreate) {
2045 accService->GetAccessibleInWeakShell(currentNode, mWeakShell, aAccessible);
2047 else { // Only return cached accessibles, don't create anything
2048 nsCOMPtr<nsIAccessNode> accessNode;
2049 GetCachedAccessNode(currentNode, getter_AddRefs(accessNode)); // AddRefs
2050 if (accessNode) {
2051 CallQueryInterface(accessNode, aAccessible); // AddRefs
2054 } while (!*aAccessible);
2056 return NS_OK;
2059 nsresult
2060 nsDocAccessible::FireShowHideEvents(nsIDOMNode *aDOMNode, PRBool aAvoidOnThisNode, PRUint32 aEventType,
2061 PRBool aDelay, PRBool aForceIsFromUserInput)
2063 NS_ENSURE_ARG(aDOMNode);
2065 nsCOMPtr<nsIAccessible> accessible;
2066 if (!aAvoidOnThisNode) {
2067 if (aEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
2068 aEventType == nsIAccessibleEvent::EVENT_DOM_DESTROY) {
2069 // Don't allow creation for accessibles when nodes going away
2070 nsCOMPtr<nsIAccessNode> accessNode;
2071 GetCachedAccessNode(aDOMNode, getter_AddRefs(accessNode));
2072 accessible = do_QueryInterface(accessNode);
2073 } else {
2074 // Allow creation of new accessibles for show events
2075 GetAccService()->GetAttachedAccessibleFor(aDOMNode,
2076 getter_AddRefs(accessible));
2080 if (accessible) {
2081 // Found an accessible, so fire the show/hide on it and don't
2082 // look further into this subtree
2083 PRBool isAsynch = aEventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
2084 aEventType == nsIAccessibleEvent::EVENT_ASYNCH_SHOW;
2086 nsCOMPtr<nsIAccessibleEvent> event =
2087 new nsAccEvent(aEventType, accessible, isAsynch,
2088 nsAccEvent::eCoalesceFromSameSubtree);
2089 NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
2090 if (aForceIsFromUserInput) {
2091 nsAccEvent::PrepareForEvent(event, aForceIsFromUserInput);
2093 if (aDelay) {
2094 return FireDelayedAccessibleEvent(event);
2096 return FireAccessibleEvent(event);
2099 // Could not find accessible to show hide yet, so fire on any
2100 // accessible descendants in this subtree
2101 nsCOMPtr<nsINode> node(do_QueryInterface(aDOMNode));
2102 PRUint32 count = node->GetChildCount();
2103 for (PRUint32 index = 0; index < count; index++) {
2104 nsCOMPtr<nsIDOMNode> childNode = do_QueryInterface(node->GetChildAt(index));
2105 nsresult rv = FireShowHideEvents(childNode, PR_FALSE, aEventType,
2106 aDelay, aForceIsFromUserInput);
2107 NS_ENSURE_SUCCESS(rv, rv);
2110 return NS_OK;