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
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.
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"
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"
68 #include "nsUnicharUtils.h"
70 #include "nsIWebNavigation.h"
71 #include "nsIFocusController.h"
73 #include "nsIXULDocument.h"
76 //=============================//
78 //=============================//
80 PRUint32
nsDocAccessible::gLastFocusedAccessiblesState
= 0;
81 nsIAtom
*nsDocAccessible::gLastFocusedFrameType
= nsnull
;
83 //-----------------------------------------------------
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.
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
100 nsCOMPtr
<nsIPresShell
> shell(do_QueryReferent(mWeakShell
));
103 mDocument
= shell
->GetDocument();
106 nsIViewManager
* vm
= shell
->GetViewManager();
108 nsCOMPtr
<nsIWidget
> widget
;
109 vm
->GetWidget(getter_AddRefs(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
);
124 docShell
->GetBusyFlags(&busyFlags
);
125 if (busyFlags
== nsIDocShell::BUSY_FLAGS_NONE
) {
126 mIsContentLoaded
= PR_TRUE
;
131 //-----------------------------------------------------
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
)
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
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
));
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
) {
190 nsCOMPtr
<nsIXULDocument
> xulDoc(do_QueryInterface(mDocument
));
192 *aRole
= nsIAccessibleRole::ROLE_APPLICATION
;
194 *aRole
= nsIAccessibleRole::ROLE_DOCUMENT
;
197 *aRole
= nsIAccessibleRole::ROLE_DOCUMENT
;
201 else if (itemType
== nsIDocShellTreeItem::typeContent
) {
202 *aRole
= nsIAccessibleRole::ROLE_DOCUMENT
;
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
));
221 nsRoleMapEntry
*roleMapEntry
= nsAccUtils::GetRoleMapEntry(ownerNode
);
223 mRoleMapEntry
= roleMapEntry
; // Override
230 nsDocAccessible::GetDescription(nsAString
& aDescription
)
233 mParent
->GetDescription(aDescription
);
235 if (aDescription
.IsEmpty()) {
236 nsAutoString description
;
237 GetTextFromRelationID(nsAccessibilityAtoms::aria_describedby
, description
);
238 aDescription
= description
;
245 nsDocAccessible::GetState(PRUint32
*aState
, PRUint32
*aExtraState
)
247 // nsAccessible::GetState() always fail for document accessible.
248 nsAccessible::GetState(aState
, aExtraState
);
253 nsCOMPtr
<nsIXULDocument
> xulDoc(do_QueryInterface(mDocument
));
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.
260 *aState
|= nsIAccessibleStates::STATE_FOCUSABLE
;
261 if (gLastFocusedNode
== mDOMNode
) {
262 *aState
|= nsIAccessibleStates::STATE_FOCUSED
;
266 if (!mIsContentLoaded
) {
267 *aState
|= nsIAccessibleStates::STATE_BUSY
;
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
));
287 *aState
|= nsIAccessibleStates::STATE_READONLY
;
289 else if (aExtraState
) {
290 *aExtraState
|= nsIAccessibleStates::EXT_STATE_EDITABLE
;
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
);
312 nsDocAccessible::GetAttributes(nsIPersistentProperties
**aAttributes
)
314 nsAccessible::GetAttributes(aAttributes
);
316 mParent
->GetAttributes(aAttributes
); // Add parent attributes (override inner)
321 NS_IMETHODIMP
nsDocAccessible::GetFocusedChild(nsIAccessible
**aFocusedChild
)
323 if (!gLastFocusedNode
) {
324 *aFocusedChild
= nsnull
;
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
);
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());
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
)
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
;
376 nsCOMPtr
<nsIURI
> pURI
;
377 webNav
->GetCurrentURI(getter_AddRefs(pURI
));
379 pURI
->GetSpec(theURL
);
381 CopyUTF8toUTF16(theURL
, aURL
);
385 NS_IMETHODIMP
nsDocAccessible::GetTitle(nsAString
& aTitle
)
388 aTitle
= mDocument
->GetDocumentTitle();
392 return NS_ERROR_FAILURE
;
395 NS_IMETHODIMP
nsDocAccessible::GetMimeType(nsAString
& aMimeType
)
397 nsCOMPtr
<nsIDOMNSDocument
> domnsDocument(do_QueryInterface(mDocument
));
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
;
410 nsCOMPtr
<nsIXULDocument
> xulDoc(do_QueryInterface(mDocument
));
412 aDocType
.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
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
)
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
)
440 NS_IMETHODIMP
nsDocAccessible::GetWindow(nsIDOMWindow
**aDOMWin
)
444 return NS_ERROR_FAILURE
; // Accessible is Shutdown()
446 *aDOMWin
= mDocument
->GetWindow();
449 return NS_ERROR_FAILURE
; // No DOM Window
456 NS_IMETHODIMP
nsDocAccessible::GetDocument(nsIDOMDocument
**aDOMDoc
)
458 nsCOMPtr
<nsIDOMDocument
> domDoc(do_QueryInterface(mDocument
));
466 return NS_ERROR_FAILURE
;
469 NS_IMETHODIMP
nsDocAccessible::GetAssociatedEditor(nsIEditor
**aEditor
)
471 NS_ENSURE_ARG_POINTER(aEditor
);
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
));
483 return NS_OK
; // No editing session interface
485 nsCOMPtr
<nsIEditor
> editor
;
486 editingSession
->GetEditorForWindow(mDocument
->GetWindow(), getter_AddRefs(editor
));
491 editor
->GetIsDocumentEditable(&isEditable
);
493 NS_ADDREF(*aEditor
= editor
);
498 NS_IMETHODIMP
nsDocAccessible::GetCachedAccessNode(void *aUniqueID
, nsIAccessNode
**aAccessNode
)
500 GetCacheEntry(mAccessNodeCache
, aUniqueID
, aAccessNode
); // Addrefs for us
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
));
513 privateParent
->TestChildCache(accessible
);
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
));
530 nsCOMPtr
<nsPIAccessNode
> prAccessNode
= do_QueryInterface(accessNode
);
531 prAccessNode
->Shutdown();
534 PutCacheEntry(mAccessNodeCache
, aUniqueID
, aAccessNode
);
538 NS_IMETHODIMP
nsDocAccessible::GetParent(nsIAccessible
**aParent
)
540 // Hook up our new accessible with our parent
542 NS_ENSURE_TRUE(mDocument
, NS_ERROR_FAILURE
);
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
));
549 nsCOMPtr
<nsIAccessibilityService
> accService
=
550 do_GetService("@mozilla.org/accessibilityService;1");
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);
570 nsCOMPtr
<nsIAccessible
> parentAccessible
; // Ensure outer doc mParent accessible
571 GetParent(getter_AddRefs(parentAccessible
));
573 return nsHyperTextAccessibleWrap::Init();
576 NS_IMETHODIMP
nsDocAccessible::Shutdown()
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
;
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
)
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
));
621 void nsDocAccessible::ShutdownChildDocuments(nsIDocShellTreeItem
*aStart
)
623 nsCOMPtr
<nsIDocShellTreeNode
> treeNode(do_QueryInterface(aStart
));
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
) {
634 nsCOMPtr
<nsIAccessibleDocument
> docAccessible
=
635 GetDocAccessibleFor(treeItemChild
);
636 nsCOMPtr
<nsPIAccessNode
> accessNode
= do_QueryInterface(docAccessible
);
638 accessNode
->Shutdown();
644 nsIFrame
* nsDocAccessible::GetFrame()
646 nsCOMPtr
<nsIPresShell
> shell(do_QueryReferent(mWeakShell
));
648 nsIFrame
* root
= nsnull
;
650 root
= shell
->GetRootFrame();
655 void nsDocAccessible::GetBoundsRect(nsRect
& aBounds
, nsIFrame
** aRelativeFrame
)
657 *aRelativeFrame
= GetFrame();
659 nsIDocument
*document
= mDocument
;
660 nsIDocument
*parentDoc
= nsnull
;
663 nsIPresShell
*presShell
= document
->GetPrimaryShell();
667 nsIViewManager
* vm
= presShell
->GetViewManager();
672 nsIScrollableView
* scrollableView
= nsnull
;
673 vm
->GetRootScrollableView(&scrollableView
);
675 nsRect
viewBounds(0, 0, 0, 0);
676 if (scrollableView
) {
677 viewBounds
= scrollableView
->View()->GetBounds();
681 vm
->GetRootView(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
714 docShellTreeItem
->GetItemType(&itemType
);
716 PRBool isContent
= (itemType
== nsIDocShellTreeItem::typeContent
);
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
));
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);
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
);
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");
785 NS_IMETHODIMP
nsDocAccessible::FireAnchorJumpEvent()
787 if (!mIsContentLoaded
|| !mDocument
) {
790 nsCOMPtr
<nsISupports
> container
= mDocument
->GetContainer();
791 nsCOMPtr
<nsIWebNavigation
> webNav(do_GetInterface(container
));
792 nsCAutoString theURL
;
794 nsCOMPtr
<nsIURI
> pURI
;
795 webNav
->GetCurrentURI(getter_AddRefs(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) {
806 currentAnchor
.Assign(Substring(theURL
,
808 (PRInt32
)theURL
.Length()-hasPosition
-1));
811 if (currentAnchor
.Equals(lastAnchor
)) {
812 mIsAnchorJumped
= PR_FALSE
;
814 mIsAnchorJumped
= PR_TRUE
;
815 lastAnchor
.Assign(currentAnchor
);
821 NS_IMETHODIMP
nsDocAccessible::FireDocLoadEvents(PRUint32 aEventType
)
823 if (!mDocument
|| !mWeakShell
) {
824 return NS_OK
; // Document has been shut down
828 (aEventType
== nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
||
829 aEventType
== nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED
);
831 mIsContentLoaded
= isFinished
;
833 if (mIsLoadCompleteFired
)
835 mIsLoadCompleteFired
= PR_TRUE
;
838 nsCOMPtr
<nsIDocShellTreeItem
> treeItem
=
839 nsAccUtils::GetDocShellTreeItemFor(mDOMNode
);
843 nsCOMPtr
<nsIDocShellTreeItem
> sameTypeRoot
;
844 treeItem
->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot
));
847 // Need to wait until scrollable view is available
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
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
,
885 FireAccessibleEvent(accEvent
);
888 nsAccUtils::FireAccEvent(aEventType
, this);
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
;
920 vm
= presShell
->GetViewManager();
922 nsIScrollableView
* scrollableView
= nsnull
;
924 vm
->GetRootScrollableView(&scrollableView
);
927 scrollableView
->AddScrollPositionListener(this);
930 void nsDocAccessible::RemoveScrollListener()
932 nsCOMPtr
<nsIPresShell
> presShell(do_QueryReferent(mWeakShell
));
934 nsIViewManager
* vm
= nsnull
;
936 vm
= presShell
->GetViewManager();
938 nsIScrollableView
* scrollableView
= nsnull
;
940 vm
->GetRootScrollableView(&scrollableView
);
943 scrollableView
->RemoveScrollPositionListener(this);
946 NS_IMETHODIMP
nsDocAccessible::ScrollPositionWillChange(nsIScrollableView
*aView
, nscoord aX
, nscoord aY
)
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
960 mScrollWatchTimer
= do_CreateInstance("@mozilla.org/timer;1");
961 if (mScrollWatchTimer
) {
962 NS_ADDREF_THIS(); // Kung fu death grip
963 mScrollWatchTimer
->InitWithFuncCallback(ScrollTimerCallback
, this,
965 nsITimer::TYPE_REPEATING_SLACK
);
968 mScrollPositionChangedTicks
= 1;
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
,
980 FireAccessibleEvent(event
);
986 ///////////////////////////////////////////////////////////////////////
987 // nsIDocumentObserver
989 NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(nsDocAccessible
)
990 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(nsDocAccessible
)
991 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(nsDocAccessible
)
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
);
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.
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
1025 nsCOMPtr
<nsISupports
> container
= mDocument
->GetContainer();
1026 nsCOMPtr
<nsIDocShell
> docShell
= do_QueryInterface(container
);
1032 docShell
->GetBusyFlags(&busyFlags
);
1034 return; // Still loading, ignore setting of initial attributes
1037 nsCOMPtr
<nsIPresShell
> shell
= GetPresShell();
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
)) {
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
,
1066 FireDelayedAccessibleEvent(enabledChangeEvent
);
1067 nsCOMPtr
<nsIAccessibleStateChangeEvent
> sensitiveChangeEvent
=
1068 new nsAccStateChangeEvent(targetNode
,
1069 nsIAccessibleStates::EXT_STATE_SENSITIVE
,
1071 FireDelayedAccessibleEvent(sensitiveChangeEvent
);
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
);
1096 if (aAttribute
== nsAccessibilityAtoms::alt
||
1097 aAttribute
== nsAccessibilityAtoms::title
) {
1098 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE
,
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
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
,
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
,
1133 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD
,
1138 if (aAttribute
== nsAccessibilityAtoms::contenteditable
) {
1139 nsCOMPtr
<nsIAccessibleStateChangeEvent
> editableChangeEvent
=
1140 new nsAccStateChangeEvent(targetNode
,
1141 nsIAccessibleStates::EXT_STATE_EDITABLE
,
1143 FireDelayedAccessibleEvent(editableChangeEvent
);
1149 nsDocAccessible::ARIAAttributeChanged(nsIContent
* aContent
, nsIAtom
* aAttribute
)
1151 nsCOMPtr
<nsIDOMNode
> targetNode(do_QueryInterface(aContent
));
1155 if (aAttribute
== nsAccessibilityAtoms::aria_required
) {
1156 nsCOMPtr
<nsIAccessibleStateChangeEvent
> event
=
1157 new nsAccStateChangeEvent(targetNode
,
1158 nsIAccessibleStates::STATE_REQUIRED
,
1160 FireDelayedAccessibleEvent(event
);
1164 if (aAttribute
== nsAccessibilityAtoms::aria_invalid
) {
1165 nsCOMPtr
<nsIAccessibleStateChangeEvent
> event
=
1166 new nsAccStateChangeEvent(targetNode
,
1167 nsIAccessibleStates::STATE_INVALID
,
1169 FireDelayedAccessibleEvent(event
);
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();
1180 rootAcc
->FireAccessibleFocusEvent(nsnull
, currentFocus
, nsnull
, PR_TRUE
);
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.
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
));
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
,
1217 FireDelayedAccessibleEvent(event
);
1224 if (aAttribute
== nsAccessibilityAtoms::aria_expanded
) {
1225 nsCOMPtr
<nsIAccessibleStateChangeEvent
> event
=
1226 new nsAccStateChangeEvent(targetNode
,
1227 nsIAccessibleStates::STATE_EXPANDED
,
1229 FireDelayedAccessibleEvent(event
);
1233 if (aAttribute
== nsAccessibilityAtoms::aria_readonly
) {
1234 nsCOMPtr
<nsIAccessibleStateChangeEvent
> event
=
1235 new nsAccStateChangeEvent(targetNode
,
1236 nsIAccessibleStates::STATE_READONLY
,
1238 FireDelayedAccessibleEvent(event
);
1242 if (aAttribute
== nsAccessibilityAtoms::aria_valuenow
) {
1243 FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE
,
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();
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
,
1283 if (0 == (aStateMask
& NS_EVENT_STATE_CHECKED
)) {
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
);
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
);
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.
1328 nsDocAccessible::ParentChainChanged(nsIContent
*aContent
)
1333 nsDocAccessible::FireValueChangeForTextFields(nsIAccessible
*aPossibleTextFieldAccessible
)
1335 if (Role(aPossibleTextFieldAccessible
) != nsIAccessibleRole::ROLE_ENTRY
)
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
);
1346 nsDocAccessible::FireTextChangeEventForText(nsIContent
*aContent
,
1347 CharacterDataChangeInfo
* aInfo
,
1350 if (!mIsContentLoaded
|| !mDocument
) {
1354 nsCOMPtr
<nsIDOMNode
> node(do_QueryInterface(aContent
));
1358 nsCOMPtr
<nsIAccessible
> accessible
;
1359 nsresult rv
= GetAccessibleInParentChain(node
, PR_TRUE
, getter_AddRefs(accessible
));
1360 if (NS_FAILED(rv
) || !accessible
)
1363 nsRefPtr
<nsHyperTextAccessible
> textAccessible
;
1364 rv
= accessible
->QueryInterface(NS_GET_IID(nsHyperTextAccessible
),
1365 getter_AddRefs(textAccessible
));
1366 if (NS_FAILED(rv
) || !textAccessible
)
1369 PRInt32 start
= aInfo
->mChangeStart
;
1372 rv
= textAccessible
->DOMPointToHypertextOffset(node
, start
, &offset
);
1376 PRInt32 length
= aIsInserted
?
1377 aInfo
->mReplaceLength
: // text has been added
1378 aInfo
->mChangeEnd
- start
; // text has been removed
1381 nsCOMPtr
<nsIPresShell
> shell(do_QueryReferent(mWeakShell
));
1385 PRUint32 renderedStartOffset
, renderedEndOffset
;
1386 nsIFrame
* frame
= shell
->GetPrimaryFrameFor(aContent
);
1390 rv
= textAccessible
->ContentToRenderedOffset(frame
, start
,
1391 &renderedStartOffset
);
1395 rv
= textAccessible
->ContentToRenderedOffset(frame
, start
+ length
,
1396 &renderedEndOffset
);
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
,
1417 nsRefPtr
<nsHyperTextAccessible
> textAccessible
;
1418 aContainerAccessible
->QueryInterface(NS_GET_IID(nsHyperTextAccessible
),
1419 getter_AddRefs(textAccessible
));
1420 if (!textAccessible
) {
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
;
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
) {
1454 child
.swap(changeAccessible
);
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
));
1466 PRBool isEmpty
= PR_FALSE
;
1467 editor
->GetDocumentIsEmpty(&isEmpty
);
1479 nsIAccessibleTextChangeEvent
*event
=
1480 new nsAccTextChangeEvent(aContainerAccessible
, offset
, length
, aIsInserting
, aIsAsynch
);
1481 NS_IF_ADDREF(event
);
1486 nsresult
nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent
,
1487 nsIDOMNode
*aDOMNode
,
1488 nsAccEvent::EEventRule aAllowDupes
,
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
);
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
);
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();
1529 length
= 0; // The doc is now shut down, don't fire events in it anymore
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
)
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
));
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
);
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
) {
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
);
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
))) {
1632 PRUnichar chAtOffset
;
1633 accessibleText
->GetCharacterAtOffset(caretOffset
, &chAtOffset
);
1634 printf("\nCaret moved to %d with char %c", caretOffset
, chAtOffset
);
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
);
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
);
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
));
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
;
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
));
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
));
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
));
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
));
1735 // Popup elements already fire these via DOMMenuInactive
1736 // handling in nsRootAccessible::HandleEvent
1737 nsAccUtils::FireAccEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END
,
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;
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
));
1779 nextNode
.swap(iterNode
);
1780 RefreshNodes(iterNode
);
1781 iterNode
->GetNextSibling(getter_AddRefs(nextNode
));
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();
1796 // Shut down the actual accessible or access node
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
)
1810 aChangeEventType
== nsIAccessibleEvent::EVENT_ASYNCH_HIDE
||
1811 aChangeEventType
== nsIAccessibleEvent::EVENT_DOM_DESTROY
;
1814 aChangeEventType
== nsIAccessibleEvent::EVENT_ASYNCH_SHOW
||
1815 aChangeEventType
== nsIAccessibleEvent::EVENT_DOM_CREATE
;
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");
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
);
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
);
1900 nsCOMPtr
<nsIAccessible
> containerAccessible
;
1901 GetAccessibleInParentChain(childNode
, PR_TRUE
, getter_AddRefs(containerAccessible
));
1902 if (!containerAccessible
) {
1903 containerAccessible
= this;
1907 // Fire EVENT_ASYNCH_HIDE or EVENT_DOM_DESTROY
1909 nsCOMPtr
<nsIContent
> content(do_QueryInterface(childNode
));
1911 nsIFrame
*frame
= presShell
->GetPrimaryFrameFor(content
);
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.
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
) {
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
,
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
,
1985 // Check to see if change occured inside an alert, and fire an EVENT_ALERT if it did
1986 nsIContent
*ancestor
= aChild
;
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
);
1994 ancestor
= ancestor
->GetParent();
1995 nsCOMPtr
<nsIDOMNode
> ancestorNode
= do_QueryInterface(ancestor
);
1996 if (!ancestorNode
) {
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
);
2019 nsDocAccessible::GetAccessibleInParentChain(nsIDOMNode
*aNode
,
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
);
2032 currentNode
->GetParentNode(getter_AddRefs(parentNode
));
2033 currentNode
= parentNode
;
2036 *aAccessible
= this;
2040 nsCOMPtr
<nsIDOMNode
> relevantNode
;
2041 if (NS_SUCCEEDED(accService
->GetRelevantContentNodeFor(currentNode
, getter_AddRefs(relevantNode
))) && relevantNode
) {
2042 currentNode
= relevantNode
;
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
2051 CallQueryInterface(accessNode
, aAccessible
); // AddRefs
2054 } while (!*aAccessible
);
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
);
2074 // Allow creation of new accessibles for show events
2075 GetAccService()->GetAttachedAccessibleFor(aDOMNode
,
2076 getter_AddRefs(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
);
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
);