Bug 625652 - make sure accessible tree is correct when rendered text is changed, r=davidb, sr=roc, a=roc

This commit is contained in:
Alexander Surkov 2011-01-28 16:42:22 +08:00
parent 0f0efb1157
commit 9bb6534b32
10 changed files with 356 additions and 7 deletions

View File

@ -56,6 +56,8 @@ NotificationController::NotificationController(nsDocAccessible* aDocument,
mObservingState(eNotObservingRefresh), mDocument(aDocument),
mPresShell(aPresShell), mTreeConstructedState(eTreeConstructionPending)
{
mTextHash.Init();
// Schedule initial accessible tree construction.
ScheduleProcessing();
}
@ -113,6 +115,7 @@ NotificationController::Shutdown()
mDocument = nsnull;
mPresShell = nsnull;
mTextHash.Clear();
mContentInsertions.Clear();
mNotifications.Clear();
mEvents.Clear();
@ -208,6 +211,11 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
if (!mDocument->IsBoundToParent())
return;
#ifdef DEBUG_NOTIFICATIONS
printf("\ninitial tree created, document: %p, document node: %p\n",
mDocument.get(), mDocument->GetDocumentNode());
#endif
mTreeConstructedState = eTreeConstructed;
mDocument->CacheChildrenInSubtree(mDocument);
@ -235,6 +243,10 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
return;
}
// Process rendered text change notifications.
mTextHash.EnumerateEntries(TextEnumerator, mDocument);
mTextHash.Clear();
// Bind hanging child documents.
PRUint32 childDocCount = mHangingChildDocuments.Length();
for (PRUint32 idx = 0; idx < childDocCount; idx++) {
@ -561,6 +573,89 @@ NotificationController::CreateTextChangeEventFor(AccMutationEvent* aEvent)
aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
}
PLDHashOperator
NotificationController::TextEnumerator(nsPtrHashKey<nsIContent>* aEntry,
void* aUserArg)
{
nsDocAccessible* document = static_cast<nsDocAccessible*>(aUserArg);
nsIContent* textNode = aEntry->GetKey();
nsAccessible* textAcc = document->GetAccessible(textNode);
// If the text node is not in tree or doesn't have frame then this case should
// have been handled already by content removal notifications.
nsINode* containerNode = textNode->GetNodeParent();
if (!containerNode) {
NS_ASSERTION(!textAcc,
"Text node was removed but accessible is kept alive!");
return PL_DHASH_NEXT;
}
nsIFrame* textFrame = textNode->GetPrimaryFrame();
if (!textFrame) {
NS_ASSERTION(!textAcc,
"Text node isn't rendered but accessible is kept alive!");
return PL_DHASH_NEXT;
}
nsIContent* containerElm = containerNode->IsElement() ?
containerNode->AsElement() : nsnull;
nsAutoString renderedText;
textFrame->GetRenderedText(&renderedText);
// Remove text accessible if rendered text is empty.
if (textAcc) {
if (renderedText.IsEmpty()) {
#ifdef DEBUG_NOTIFICATIONS
PRUint32 index = containerNode->IndexOf(textNode);
nsCAutoString tag;
nsCAutoString id;
if (containerElm) {
containerElm->Tag()->ToUTF8String(tag);
nsIAtom* atomid = containerElm->GetID();
if (atomid)
atomid->ToUTF8String(id);
}
printf("\npending text node removal: container: %s@id='%s', index in container: %d\n\n",
tag.get(), id.get(), index);
#endif
document->ContentRemoved(containerElm, textNode);
}
return PL_DHASH_NEXT;
}
// Append an accessible if rendered text is not empty.
if (!renderedText.IsEmpty()) {
#ifdef DEBUG_NOTIFICATIONS
PRUint32 index = containerNode->IndexOf(textNode);
nsCAutoString tag;
nsCAutoString id;
if (containerElm) {
containerElm->Tag()->ToUTF8String(tag);
nsIAtom* atomid = containerElm->GetID();
if (atomid)
atomid->ToUTF8String(id);
}
printf("\npending text node insertion: container: %s@id='%s', index in container: %d\n\n",
tag.get(), id.get(), index);
#endif
nsAccessible* container = document->GetAccessibleOrContainer(containerNode);
nsTArray<nsCOMPtr<nsIContent> > insertedContents;
insertedContents.AppendElement(textNode);
document->ProcessContentInserted(container, &insertedContents);
}
return PL_DHASH_NEXT;
}
////////////////////////////////////////////////////////////////////////////////
// NotificationController: content inserted notification
@ -616,7 +711,7 @@ NotificationController::ContentInsertion::Process()
catomid->ToUTF8String(cid);
}
printf("\npending content insertion process: %s@id='%s', container: %s@id='%s', inserted content amount: %d\n\n",
printf("\npending content insertion: %s@id='%s', container: %s@id='%s', inserted content amount: %d\n\n",
tag.get(), id.get(), ctag.get(), cid.get(), mInsertedContent.Length());
#endif

View File

@ -149,6 +149,16 @@ public:
*/
void ScheduleChildDocBinding(nsDocAccessible* aDocument);
/**
* Schedule the accessible tree update because of rendered text changes.
*/
inline void ScheduleTextUpdate(nsIContent* aTextNode)
{
// Ignore the notification if initial tree construction hasn't been done yet.
if (mTreeConstructedState != eTreeConstructionPending)
mTextHash.PutEntry(aTextNode);
}
/**
* Pend accessible tree update for content insertion.
*/
@ -323,6 +333,17 @@ private:
*/
nsTArray<nsRefPtr<ContentInsertion> > mContentInsertions;
/**
* A pending accessible tree update notifications for rendered text changes.
*/
nsTHashtable<nsPtrHashKey<nsIContent> > mTextHash;
/**
* Update the accessible tree for pending rendered text change notifications.
*/
static PLDHashOperator TextEnumerator(nsPtrHashKey<nsIContent>* aEntry,
void* aUserArg);
/**
* Other notifications like DOM events. Don't make this an nsAutoTArray; we
* use SwapElements() on it.

View File

@ -528,6 +528,15 @@ nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
docAccessible->ContentRemoved(aContainer, aChild);
}
void
nsAccessibilityService::UpdateText(nsIPresShell* aPresShell,
nsIContent* aContent)
{
nsDocAccessible* document = GetDocAccessible(aPresShell->GetDocument());
if (document)
document->UpdateText(aContent);
}
void
nsAccessibilityService::PresShellDestroyed(nsIPresShell *aPresShell)
{

View File

@ -118,6 +118,8 @@ public:
virtual void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aContainer,
nsIContent* aChild);
virtual void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget);
virtual void PresShellDestroyed(nsIPresShell* aPresShell);

View File

@ -309,6 +309,17 @@ public:
*/
void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode);
/**
* Updates accessible tree when rendered text is changed.
*/
inline void UpdateText(nsIContent* aTextNode)
{
NS_ASSERTION(mNotificationController, "The document was shut down!");
if (mNotificationController)
mNotificationController->ScheduleTextUpdate(aTextNode);
}
/**
* Recreate an accessible, results in hide/show events pair.
*/

