Bug 592913 - provide a way to quickly determine whether an accessible object is a descendant of a tab document, r=marcoz, davidb, a=blocking

This commit is contained in:
Alexander Surkov 2010-09-09 23:44:56 +09:00
parent c5cea9abde
commit 0ee231bcdf
8 changed files with 235 additions and 71 deletions

View File

@ -56,7 +56,7 @@ interface nsIDOMWindow;
* nsIAccessNode::GetAccessibleDocument() or
* nsIAccessibleEvent::GetAccessibleDocument()
*/
[scriptable, uuid(03c6ce8a-aa40-4484-9282-e6579c56e054)]
[scriptable, uuid(451242bd-8a0c-4198-ae88-c053609a4e5d)]
interface nsIAccessibleDocument : nsISupports
{
/**
@ -99,4 +99,19 @@ interface nsIAccessibleDocument : nsISupports
* For example, in Windows you can static cast it to an HWND.
*/
[noscript] readonly attribute voidPtr windowHandle;
/**
* Return the parent document accessible.
*/
readonly attribute nsIAccessibleDocument parentDocument;
/**
* Return the count of child document accessibles.
*/
readonly attribute unsigned long childDocumentCount;
/**
* Return the child document accessible at the given index.
*/
nsIAccessibleDocument getChildDocumentAt(in unsigned long index);
};

View File

