Back to index

lightning-sunbird  0.9+nobinonly
nsAccessibleTreeWalker.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2003
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Original Author: Aaron Leventhal (aaronl@netscape.com)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsAccessibleTreeWalker.h"
00040 #include "nsAccessibilityAtoms.h"
00041 #include "nsAccessNode.h"
00042 #include "nsIServiceManager.h"
00043 #include "nsIContent.h"
00044 #include "nsIDOMXULElement.h"
00045 #include "nsIPresShell.h"
00046 #include "nsIFrame.h"
00047 #include "nsWeakReference.h"
00048 
00049 nsAccessibleTreeWalker::nsAccessibleTreeWalker(nsIWeakReference* aPresShell, nsIDOMNode* aNode, PRBool aWalkAnonContent): 
00050   mWeakShell(aPresShell), 
00051   mAccService(do_GetService("@mozilla.org/accessibilityService;1"))
00052 {
00053   mState.domNode = aNode;
00054   mState.prevState = nsnull;
00055   mState.siblingIndex = eSiblingsUninitialized;
00056   mState.siblingList = nsnull;
00057   mState.isHidden = false;
00058   mState.frame = nsnull;
00059 
00060   if (aWalkAnonContent) {
00061     nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
00062     if (presShell)
00063       mBindingManager = presShell->GetDocument()->BindingManager();
00064   }
00065   MOZ_COUNT_CTOR(nsAccessibleTreeWalker);
00066 }
00067 
00068 nsAccessibleTreeWalker::~nsAccessibleTreeWalker()
00069 {
00070   // Clear state stack from memory
00071   while (NS_SUCCEEDED(PopState()))
00072     /* do nothing */ ;
00073    MOZ_COUNT_DTOR(nsAccessibleTreeWalker);
00074 }
00075 
00076 // GetFullParentNode gets the parent node in the deep tree
00077 // This might not be the DOM parent in cases where <children/> was used in an XBL binding.
00078 // In that case, this returns the parent in the XBL'ized tree.
00079 
00080 NS_IMETHODIMP nsAccessibleTreeWalker::GetFullTreeParentNode(nsIDOMNode *aChildNode, nsIDOMNode **aParentNodeOut)
00081 {
00082   nsCOMPtr<nsIContent> childContent(do_QueryInterface(aChildNode));
00083   nsCOMPtr<nsIContent> bindingParentContent;
00084   nsCOMPtr<nsIDOMNode> parentNode;
00085 
00086   if (mState.prevState)
00087     parentNode = mState.prevState->domNode;
00088   else {
00089     if (mBindingManager) {
00090       mBindingManager->GetInsertionParent(childContent, getter_AddRefs(bindingParentContent));
00091       if (bindingParentContent) 
00092         parentNode = do_QueryInterface(bindingParentContent);
00093     }
00094 
00095     if (!parentNode) 
00096       aChildNode->GetParentNode(getter_AddRefs(parentNode));
00097   }
00098 
00099   if (parentNode) {
00100     *aParentNodeOut = parentNode;
00101     NS_ADDREF(*aParentNodeOut);
00102     return NS_OK;
00103   }
00104   return NS_ERROR_FAILURE;
00105 }
00106 
00107 void nsAccessibleTreeWalker::GetKids(nsIDOMNode *aParentNode)
00108 {
00109   nsCOMPtr<nsIContent> parentContent(do_QueryInterface(aParentNode));
00110   if (!parentContent || !parentContent->IsContentOfType(nsIContent::eHTML)) {
00111     mState.frame = nsnull;  // Don't walk frames in non-HTML content, just walk the DOM.
00112   }
00113 
00114   PushState();
00115   UpdateFrame(PR_TRUE);
00116 
00117   // Walk frames? UpdateFrame() sets this when it sees anonymous frames
00118   if (mState.siblingIndex == eSiblingsWalkFrames) {
00119     return;
00120   }
00121 
00122   // Walk anonymous content? Not currently used for HTML -- anonymous content there uses frame walking
00123   mState.siblingIndex = 0;   // Indicates our index into the sibling list
00124   if (parentContent) {
00125     if (mBindingManager && !parentContent->IsContentOfType(nsIContent::eHTML)) {
00126       // Walk anonymous content
00127       mBindingManager->GetXBLChildNodesFor(parentContent, getter_AddRefs(mState.siblingList)); // returns null if no anon nodes
00128     }
00129     if (!mState.siblingList) {
00130       // Walk normal DOM. Just use nsIContent -- it doesn't require 
00131       // the mallocs that GetChildNodes() needs
00132       //aParentNode->GetChildNodes(getter_AddRefs(mState.siblingList));
00133       mState.parentContent = parentContent;
00134       mState.domNode = do_QueryInterface(parentContent->GetChildAt(0 /* 0 == mState.siblingIndex */));
00135       return;
00136     }
00137   }
00138   else {
00139     // We're on document node, that's why we could not QI to nsIContent.
00140     // So, use nsIDOMNodeList method to walk content.
00141     aParentNode->GetChildNodes(getter_AddRefs(mState.siblingList));
00142     if (!mState.siblingList) {
00143       return;
00144     }
00145   }
00146 
00147   mState.siblingList->Item(0 /* 0 == mState.siblingIndex */, getter_AddRefs(mState.domNode));
00148 }
00149 
00150 NS_IMETHODIMP nsAccessibleTreeWalker::GetParent()
00151 {
00152   nsCOMPtr<nsIDOMNode> parent;
00153 
00154   while (NS_SUCCEEDED(GetFullTreeParentNode(mState.domNode, getter_AddRefs(parent)))) {
00155     if (NS_FAILED(PopState())) {
00156       mState.domNode = parent;
00157       GetAccessible();
00158     }
00159     if (mState.accessible)
00160       return NS_OK;
00161   }
00162 
00163   return NS_ERROR_FAILURE;
00164 }
00165 
00166 NS_IMETHODIMP nsAccessibleTreeWalker::PopState()
00167 {
00168   nsIFrame *frameParent = mState.frame? mState.frame->GetParent(): nsnull;
00169   if (mState.prevState) {
00170     WalkState *toBeDeleted = mState.prevState;
00171     mState = *mState.prevState; // deep copy
00172     mState.isHidden = PR_FALSE; // If we were in a child, the parent wasn't hidden
00173     if (!mState.frame) {
00174       mState.frame = frameParent;
00175     }
00176     delete toBeDeleted;
00177     return NS_OK;
00178   }
00179   ClearState();
00180   mState.frame = frameParent;
00181   mState.isHidden = PR_FALSE;
00182   return NS_ERROR_FAILURE;
00183 }
00184 
00185 void nsAccessibleTreeWalker::ClearState()
00186 {
00187   mState.siblingList = nsnull;
00188   mState.parentContent = nsnull;
00189   mState.accessible = nsnull;
00190   mState.domNode = nsnull;
00191   mState.siblingIndex = eSiblingsUninitialized;
00192 }
00193 
00194 NS_IMETHODIMP nsAccessibleTreeWalker::PushState()
00195 {
00196   // Duplicate mState and put right before end; reset mState; make mState the new end of the stack
00197   WalkState* nextToLastState= new WalkState();
00198   if (!nextToLastState)
00199     return NS_ERROR_OUT_OF_MEMORY;
00200   *nextToLastState = mState;  // Deep copy - copy contents of struct to new state that will be added to end of our stack
00201   ClearState();
00202   mState.prevState = nextToLastState;   // Link to previous state
00203   return NS_OK;
00204 }
00205 
00206 void nsAccessibleTreeWalker::GetNextDOMNode()
00207 {
00208   // Get next DOM node
00209   if (mState.parentContent) {
00210     mState.domNode = do_QueryInterface(mState.parentContent->GetChildAt(++mState.siblingIndex));
00211   }
00212   else if (mState.siblingIndex == eSiblingsWalkFrames) {
00213     if (mState.frame) {
00214       mState.domNode = do_QueryInterface(mState.frame->GetContent());
00215     } else {
00216       mState.domNode = nsnull;
00217     }
00218   }
00219   else { 
00220     mState.siblingList->Item(++mState.siblingIndex, getter_AddRefs(mState.domNode));
00221   }
00222 }
00223 
00224 NS_IMETHODIMP nsAccessibleTreeWalker::GetNextSibling()
00225 {
00226   // Make sure mState.prevState and mState.siblingIndex are initialized so we can walk forward
00227   NS_ASSERTION(mState.prevState && mState.siblingIndex != eSiblingsUninitialized,
00228                "Error - GetNextSibling() only works after a GetFirstChild(), so we must have a prevState.");
00229   mState.accessible = nsnull;
00230 
00231   while (PR_TRUE) {
00232     // Get next frame
00233     UpdateFrame(PR_FALSE);
00234     GetNextDOMNode();
00235 
00236     if (!mState.domNode) {  // Done with current siblings
00237       PopState();   // Use parent - go up in stack. Can always pop state because we have to start with a GetFirstChild().
00238       if (!mState.prevState) {
00239         mState.accessible = nsnull;
00240         break; // Back to original accessible that we did GetFirstChild() from
00241       }
00242     }
00243     else if ((mState.domNode != mState.prevState->domNode && GetAccessible()) || 
00244              NS_SUCCEEDED(GetFirstChild())) {
00245       return NS_OK; // if next is accessible, use it 
00246     }
00247   }
00248   return NS_ERROR_FAILURE;
00249 }
00250 
00251 NS_IMETHODIMP nsAccessibleTreeWalker::GetFirstChild()
00252 {
00253   mState.accessible = nsnull;
00254   if (mState.isHidden || !mState.domNode) {
00255     return NS_ERROR_FAILURE;
00256   }
00257 
00258   nsCOMPtr<nsIDOMNode> parent(mState.domNode);
00259   GetKids(parent); // Side effects change our state (mState)
00260 
00261   // Recursive loop: depth first search for first accessible child
00262   while (mState.domNode) {
00263     if ((mState.domNode != parent && GetAccessible()) || NS_SUCCEEDED(GetFirstChild()))
00264       return NS_OK;
00265     UpdateFrame(PR_FALSE);
00266     GetNextDOMNode();
00267   }
00268 
00269   PopState();  // Return to previous state
00270   return NS_ERROR_FAILURE;
00271 }
00272 
00273 void nsAccessibleTreeWalker::UpdateFrame(PRBool aTryFirstChild)
00274 {
00275   if (mState.frame) {
00276     mState.frame = aTryFirstChild? mState.frame->GetFirstChild(nsnull) : 
00277                                    mState.frame->GetNextSibling();
00278 #ifndef MOZ_ACCESSIBILITY_ATK
00279     if (mState.frame && mState.siblingIndex < 0 && 
00280         mState.frame->GetContent()->IsNativeAnonymous()) {
00281       mState.domNode = do_QueryInterface(mState.frame->GetContent());
00282       mState.siblingIndex = eSiblingsWalkFrames;
00283     }
00284 #endif
00285   }
00286 }
00287 
00292 PRBool nsAccessibleTreeWalker::GetAccessible()
00293 {
00294   if (!mAccService) {
00295     return false;
00296   }
00297   mState.accessible = nsnull;
00298   nsCOMPtr<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
00299 
00300   if (NS_SUCCEEDED(mAccService->GetAccessible(mState.domNode, presShell, mWeakShell, 
00301                                               &mState.frame, &mState.isHidden,
00302                                               getter_AddRefs(mState.accessible)))) {
00303     NS_ASSERTION(mState.accessible, "No accessible but no failure return code");
00304     return true;
00305   }
00306   return false;
00307 }
00308