View File

@ -907,7 +907,7 @@ function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
function invokerChecker_targetDescrGetter()
{
if (typeof this.mTarget == "function")
return this.mTarget.toSource() + this.mTargetFuncArg;
return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
return prettyName(this.mTarget);
}

View File

@ -32,7 +32,7 @@
testAccessibleTree("txc1", accTree);
// input@type="text"
// input@type="text", value
accTree = {
role: ROLE_ENTRY,
children: [
@ -45,6 +45,14 @@
testAccessibleTree("txc2", accTree);
// input@type="text", no value
accTree =
{ ENTRY: [
{ TEXT_LEAF: [ ] }
] };
testAccessibleTree("txc3", accTree);
// textarea
accTree = {
role: ROLE_ENTRY,
@ -58,7 +66,7 @@
]
};
testAccessibleTree("txc3", accTree);
testAccessibleTree("txc4", accTree);
// input@type="password"
accTree = {
@ -71,7 +79,7 @@
]
};
testAccessibleTree("txc4", accTree);
testAccessibleTree("txc5", accTree);
SimpleTest.finish();
}
@ -92,6 +100,11 @@
title="Create child accessibles for text controls from native anonymous content">
Mozilla Bug 542824
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"
title="Make sure accessible tree is correct when rendered text is changed">
Mozilla Bug 625652
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
@ -102,11 +115,12 @@
1hellohello
</div>
<input id="txc2" value="hello">
<textarea id="txc3">
<input id="txc3">
<textarea id="txc4">
hello1
hello2
</textarea>
<input id="txc4" type="password" value="hello">
<input id="txc5" type="password" value="hello">
</body>
</html>

View File

