/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2003 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Original Author: Aaron Leventhal (aaronl@netscape.com) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAccessNode.h" #include "nsIAccessible.h" #include "nsAccessibilityAtoms.h" #include "nsHashtable.h" #include "nsIAccessibilityService.h" #include "nsIAccessibleDocument.h" #include "nsPIAccessibleDocument.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocument.h" #include "nsIDOMCSSStyleDeclaration.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMNSHTMLElement.h" #include "nsIDOMViewCSS.h" #include "nsIDOMWindow.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIFrame.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsPresContext.h" #include "nsIPresShell.h" #include "nsIServiceManager.h" #include "nsIStringBundle.h" #include "nsITimer.h" /* For documentation of the accessibility architecture, * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html */ nsIStringBundle *nsAccessNode::gStringBundle = 0; nsIStringBundle *nsAccessNode::gKeyStringBundle = 0; nsITimer *nsAccessNode::gDoCommandTimer = 0; nsIDOMNode *nsAccessNode::gLastFocusedNode = 0; PRBool nsAccessNode::gIsAccessibilityActive = PR_FALSE; PRBool nsAccessNode::gIsCacheDisabled = PR_FALSE; nsInterfaceHashtable nsAccessNode::gGlobalDocAccessibleCache; /* * Class nsAccessNode */ //----------------------------------------------------- // construction //----------------------------------------------------- NS_IMPL_ISUPPORTS2(nsAccessNode, nsIAccessNode, nsPIAccessNode) nsAccessNode::nsAccessNode(nsIDOMNode *aNode, nsIWeakReference* aShell): mDOMNode(aNode), mWeakShell(aShell), mRefCnt(0) { #ifdef DEBUG mIsInitialized = PR_FALSE; #endif } //----------------------------------------------------- // destruction //----------------------------------------------------- nsAccessNode::~nsAccessNode() { if (mWeakShell) { Shutdown(); // Otherwise virtual Shutdown() methods could get fired twice } } NS_IMETHODIMP nsAccessNode::Init() { // We have to put this here, instead of constructor, otherwise // we don't have the virtual GetUniqueID() method for the hash key. // We need that for accessibles that don't have DOM nodes NS_ASSERTION(!mIsInitialized, "Initialized twice!"); nsCOMPtr docAccessible(GetDocAccessible()); if (!docAccessible) { // No doc accessible yet for this node's document. // There was probably an accessible event fired before the // current document was ever asked for by the assistive technology. // Create a doc accessible so we can cache this node nsCOMPtr presShell(do_QueryReferent(mWeakShell)); if (presShell) { nsCOMPtr docNode(do_QueryInterface(presShell->GetDocument())); if (docNode) { nsCOMPtr accService = do_GetService("@mozilla.org/accessibilityService;1"); NS_ASSERTION(accService, "No accessibility service"); if (accService) { nsCOMPtr accessible; accService->GetAccessibleInShell(docNode, presShell, getter_AddRefs(accessible)); docAccessible = do_QueryInterface(accessible); } } } NS_ASSERTION(docAccessible, "Cannot cache new nsAccessNode"); if (!docAccessible) { return NS_ERROR_FAILURE; } } void* uniqueID; GetUniqueID(&uniqueID); nsCOMPtr privateDocAccessible = do_QueryInterface(docAccessible); NS_ASSERTION(privateDocAccessible, "No private docaccessible for docaccessible"); privateDocAccessible->CacheAccessNode(uniqueID, this); #ifdef DEBUG mIsInitialized = PR_TRUE; #endif return NS_OK; } NS_IMETHODIMP nsAccessNode::Shutdown() { mDOMNode = nsnull; mWeakShell = nsnull; return NS_OK; } NS_IMETHODIMP nsAccessNode::GetUniqueID(void **aUniqueID) { *aUniqueID = NS_STATIC_CAST(void*, mDOMNode); return NS_OK; } NS_IMETHODIMP nsAccessNode::GetOwnerWindow(void **aWindow) { nsCOMPtr docAccessible(GetDocAccessible()); NS_ASSERTION(docAccessible, "No root accessible pointer back, Init() not called."); return docAccessible->GetWindowHandle(aWindow); } void nsAccessNode::InitXPAccessibility() { if (gIsAccessibilityActive) { return; } nsCOMPtr stringBundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID); if (stringBundleService) { // Static variables are released in ShutdownAllXPAccessibility(); stringBundleService->CreateBundle(ACCESSIBLE_BUNDLE_URL, &gStringBundle); stringBundleService->CreateBundle(PLATFORM_KEYS_BUNDLE_URL, &gKeyStringBundle); } nsAccessibilityAtoms::AddRefAtoms(); gGlobalDocAccessibleCache.Init(4); nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) { prefBranch->GetBoolPref("accessibility.disablecache", &gIsCacheDisabled); } gIsAccessibilityActive = PR_TRUE; } void nsAccessNode::ShutdownXPAccessibility() { // Called by nsAccessibilityService::Shutdown() // which happens when xpcom is shutting down // at exit of program if (!gIsAccessibilityActive) { return; } NS_IF_RELEASE(gStringBundle); NS_IF_RELEASE(gKeyStringBundle); NS_IF_RELEASE(gDoCommandTimer); NS_IF_RELEASE(gLastFocusedNode); ClearCache(gGlobalDocAccessibleCache); gIsAccessibilityActive = PR_FALSE; } already_AddRefed nsAccessNode::GetPresShell() { nsIPresShell *presShell = nsnull; if (mWeakShell) CallQueryReferent(mWeakShell.get(), &presShell); if (!presShell) { if (mWeakShell) { // If our pres shell has died, but we're still holding onto // a weak reference, our accessibles are no longer relevant // and should be shut down Shutdown(); } return nsnull; } return presShell; } nsPresContext* nsAccessNode::GetPresContext() { nsCOMPtr presShell(GetPresShell()); if (!presShell) { return nsnull; } return presShell->GetPresContext(); } already_AddRefed nsAccessNode::GetDocAccessible() { return GetDocAccessibleFor(mWeakShell); // Addref'd } nsIFrame* nsAccessNode::GetFrame() { nsCOMPtr shell(do_QueryReferent(mWeakShell)); if (!shell) return nsnull; nsIFrame* frame; nsCOMPtr content(do_QueryInterface(mDOMNode)); while (content) { shell->GetPrimaryFrameFor(content, &frame); if (frame) { return frame; } nsCOMPtr tempContent = content->GetParent(); content = tempContent; } return nsnull; } NS_IMETHODIMP nsAccessNode::GetDOMNode(nsIDOMNode **aNode) { NS_IF_ADDREF(*aNode = mDOMNode); return NS_OK; } NS_IMETHODIMP nsAccessNode::GetNumChildren(PRInt32 *aNumChildren) { nsCOMPtr content(do_QueryInterface(mDOMNode)); if (!content) { *aNumChildren = 0; return NS_ERROR_NULL_POINTER; } *aNumChildren = content->GetChildCount(); return NS_OK; } NS_IMETHODIMP nsAccessNode::GetAccessibleDocument(nsIAccessibleDocument **aDocAccessible) { *aDocAccessible = GetDocAccessibleFor(mWeakShell).get(); return NS_OK; } NS_IMETHODIMP nsAccessNode::GetInnerHTML(nsAString& aInnerHTML) { aInnerHTML.Truncate(); nsCOMPtr domNSElement(do_QueryInterface(mDOMNode)); NS_ENSURE_TRUE(domNSElement, NS_ERROR_NULL_POINTER); return domNSElement->GetInnerHTML(aInnerHTML); } nsresult nsAccessNode::MakeAccessNode(nsIDOMNode *aNode, nsIAccessNode **aAccessNode) { nsCOMPtr accService = do_GetService("@mozilla.org/accessibilityService;1"); NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE); nsCOMPtr accessNode; accService->GetCachedAccessNode(aNode, mWeakShell, getter_AddRefs(accessNode)); if (!accessNode) { nsCOMPtr accessible; accService->GetAccessibleInWeakShell(aNode, mWeakShell, getter_AddRefs(accessible)); accessNode = do_QueryInterface(accessible); } if (accessNode) { NS_ADDREF(*aAccessNode = accessNode); return NS_OK; } nsAccessNode *newAccessNode = new nsAccessNode(aNode, mWeakShell); if (!newAccessNode) { return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(*aAccessNode = newAccessNode); newAccessNode->Init(); return NS_OK; } NS_IMETHODIMP nsAccessNode::GetFirstChildNode(nsIAccessNode **aAccessNode) { NS_ENSURE_TRUE(mDOMNode, NS_ERROR_NULL_POINTER); nsCOMPtr domNode; mDOMNode->GetFirstChild(getter_AddRefs(domNode)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetLastChildNode(nsIAccessNode **aAccessNode) { NS_ENSURE_TRUE(mDOMNode, NS_ERROR_NULL_POINTER); nsCOMPtr domNode; mDOMNode->GetLastChild(getter_AddRefs(domNode)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetParentNode(nsIAccessNode **aAccessNode) { NS_ENSURE_TRUE(mDOMNode, NS_ERROR_NULL_POINTER); nsCOMPtr domNode; mDOMNode->GetParentNode(getter_AddRefs(domNode)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetPreviousSiblingNode(nsIAccessNode **aAccessNode) { NS_ENSURE_TRUE(mDOMNode, NS_ERROR_NULL_POINTER); nsCOMPtr domNode; mDOMNode->GetPreviousSibling(getter_AddRefs(domNode)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetNextSiblingNode(nsIAccessNode **aAccessNode) { NS_ENSURE_TRUE(mDOMNode, NS_ERROR_NULL_POINTER); nsCOMPtr domNode; mDOMNode->GetNextSibling(getter_AddRefs(domNode)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetChildNodeAt(PRInt32 aChildNum, nsIAccessNode **aAccessNode) { nsCOMPtr content(do_QueryInterface(mDOMNode)); NS_ENSURE_TRUE(content, NS_ERROR_NULL_POINTER); nsCOMPtr domNode = do_QueryInterface(content->GetChildAt(aChildNum)); NS_ENSURE_TRUE(domNode, NS_ERROR_NULL_POINTER); return MakeAccessNode(domNode, aAccessNode); } NS_IMETHODIMP nsAccessNode::GetComputedStyleValue(const nsAString& aPseudoElt, const nsAString& aPropertyName, nsAString& aValue) { nsCOMPtr domElement(do_QueryInterface(mDOMNode)); nsPresContext *presContext = GetPresContext(); NS_ENSURE_TRUE(domElement && presContext, NS_ERROR_FAILURE); nsCOMPtr container = presContext->GetContainer(); nsCOMPtr domWin(do_GetInterface(container)); nsCOMPtr viewCSS(do_QueryInterface(domWin)); NS_ENSURE_TRUE(viewCSS, NS_ERROR_FAILURE); nsCOMPtr styleDecl; viewCSS->GetComputedStyle(domElement, aPseudoElt, getter_AddRefs(styleDecl)); NS_ENSURE_TRUE(styleDecl, NS_ERROR_FAILURE); return styleDecl->GetPropertyValue(aPropertyName, aValue); } /***************** Hashtable of nsIAccessNode's *****************/ already_AddRefed nsAccessNode::GetDocAccessibleFor(nsIWeakReference *aPresShell) { nsIAccessibleDocument *docAccessible = nsnull; nsCOMPtr accessNode; gGlobalDocAccessibleCache.Get(NS_STATIC_CAST(void*, aPresShell), getter_AddRefs(accessNode)); if (accessNode) { CallQueryInterface(accessNode, &docAccessible); } return docAccessible; } already_AddRefed nsAccessNode::GetDocAccessibleFor(nsISupports *aContainer) { nsCOMPtr docShell(do_QueryInterface(aContainer)); NS_ASSERTION(docShell, "This method currently only supports docshells"); nsCOMPtr presShell; docShell->GetPresShell(getter_AddRefs(presShell)); nsCOMPtr weakShell(do_GetWeakReference(presShell)); return weakShell ? GetDocAccessibleFor(weakShell) : nsnull; } already_AddRefed nsAccessNode::GetDocAccessibleFor(nsIDOMNode *aNode) { nsCOMPtr eventShell = GetPresShellFor(aNode); nsCOMPtr weakEventShell(do_GetWeakReference(eventShell)); return weakEventShell? GetDocAccessibleFor(weakEventShell) : nsnull; } already_AddRefed nsAccessNode::GetPresShellFor(nsIDOMNode *aNode) { nsCOMPtr domDocument; aNode->GetOwnerDocument(getter_AddRefs(domDocument)); nsCOMPtr doc(do_QueryInterface(domDocument)); if (!doc) { // This is necessary when the node is the document node doc = do_QueryInterface(aNode); } nsIPresShell *presShell = nsnull; if (doc) { presShell = doc->GetShellAt(0); NS_IF_ADDREF(presShell); } return presShell; } already_AddRefed nsAccessNode::GetDocShellTreeItemFor(nsIDOMNode *aStartNode) { nsCOMPtr domDoc; aStartNode->GetOwnerDocument(getter_AddRefs(domDoc)); nsCOMPtr doc(do_QueryInterface(domDoc)); if (!doc) { doc = do_QueryInterface(aStartNode); } NS_ASSERTION(doc, "No document for node passed in"); NS_ENSURE_TRUE(doc, nsnull); nsCOMPtr container = doc->GetContainer(); nsIDocShellTreeItem *docShellTreeItem = nsnull; if (container) { CallQueryInterface(container, &docShellTreeItem); } return docShellTreeItem; } void nsAccessNode::PutCacheEntry(nsInterfaceHashtable &aCache, void* aUniqueID, nsIAccessNode *aAccessNode) { #ifdef DEBUG nsCOMPtr oldAccessNode; GetCacheEntry(aCache, aUniqueID, getter_AddRefs(oldAccessNode)); NS_ASSERTION(!oldAccessNode, "This cache entry shouldn't exist already"); #endif aCache.Put(aUniqueID, aAccessNode); } void nsAccessNode::GetCacheEntry(nsInterfaceHashtable &aCache, void* aUniqueID, nsIAccessNode **aAccessNode) { aCache.Get(aUniqueID, aAccessNode); // AddRefs for us } PLDHashOperator nsAccessNode::ClearCacheEntry(const void* aKey, nsCOMPtr& aAccessNode, void* aUserArg) { nsCOMPtr privateAccessNode(do_QueryInterface(aAccessNode)); privateAccessNode->Shutdown(); return PL_DHASH_REMOVE; } void nsAccessNode::ClearCache(nsInterfaceHashtable &aCache) { aCache.Enumerate(ClearCacheEntry, nsnull); }