@ -133,11 +133,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventQueue");
cb.NoteXPCOMChild(tmp->mEventQueue.get());
PRUint32 i, length = tmp->mChildDocuments.Length();
for (i = 0; i < length; ++i) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChildDocuments[i]");
cb.NoteXPCOMChild(static_cast<nsIAccessible*>(tmp->mChildDocuments[i].get()));
}
CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments)
ClearCache(tmp->mAccessibleCache);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -498,6 +505,44 @@ nsDocAccessible::GetDOMDocument(nsIDOMDocument **aDOMDocument)
return NS_OK;
}
NS_IMETHODIMP
nsDocAccessible::GetParentDocument(nsIAccessibleDocument** aDocument)
{
NS_ENSURE_ARG_POINTER(aDocument);
*aDocument = nsnull;
if (!IsDefunct())
NS_IF_ADDREF(*aDocument = ParentDocument());
return NS_OK;
}
NS_IMETHODIMP
nsDocAccessible::GetChildDocumentCount(PRUint32* aCount)
{
NS_ENSURE_ARG_POINTER(aCount);
*aCount = 0;
if (!IsDefunct())
*aCount = ChildDocumentCount();
return NS_OK;
}
NS_IMETHODIMP
nsDocAccessible::GetChildDocumentAt(PRUint32 aIndex,
nsIAccessibleDocument** aDocument)
{
NS_ENSURE_ARG_POINTER(aDocument);
*aDocument = nsnull;
if (IsDefunct())
return NS_OK;
NS_IF_ADDREF(*aDocument = GetChildDocumentAt(aIndex));
return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
}
// nsIAccessibleHyperText method
NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
{
@ -531,6 +576,7 @@ NS_IMETHODIMP nsDocAccessible::GetAssociatedEditor(nsIEditor **aEditor)
return NS_OK;
}
// nsDocAccessible public method
nsAccessible *
nsDocAccessible::GetCachedAccessible(void *aUniqueID)
{
@ -603,6 +649,10 @@ nsDocAccessible::Init()
AddEventListeners();
nsDocAccessible* parentDocument = mParent->GetDocAccessible();
if (parentDocument)
parentDocument->AppendChildDocument(this);
// Fire reorder event to notify new accessible document has been created and
// attached to the tree.
nsRefPtr<AccEvent> reorderEvent =
@ -629,8 +679,15 @@ nsDocAccessible::Shutdown()
RemoveEventListeners();
if (mParent)
if (mParent) {
nsDocAccessible* parentDocument = mParent->GetDocAccessible();
if (parentDocument)
parentDocument->RemoveChildDocument(this);
mParent->RemoveChild(this);
}
mChildDocuments.Clear();
mWeakShell = nsnull; // Avoid reentrancy
@ -1278,6 +1335,23 @@ nsDocAccessible::HandleAccEvent(AccEvent* aAccEvent)
////////////////////////////////////////////////////////////////////////////////
// Public members
nsAccessible*
nsDocAccessible::GetCachedAccessibleInSubtree(void* aUniqueID)
{
nsAccessible* child = GetCachedAccessible(aUniqueID);
if (child)
return child;
PRUint32 childDocCount = mChildDocuments.Length();
for (PRUint32 childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
nsDocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
child = childDocument->GetCachedAccessibleInSubtree(aUniqueID);
if (child)
return child;
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// Protected members

View File

@ -141,6 +141,24 @@ public:
*/
void MarkAsLoaded() { mIsLoaded = PR_TRUE; }
/**
* Return the parent document.
*/
nsDocAccessible* ParentDocument() const
{ return mParent ? mParent->GetDocAccessible() : nsnull; }
/**
* Return the child document count.
*/
PRUint32 ChildDocumentCount() const
{ return mChildDocuments.Length(); }
/**
* Return the child document at the given index.
*/
nsDocAccessible* GetChildDocumentAt(PRUint32 aIndex) const
{ return mChildDocuments.SafeElementAt(aIndex, nsnull); }
/**
* Non-virtual method to fire a delayed event after a 0 length timeout.
*
@ -188,6 +206,12 @@ public:
*/
nsAccessible* GetCachedAccessible(void *aUniqueID);
/**
* Return the cached accessible by the given unique ID looking through
* this and nested documents.
*/
nsAccessible* GetCachedAccessibleInSubtree(void* aUniqueID);
/**
* Cache the accessible.
*
@ -217,6 +241,24 @@ protected:
void AddScrollListener();
void RemoveScrollListener();
/**
* Append the given document accessible to this document's child document
* accessibles.
*/
bool AppendChildDocument(nsDocAccessible* aChildDocument)
{
return mChildDocuments.AppendElement(aChildDocument);
}
/**
* Remove the given document accessible from this document's child document
* accessibles.
*/
void RemoveChildDocument(nsDocAccessible* aChildDocument)
{
mChildDocuments.RemoveElement(aChildDocument);
}
/**
* Invalidate parent-child relations for any cached accessible in the DOM
* subtree. Accessible objects aren't destroyed.
@ -337,6 +379,8 @@ protected:
static PRUint32 gLastFocusedAccessiblesState;
static nsIAtom *gLastFocusedFrameType;
nsTArray<nsRefPtr<nsDocAccessible> > mChildDocuments;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsDocAccessible,

View File

@ -282,24 +282,21 @@ STDMETHODIMP nsAccessibleWrap::get_accChild(
{
__try {
*ppdispChild = NULL;
if (!mWeakShell || varChild.vt != VT_I4)
if (IsDefunct())
return E_FAIL;
if (varChild.lVal == CHILDID_SELF) {
*ppdispChild = static_cast<IDispatch*>(this);
AddRef();
return S_OK;
}
// IAccessible::accChild is used to return this accessible or child accessible
// at the given index or to get an accessible by child ID in the case of
// document accessible (it's handled by overriden GetXPAccessibleFor method
// on the document accessible). The getting an accessible by child ID is used
// by AccessibleObjectFromEvent() called by AT when AT handles our MSAA event.
nsAccessible* child = GetXPAccessibleFor(varChild);
if (child)
*ppdispChild = NativeAccessible(child);
if (!nsAccUtils::MustPrune(this)) {
nsAccessible* child = GetChildAt(varChild.lVal - 1);
if (child) {
*ppdispChild = NativeAccessible(child);
}
}
} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
return (*ppdispChild)? S_OK: E_FAIL;
return (*ppdispChild)? S_OK: E_INVALIDARG;
}
STDMETHODIMP nsAccessibleWrap::get_accName(
@ -1804,7 +1801,7 @@ nsAccessibleWrap::UnattachIEnumVariant()
nsAccessible*
nsAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
{
if (IsDefunct())
if (aVarChild.vt != VT_I4)
return nsnull;
// if its us real easy - this seems to always be the case

View File

@ -103,39 +103,13 @@ nsDocAccessibleWrap::GetXPAccessibleFor(const VARIANT& aVarChild)
// accessible through whole accessible subtree including subdocuments.
// Otherwise we treat lVal as index in parent.
if (aVarChild.lVal < 0)
return IsDefunct() ? nsnull : GetXPAccessibleForChildID(aVarChild);
return nsAccessibleWrap::GetXPAccessibleFor(aVarChild);
}
STDMETHODIMP
nsDocAccessibleWrap::get_accChild(VARIANT varChild,
IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
{
__try {
*ppdispChild = NULL;
if (varChild.vt == VT_I4 && varChild.lVal < 0) {
// IAccessible::accChild can be used to get an accessible by child ID.
// It is used by AccessibleObjectFromEvent() called by AT when AT handles
// our MSAA event.
nsAccessible *xpAccessible = GetXPAccessibleForChildID(varChild);
if (!xpAccessible)
return E_FAIL;
IAccessible *msaaAccessible = NULL;
xpAccessible->GetNativeInterface((void**)&msaaAccessible);
*ppdispChild = static_cast<IDispatch*>(msaaAccessible);
return S_OK;
if (aVarChild.vt == VT_I4 && aVarChild.lVal < 0) {
// Convert child ID to unique ID.
void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
return GetCachedAccessibleInSubtree(uniqueID);
}
// Otherwise, the normal get_accChild() will do
return nsAccessibleWrap::get_accChild(varChild, ppdispChild);
} __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
return E_FAIL;
return nsAccessibleWrap::GetXPAccessibleFor(aVarChild);
}
STDMETHODIMP nsDocAccessibleWrap::get_URL(/* [out] */ BSTR __RPC_FAR *aURL)
@ -271,14 +245,3 @@ STDMETHODIMP nsDocAccessibleWrap::get_accValue(
return get_URL(pszValue);
}
nsAccessible*
nsDocAccessibleWrap::GetXPAccessibleForChildID(const VARIANT& aVarChild)
{
NS_PRECONDITION(aVarChild.vt == VT_I4 && aVarChild.lVal < 0,
"Variant doesn't point to child ID!");
// Convert child ID to unique ID.
void *uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
return GetAccService()->FindAccessibleInCache(uniqueID);
}

View File

@ -83,10 +83,6 @@ public:
/* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes);
// IAccessible
// Override get_accChild so that it can get any child via the unique ID
virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild(
/* [in] */ VARIANT varChild,
/* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild);
// Override get_accValue to provide URL when no other value is available
virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue(
@ -95,15 +91,6 @@ public:
// nsAccessibleWrap
virtual nsAccessible *GetXPAccessibleFor(const VARIANT& varChild);
// nsDocAccessibleWrap
/**
* Find an accessible by the given child ID in cached documents.
*
* @param aVarChild [in] variant pointing to the child ID
*/
static nsAccessible *GetXPAccessibleForChildID(const VARIANT& aVarChild);
};
#endif

View File

@ -53,6 +53,7 @@ _TEST_FILES =\
test_colorpicker.xul \
test_combobox.xul \
test_cssoverflow.html \
test_dochierarchy.html \
test_filectrl.html \
test_formctrl.html \
test_formctrl.xul \

View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<title>Test document hierarchy</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
function doTest()
{
// tabDoc and testDoc are different documents depending on whether test
// is running in standalone mode or not.
var root = getRootAccessible();
var tabDoc = window.parent ?
getAccessible(window.parent.document, [nsIAccessibleDocument]) :
getAccessible(document, [nsIAccessibleDocument]);
var testDoc = getAccessible(document, [nsIAccessibleDocument]);
var iframeDoc = getAccessible("iframe").firstChild.
QueryInterface(nsIAccessibleDocument);
is(root.parentDocument, null,
"Wrong parent document of root accessible");
is(root.childDocumentCount, 1,
"Wrong child document count of root accessible");
is(root.getChildDocumentAt(0), tabDoc,
"Wrong child document at index 0 of root accessible");
is(tabDoc.parentDocument, root,
"Wrong parent document of tab document");
is(tabDoc.childDocumentCount, 1,
"Wrong child document count of tab document");
is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc),
"Wrong child document at index 0 of tab document");
if (tabDoc != testDoc) {
is(testDoc.parentDocument, tabDoc,
"Wrong parent document of test document");
is(testDoc.childDocumentCount, 1,
"Wrong child document count of test document");
is(testDoc.getChildDocumentAt(0), iframeDoc,
"Wrong child document at index 0 of test document");
}
is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc),
"Wrong parent document of iframe document");
is(iframeDoc.childDocumentCount, 0,
"Wrong child document count of iframe document");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913"
title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document">
Mozilla Bug 592913
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<iframe src="about:mozilla" id="iframe"></iframe>
</body>
</html>