@ -54,6 +54,7 @@ _TEST_FILES =\
test_select.html \
test_textleaf.html \
test_visibility.html \
test_whitespace.html \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,188 @@
<!DOCTYPE html>
<html>
<head>
<title>Whitespace text accessible creation/desctruction</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="../events.js"></script>
<script type="application/javascript">
////////////////////////////////////////////////////////////////////////////
// Invokers
/**
* Middle image accessible removal results in text accessible removal.
*
* Before:
* DOM: whitespace img1 whitespace img2 whitespace img3 whitespace,
* a11y: img1 whitespace img2 whitespace img3
* After:
* DOM: whitespace img1 whitespace whitespace img3 whitespace,
* a11y: img1 whitespace img3
*/
function removeImg()
{
this.containerNode = getNode("container1");
this.imgNode = getNode("img1");
this.img = getAccessible(this.imgNode);
this.text = this.img.nextSibling;
this.eventSeq = [
new invokerChecker(EVENT_HIDE, this.img),
new invokerChecker(EVENT_HIDE, this.text),
new invokerChecker(EVENT_REORDER, this.containerNode)
];
this.finalCheck = function textLeafUpdate_finalCheck()
{
var tree =
{ SECTION: [
{ GRAPHIC: [] },
{ TEXT_LEAF: [] },
{ GRAPHIC: [] }
] };
testAccessibleTree(this.containerNode, tree);
}
this.invoke = function setOnClickAttr_invoke()
{
var tree =
{ SECTION: [
{ GRAPHIC: [] },
{ TEXT_LEAF: [] },
{ GRAPHIC: [] },
{ TEXT_LEAF: [] },
{ GRAPHIC: [] }
] };
testAccessibleTree(this.containerNode, tree);
this.containerNode.removeChild(this.imgNode);
}
this.getID = function setOnClickAttr_getID()
{
return "remove middle img";
}
}
/**
* Append image making the whitespace visible and thus accessible.
* Note: images and whitespaces are on different leves of accessible trees,
* so that image container accessible update doesn't update the tree
* of whitespace container.
*
* Before:
* DOM: whitespace emptylink whitespace linkwithimg whitespace
* a11y: emptylink linkwithimg
* After:
* DOM: whitespace linkwithimg whitespace linkwithimg whitespace
* a11y: linkwithimg whitespace linkwithimg
*/
function insertImg()
{
this.containerNode = getNode("container2");
this.topNode = this.containerNode.parentNode;
this.textNode = this.containerNode.nextSibling;
this.imgNode = document.createElement("img");
this.imgNode.setAttribute("src", "../moz.png");
this.eventSeq = [
new invokerChecker(EVENT_SHOW, getAccessible, this.imgNode),
new invokerChecker(EVENT_SHOW, getAccessible, this.textNode),
new invokerChecker(EVENT_REORDER, this.topNode)
];
this.invoke = function insertImg_invoke()
{
var tree =
{ SECTION: [
{ LINK: [] },
{ LINK: [
{ GRAPHIC: [] }
] }
] };
testAccessibleTree(this.topNode, tree);
this.containerNode.appendChild(this.imgNode);
}
this.finalCheck = function insertImg_finalCheck()
{
var tree =
{ SECTION: [
{ LINK: [
{ GRAPHIC: [ ] }
] },
{ TEXT_LEAF: [ ] },
{ LINK: [
{ GRAPHIC: [ ] }
] }
] };
testAccessibleTree(this.topNode, tree);
}
this.getID = function appendImg_getID()
{
return "insert img into internal container";
}
}
////////////////////////////////////////////////////////////////////////////
// Test
//gA11yEventDumpID = "eventdump"; // debug stuff
//gA11yEventDumpToConsole = true;
var gQueue = null;
function doTest()
{
gQueue = new eventQueue();
gQueue.push(new removeImg());
gQueue.push(new insertImg());
gQueue.invoke(); // SimpleTest.finish() will be called in the end
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
<a target="_blank"
title="Make sure accessible tree is correct when rendered text is changed"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652">
Mozilla Bug 625652
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="container1"> <img src="../moz.png"> <img id="img1" src="../moz.png"> <img src="../moz.png"> </div>
<div> <a id="container2"></a> <a><img src="../moz.png"></a> </div>
<div id="eventdump"></div>
</body>
</html>

View File

@ -6908,6 +6908,14 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
aMetrics.width, aMetrics.height, aMetrics.ascent,
aStatus);
#endif
#ifdef ACCESSIBILITY
// Schedule the update of accessible tree when rendered text might be changed.
nsAccessibilityService* accService = nsIPresShell::AccService();
if (accService) {
accService->UpdateText(presContext->PresShell(), mContent);
}
#endif
}
/* virtual */ PRBool