RetroZilla/accessible/src/base/nsAccessNode.cpp

539 lines
16 KiB
C++

/* -*- 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<nsVoidHashKey, nsIAccessNode> 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<nsIAccessibleDocument> 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<nsIPresShell> presShell(do_QueryReferent(mWeakShell));
if (presShell) {
nsCOMPtr<nsIDOMNode> docNode(do_QueryInterface(presShell->GetDocument()));
if (docNode) {
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
NS_ASSERTION(accService, "No accessibility service");
if (accService) {
nsCOMPtr<nsIAccessible> 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<nsPIAccessibleDocument> 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<nsIAccessibleDocument> docAccessible(GetDocAccessible());
NS_ASSERTION(docAccessible, "No root accessible pointer back, Init() not called.");
return docAccessible->GetWindowHandle(aWindow);
}
void nsAccessNode::InitXPAccessibility()
{
if (gIsAccessibilityActive) {
return;
}
nsCOMPtr<nsIStringBundleService> 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<nsIPrefBranch> 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<nsIPresShell> 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<nsIPresShell> presShell(GetPresShell());
if (!presShell) {
return nsnull;
}
return presShell->GetPresContext();
}
already_AddRefed<nsIAccessibleDocument> nsAccessNode::GetDocAccessible()
{
return GetDocAccessibleFor(mWeakShell); // Addref'd
}
nsIFrame* nsAccessNode::GetFrame()
{
nsCOMPtr<nsIPresShell> shell(do_QueryReferent(mWeakShell));
if (!shell)
return nsnull;
nsIFrame* frame;
nsCOMPtr<nsIContent> content(do_QueryInterface(mDOMNode));
while (content) {
shell->GetPrimaryFrameFor(content, &frame);
if (frame) {
return frame;
}
nsCOMPtr<nsIContent> 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<nsIContent> 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<nsIDOMNSHTMLElement> domNSElement(do_QueryInterface(mDOMNode));
NS_ENSURE_TRUE(domNSElement, NS_ERROR_NULL_POINTER);
return domNSElement->GetInnerHTML(aInnerHTML);
}
nsresult
nsAccessNode::MakeAccessNode(nsIDOMNode *aNode, nsIAccessNode **aAccessNode)
{
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
NS_ENSURE_TRUE(accService, NS_ERROR_FAILURE);
nsCOMPtr<nsIAccessNode> accessNode;
accService->GetCachedAccessNode(aNode, mWeakShell, getter_AddRefs(accessNode));
if (!accessNode) {
nsCOMPtr<nsIAccessible> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsIContent> content(do_QueryInterface(mDOMNode));
NS_ENSURE_TRUE(content, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDOMNode> 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<nsIDOMElement> domElement(do_QueryInterface(mDOMNode));
nsPresContext *presContext = GetPresContext();
NS_ENSURE_TRUE(domElement && presContext, NS_ERROR_FAILURE);
nsCOMPtr<nsISupports> container = presContext->GetContainer();
nsCOMPtr<nsIDOMWindow> domWin(do_GetInterface(container));
nsCOMPtr<nsIDOMViewCSS> viewCSS(do_QueryInterface(domWin));
NS_ENSURE_TRUE(viewCSS, NS_ERROR_FAILURE);
nsCOMPtr<nsIDOMCSSStyleDeclaration> 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<nsIAccessibleDocument>
nsAccessNode::GetDocAccessibleFor(nsIWeakReference *aPresShell)
{
nsIAccessibleDocument *docAccessible = nsnull;
nsCOMPtr<nsIAccessNode> accessNode;
gGlobalDocAccessibleCache.Get(NS_STATIC_CAST(void*, aPresShell), getter_AddRefs(accessNode));
if (accessNode) {
CallQueryInterface(accessNode, &docAccessible);
}
return docAccessible;
}
already_AddRefed<nsIAccessibleDocument>
nsAccessNode::GetDocAccessibleFor(nsISupports *aContainer)
{
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
NS_ASSERTION(docShell, "This method currently only supports docshells");
nsCOMPtr<nsIPresShell> presShell;
docShell->GetPresShell(getter_AddRefs(presShell));
nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell));
return weakShell ? GetDocAccessibleFor(weakShell) : nsnull;
}
already_AddRefed<nsIAccessibleDocument>
nsAccessNode::GetDocAccessibleFor(nsIDOMNode *aNode)
{
nsCOMPtr<nsIPresShell> eventShell = GetPresShellFor(aNode);
nsCOMPtr<nsIWeakReference> weakEventShell(do_GetWeakReference(eventShell));
return weakEventShell? GetDocAccessibleFor(weakEventShell) : nsnull;
}
already_AddRefed<nsIPresShell>
nsAccessNode::GetPresShellFor(nsIDOMNode *aNode)
{
nsCOMPtr<nsIDOMDocument> domDocument;
aNode->GetOwnerDocument(getter_AddRefs(domDocument));
nsCOMPtr<nsIDocument> 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<nsIDocShellTreeItem>
nsAccessNode::GetDocShellTreeItemFor(nsIDOMNode *aStartNode)
{
nsCOMPtr<nsIDOMDocument> domDoc;
aStartNode->GetOwnerDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> 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<nsISupports> container = doc->GetContainer();
nsIDocShellTreeItem *docShellTreeItem = nsnull;
if (container) {
CallQueryInterface(container, &docShellTreeItem);
}
return docShellTreeItem;
}
void nsAccessNode::PutCacheEntry(nsInterfaceHashtable<nsVoidHashKey, nsIAccessNode> &aCache,
void* aUniqueID,
nsIAccessNode *aAccessNode)
{
#ifdef DEBUG
nsCOMPtr<nsIAccessNode> 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<nsVoidHashKey, nsIAccessNode> &aCache,
void* aUniqueID,
nsIAccessNode **aAccessNode)
{
aCache.Get(aUniqueID, aAccessNode); // AddRefs for us
}
PLDHashOperator nsAccessNode::ClearCacheEntry(const void* aKey, nsCOMPtr<nsIAccessNode>& aAccessNode, void* aUserArg)
{
nsCOMPtr<nsPIAccessNode> privateAccessNode(do_QueryInterface(aAccessNode));
privateAccessNode->Shutdown();
return PL_DHASH_REMOVE;
}
void nsAccessNode::ClearCache(nsInterfaceHashtable<nsVoidHashKey, nsIAccessNode> &aCache)
{
aCache.Enumerate(ClearCacheEntry, nsnull);
}