mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 08:12:05 +00:00
Bug 625652 - make sure accessible tree is correct when rendered text is changed, r=davidb, sr=roc, a=roc
This commit is contained in:
parent
0f0efb1157
commit
9bb6534b32
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -54,6 +54,7 @@ _TEST_FILES =\
|
||||
test_select.html \
|
||||
test_textleaf.html \
|
||||
test_visibility.html \
|
||||
test_whitespace.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
|
188
accessible/tests/mochitest/treeupdate/test_whitespace.html
Normal file
188
accessible/tests/mochitest/treeupdate/test_whitespace.html
Normal 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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user