Bug 396166 - xul:label@value accessible should implement nsIAccessibleText, r=tbsaunde, roc

This commit is contained in:
Alexander Surkov 2013-03-01 13:06:16 +09:00
parent aa7f8f28f4
commit 68f9fb3d83
16 changed files with 436 additions and 26 deletions

View File

@ -52,6 +52,7 @@ enum AccType {
eMenuPopupType,
eProgressType,
eRootType,
eXULLabelType,
eXULTabpanelsType,
eXULTreeType,

View File

@ -65,7 +65,7 @@ EnableLogging(const char* aModulesStr)
size_t tokenLen = strcspn(token, ",");
for (unsigned int idx = 0; idx < ArrayLength(sModuleMap); idx++) {
if (strncmp(token, sModuleMap[idx].mStr, tokenLen) == 0) {
#if !defined(MOZ_PROFILING) && (!defined(MOZ_DEBUG) || defined(MOZ_OPTIMIZE))
#if !defined(MOZ_PROFILING) && (!defined(DEBUG) || defined(MOZ_OPTIMIZE))
// Stack tracing on profiling enabled or debug not optimized builds.
if (strncmp(token, "stack", tokenLen) == 0)
break;

View File

@ -402,6 +402,24 @@ nsAccessibilityService::UpdateImageMap(nsImageFrame* aImageFrame)
}
}
void
nsAccessibilityService::UpdateLabelValue(nsIPresShell* aPresShell,
nsIContent* aLabelElm,
const nsString& aNewValue)
{
DocAccessible* document = GetDocAccessible(aPresShell);
if (document) {
Accessible* accessible = document->GetAccessible(aLabelElm);
if (accessible) {
XULLabelAccessible* xulLabel = accessible->AsXULLabel();
NS_ASSERTION(xulLabel,
"UpdateLabelValue was called for wrong accessible!");
if (xulLabel)
xulLabel->UpdateLabelValue(aNewValue);
}
}
}
void
nsAccessibilityService::PresShellActivated(nsIPresShell* aPresShell)
{

View File

@ -104,6 +104,12 @@ public:
*/
void UpdateImageMap(nsImageFrame* aImageFrame);
/**
* Update the label accessible tree when rendered @value is changed.
*/
void UpdateLabelValue(nsIPresShell* aPresShell, nsIContent* aLabelElm,
const nsString& aNewValue);
/**
* Notify accessibility that anchor jump has been accomplished to the given
* target. Used by layout.

View File

@ -46,6 +46,7 @@ class Relation;
class TableAccessible;
class TableCellAccessible;
class TextLeafAccessible;
class XULLabelAccessible;
class XULTreeAccessible;
/**
@ -523,6 +524,9 @@ public:
bool IsTextLeaf() const { return mType == eTextLeafType; }
TextLeafAccessible* AsTextLeaf();
bool IsXULLabel() const { return mType == eXULLabelType; }
XULLabelAccessible* AsXULLabel();
bool IsXULTabpanels() const { return mType == eXULTabpanelsType; }
bool IsXULTree() const { return mType == eXULTreeType; }
@ -892,7 +896,7 @@ protected:
static const uint8_t kChildrenFlagsBits = 2;
static const uint8_t kStateFlagsBits = 5;
static const uint8_t kTypeBits = 5;
static const uint8_t kTypeBits = 6;
static const uint8_t kGenericTypesBits = 12;
/**

View File

@ -7,18 +7,25 @@
#include "Accessible-inl.h"
#include "BaseAccessibles.h"
#include "DocAccessible-inl.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "nsTextEquivUtils.h"
#include "Relation.h"
#include "Role.h"
#include "States.h"
#include "TextUpdater.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
#include "nsIAccessibleRelation.h"
#include "nsIDOMXULDescriptionElement.h"
#include "nsINameSpaceManager.h"
#include "nsString.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsTextBoxFrame.h"
using namespace mozilla::a11y;
@ -30,6 +37,31 @@ XULLabelAccessible::
XULLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
HyperTextAccessibleWrap(aContent, aDoc)
{
mType = eXULLabelType;
// If @value attribute is given then it's rendered instead text content. In
// this case we need to create a text leaf accessible to make @value attribute
// accessible.
// XXX: text interface doesn't let you get the text by words.
nsTextBoxFrame* textBoxFrame = do_QueryFrame(mContent->GetPrimaryFrame());
if (textBoxFrame) {
mValueTextLeaf = new XULLabelTextLeafAccessible(mContent, mDoc);
if (mDoc->BindToDocument(mValueTextLeaf, nullptr)) {
nsAutoString text;
textBoxFrame->GetCroppedTitle(text);
mValueTextLeaf->SetText(text);
return;
}
mValueTextLeaf = nullptr;
}
}
void
XULLabelAccessible::Shutdown()
{
mValueTextLeaf = nullptr;
HyperTextAccessibleWrap::Shutdown();
}
ENameValueFlag
@ -37,7 +69,9 @@ XULLabelAccessible::NativeName(nsString& aName)
{
// if the value attr doesn't exist, the screen reader must get the accessible text
// from the accessible text interface or from the children
mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
if (mValueTextLeaf)
return mValueTextLeaf->Name(aName);
return eNameOK;
}
@ -72,6 +106,53 @@ XULLabelAccessible::RelationByType(uint32_t aType)
return rel;
}
void
XULLabelAccessible::UpdateLabelValue(const nsString& aValue)
{
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eText)) {
logging::MsgBegin("TEXT", "text may be changed (xul:label @value update)");
logging::Node("container", mContent);
logging::MsgEntry("old text '%s'",
NS_ConvertUTF16toUTF8(mValueTextLeaf->Text()).get());
logging::MsgEntry("new text: '%s'",
NS_ConvertUTF16toUTF8(aValue).get());
logging::MsgEnd();
}
#endif
TextUpdater::Run(mDoc, mValueTextLeaf, aValue);
}
void
XULLabelAccessible::CacheChildren()
{
if (mValueTextLeaf) {
AppendChild(mValueTextLeaf);
return;
}
// Cache children from subtree.
AccessibleWrap::CacheChildren();
}
////////////////////////////////////////////////////////////////////////////////
// XULLabelTextLeafAccessible
////////////////////////////////////////////////////////////////////////////////
role
XULLabelTextLeafAccessible::NativeRole()
{
return roles::TEXT_LEAF;
}
uint64_t
XULLabelTextLeafAccessible::NativeState()
{
return TextLeafAccessibleWrap::NativeState() | states::READONLY;
}
////////////////////////////////////////////////////////////////////////////////
// XULTooltipAccessible

View File

@ -6,12 +6,14 @@
#ifndef mozilla_a11y_XULElementAccessibles_h__
#define mozilla_a11y_XULElementAccessibles_h__
#include "BaseAccessibles.h"
#include "HyperTextAccessibleWrap.h"
#include "TextLeafAccessibleWrap.h"
namespace mozilla {
namespace a11y {
class XULLabelTextLeafAccessible;
/**
* Used for XUL description and label elements.
*/
@ -21,15 +23,48 @@ public:
XULLabelAccessible(nsIContent* aContent, DocAccessible* aDoc);
// Accessible
virtual void Shutdown();
virtual a11y::role NativeRole();
virtual uint64_t NativeState();
virtual Relation RelationByType(uint32_t aRelationType);
void UpdateLabelValue(const nsString& aValue);
protected:
// Accessible
virtual ENameValueFlag NativeName(nsString& aName) MOZ_OVERRIDE;
virtual void CacheChildren() MOZ_OVERRIDE;
private:
nsRefPtr<XULLabelTextLeafAccessible> mValueTextLeaf;
};
inline XULLabelAccessible*
Accessible::AsXULLabel()
{
return IsXULLabel() ? static_cast<XULLabelAccessible*>(this) : nullptr;
}
/**
* Used to implement text interface on XUL label accessible in case when text
* is provided by @value attribute (no underlying text frame).
*/
class XULLabelTextLeafAccessible MOZ_FINAL : public TextLeafAccessibleWrap
{
public:
XULLabelTextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
TextLeafAccessibleWrap(aContent, aDoc)
{ mStateFlags |= eSharedNode; }
virtual ~XULLabelTextLeafAccessible() { }
// Accessible
virtual a11y::role NativeRole() MOZ_OVERRIDE;
virtual uint64_t NativeState() MOZ_OVERRIDE;
};
/**
* Used for XUL tooltip element.
*/

View File

@ -582,13 +582,13 @@ function getTextFromClipboard()
var clip = Components.classes["@mozilla.org/widget/clipboard;1"].
getService(Components.interfaces.nsIClipboard);
if (!clip)
return;
return "";
var trans = Components.classes["@mozilla.org/widget/transferable;1"].
createInstance(Components.interfaces.nsITransferable);
trans.init(getLoadContext());
if (!trans)
return;
return "";
trans.addDataFlavor("text/unicode");
clip.getData(trans, clip.kGlobalClipboard);

View File

@ -1548,16 +1548,21 @@ function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted, aFromUse
{
this.target = getNode(aID);
this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
this.startOffset = aStart;
this.endOffset = aEnd;
this.textOrFunc = aTextOrFunc;
this.check = function textChangeChecker_check(aEvent)
{
aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
var modifiedText = (typeof aTextOrFunc == "function") ?
aTextOrFunc() : aTextOrFunc;
var modifiedTextLen = (aEnd == -1) ? modifiedText.length : aEnd - aStart;
var modifiedText = (typeof this.textOrFunc == "function") ?
this.textOrFunc() : this.textOrFunc;
var modifiedTextLen =
(this.endOffset == -1) ? modifiedText.length : aEnd - aStart;
is(aEvent.start, aStart, "Wrong start offset for " + prettyName(aID));
is(aEvent.start, this.startOffset,
"Wrong start offset for " + prettyName(aID));
is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
var changeInfo = (aIsInserted ? "inserted" : "removed");
is(aEvent.isInserted(), aIsInserted,

View File

@ -46,6 +46,7 @@ MOCHITEST_A11Y_FILES =\
test_focus_tabbox.xul \
test_focus_tree.xul \
test_fromUserInput.html \
test_label.xul \
test_menu.xul \
test_mutation.html \
test_mutation.xhtml \

View File

@ -0,0 +1,177 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tests: accessible XUL label/description events">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../role.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
<![CDATA[
////////////////////////////////////////////////////////////////////////////
// Invokers
const kRecreated = 0;
const kTextRemoved = 1;
const kTextChanged = 2;
const kNoValue = 0;
/**
* Set/remove @value attribute.
*/
function setValue(aID, aValue, aResult, aOldValue)
{
this.labelNode = getNode(aID);
this.eventSeq = [];
switch (aResult) {
case kRecreated:
this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.labelNode));
this.eventSeq.push(new invokerChecker(EVENT_SHOW, this.labelNode));
break;
case kTextRemoved:
this.eventSeq.push(
new textChangeChecker(this.labelNode, 0, aOldValue.length,
aOldValue, false));
break;
case kTextChanged:
this.eventSeq.push(
new textChangeChecker(this.labelNode, 0, aOldValue.length,
aOldValue, false));
this.eventSeq.push(
new textChangeChecker(this.labelNode, 0, aValue.length,
aValue, true));
break;
}
this.invoke = function setValue_invoke()
{
if (aValue === kNoValue)
this.labelNode.removeAttribute("value");
else
this.labelNode.setAttribute("value", aValue);
}
this.finalCheck = function setValue_finalCheck()
{
var tree =
{ LABEL: [
{ TEXT_LEAF: [ ] }
] };
testAccessibleTree(aID, tree);
}
this.getID = function setValue_getID()
{
return "set @value='" + aValue + "' for label " + prettyName(aID);
}
}
/**
* Change @crop attribute.
*/
function setCrop(aID, aCropValue, aRemovedText, aInsertedText)
{
this.labelNode = getNode(aID);
this.width = this.labelNode.boxObject.width;
this.charWidth = this.width / this.labelNode.value.length;
this.eventSeq = [
new textChangeChecker(this.labelNode, 0, -1, aRemovedText, false),
new textChangeChecker(this.labelNode, 0, -1, aInsertedText, true)
];
this.invoke = function setCrop_invoke()
{
if (!this.labelNode.hasAttribute("crop"))
this.labelNode.width = Math.floor(this.width - 2 * this.charWidth);
this.labelNode.setAttribute("crop", aCropValue);
}
this.getID = function setCrop_finalCheck()
{
return "set crop " + aCropValue;
}
}
////////////////////////////////////////////////////////////////////////////
// Test
gA11yEventDumpToConsole = true;
var gQueue = null;
function doTest()
{
gQueue = new eventQueue();
gQueue.push(new setValue("label", "shiroka strana", kRecreated));
gQueue.push(new setValue("label", "?<>!+_", kTextChanged, "shiroka strana"));
gQueue.push(new setValue("label", "", kTextRemoved, "?<>!+_"));
gQueue.push(new setValue("label", kNoValue, kRecreated));
gQueue.push(new setValue("descr", "hello world", kRecreated));
gQueue.push(new setValue("descr", "si_ya", kTextChanged, "hello world"));
gQueue.push(new setValue("descr", "", kTextRemoved, "si_ya"));
gQueue.push(new setValue("descr", kNoValue, kRecreated));
if (MAC) {
// "valuetocro" -> "…etocro"
gQueue.push(new setCrop("croplabel", "left", "valu", "…"));
// "…etocro", "val…cro"
gQueue.push(new setCrop("croplabel", "center", "…eto", "val…"));
} else {
// "valuetocro" -> "…uetocro"
gQueue.push(new setCrop("croplabel", "left", "val", "…"));
// "…uetocro" -> "valu…cro"
gQueue.push(new setCrop("croplabel", "center", "…ueto", "valu…"));
}
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
]]>
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
title="xul:label@value accessible should implement nsIAccessibleText">
Bug 396166
</a>
<br/>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<vbox flex="1">
<label id="label">hello</label>
<description id="descr">hello</description>
<hbox>
<label id="croplabel" value="valuetocro"
style="font-family: monospace;"/>
</hbox>
</vbox>
</hbox>
</window>

View File

@ -15,6 +15,7 @@ MOCHITEST_A11Y_FILES = \
doc.html \
test_doc.html \
test_hypertext.html \
test_label.xul \
test_passwords.html \
test_selection.html \
test_singleline.html \

View File

@ -0,0 +1,64 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Tests: XUL label text interface">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript"
src="../text.js"></script>
<script type="application/javascript">
<![CDATA[
////////////////////////////////////////////////////////////////////////////
// Testing
var gQueue = null;
function doTests()
{
var ids = ["label1", "label2"];
testCharacterCount(ids, 5);
testText(ids, 0, -1, "Hello");
testText(ids, 0, 1, "H");
testCharAfterOffset(ids, 0, "e", 1, 2);
testCharBeforeOffset(ids, 1, "H", 0, 1);
testCharAtOffset(ids, 1, "e", 1, 2);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
]]>
</script>
<vbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
title="xul:label@value accessible should implement nsIAccessibleText">
Bug 396166
</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<label id="label1" value="Hello"/>
<label id="label2">Hello</label>
</vbox>
</window>

View File

@ -21,20 +21,13 @@
function doTest()
{
var accTree = {
role: ROLE_GROUPING,
children: [
{
role: ROLE_LABEL,
children: [ ]
},
{
role: ROLE_CHECKBUTTON,
children: [ ]
}
]
};
var accTree =
{ GROUPING: [
{ LABEL: [
{ TEXT_LEAF: [ ] }
] },
{ CHECKBUTTON: [ ] }
] };
testAccessibleTree("groupbox", accTree);
SimpleTest.finish()

View File

@ -31,6 +31,10 @@
#include "nsLayoutUtils.h"
#include "mozilla/Attributes.h"
#ifdef ACCESSIBILITY
#include "nsAccessibilityService.h"
#endif
#ifdef IBMBIDI
#include "nsBidiUtils.h"
#include "nsBidiPresUtils.h"
@ -59,6 +63,9 @@ NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame)
NS_QUERYFRAME_HEAD(nsTextBoxFrame)
NS_QUERYFRAME_ENTRY(nsTextBoxFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsTextBoxFrameSuper)
NS_IMETHODIMP
nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID,
@ -596,8 +603,10 @@ nsTextBoxFrame::CalculateTitleForWidth(nsPresContext* aPresContext,
nsRenderingContext& aRenderingContext,
nscoord aWidth)
{
if (mTitle.IsEmpty())
if (mTitle.IsEmpty()) {
mCroppedTitle.Truncate();
return 0;
}
nsRefPtr<nsFontMetrics> fm;
nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm));
@ -993,6 +1002,17 @@ nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext)
// determine (cropped) title which fits in aRect.width and its width
nscoord titleWidth =
CalculateTitleForWidth(presContext, aRenderingContext, textRect.width);
#ifdef ACCESSIBILITY
// Make sure to update the accessible tree in case when cropped title is
// changed.
nsAccessibilityService* accService = GetAccService();
if (accService) {
accService->UpdateLabelValue(PresContext()->PresShell(), mContent,
mCroppedTitle);
}
#endif
// determine if and at which position to put the underline
UpdateAccessIndex();

View File

@ -14,6 +14,8 @@ typedef nsLeafBoxFrame nsTextBoxFrameSuper;
class nsTextBoxFrame : public nsTextBoxFrameSuper
{
public:
NS_DECL_QUERYFRAME_TARGET(nsTextBoxFrame)
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS
virtual nsSize GetPrefSize(nsBoxLayoutState& aBoxLayoutState);
@ -59,6 +61,8 @@ public:
virtual bool ComputesOwnOverflowArea();
void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; }
protected:
friend class nsAsyncAccesskeyUpdate;
friend class nsDisplayXULTextBox;