Bug 396166 - xul:label@value accessible should implement nsIAccessibleText, r=tbsaunde, roc
authorAlexander Surkov <surkov.alexander@gmail.com>
Fri, 01 Mar 2013 13:06:16 +0900
changeset 123445 5aa0d1771419443d93a661b9c4ab4f36529d3eb4
parent 123444 1f5912993f1111d978d3d5901b6f7789daa35c1b
child 123446 ba681ef61a3aaba3fc89d3be375029a8c9c190e1
push id24382
push userryanvm@gmail.com
push dateFri, 01 Mar 2013 23:43:19 +0000
treeherdermozilla-central@3362afba690e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde, roc
bugs396166
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 396166 - xul:label@value accessible should implement nsIAccessibleText, r=tbsaunde, roc
accessible/src/base/AccTypes.h
accessible/src/base/Logging.cpp
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibilityService.h
accessible/src/generic/Accessible.h
accessible/src/xul/XULElementAccessibles.cpp
accessible/src/xul/XULElementAccessibles.h
accessible/tests/mochitest/common.js
accessible/tests/mochitest/events.js
accessible/tests/mochitest/events/Makefile.in
accessible/tests/mochitest/events/test_label.xul
accessible/tests/mochitest/text/Makefile.in
accessible/tests/mochitest/text/test_label.xul
accessible/tests/mochitest/tree/test_groupbox.xul
layout/xul/base/src/nsTextBoxFrame.cpp
layout/xul/base/src/nsTextBoxFrame.h
--- a/accessible/src/base/AccTypes.h
+++ b/accessible/src/base/AccTypes.h
@@ -47,16 +47,17 @@ enum AccType {
   /**
    * Other accessible types.
    */
   eApplicationType,
   eImageMapType,
   eMenuPopupType,
   eProgressType,
   eRootType,
+  eXULLabelType,
   eXULTabpanelsType,
   eXULTreeType,
 
   eLastAccType = eXULTreeType
 };
 
 /**
  * Generic accessible type, different accessible classes can share the same
--- a/accessible/src/base/Logging.cpp
+++ b/accessible/src/base/Logging.cpp
@@ -60,17 +60,17 @@ EnableLogging(const char* aModulesStr)
   if (!aModulesStr)
     return;
 
   const char* token = aModulesStr;
   while (*token != '\0') {
     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;
 #endif
         sModules |= sModuleMap[idx].mModule;
         printf("\n\nmodule enabled: %s\n", sModuleMap[idx].mStr);
         break;
       }
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -398,16 +398,34 @@ nsAccessibilityService::UpdateImageMap(n
       // If image map was initialized after we created an accessible (that'll
       // be an image accessible) then recreate it.
       RecreateAccessible(presShell, aImageFrame->GetContent());
     }
   }
 }
 
 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)
 {
   DocAccessible* document = aPresShell->GetDocAccessible();
   if (document) {
     RootAccessible* rootDocument = document->RootAccessible();
     NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!");
     if (rootDocument)
       rootDocument->DocumentActivated(document);
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -100,16 +100,22 @@ public:
                                 bool aHasBullet);
 
   /**
    * Update the image map.
    */
   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.
    */
   void NotifyOfAnchorJumpTo(nsIContent *aTarget);
 
   /**
    * Notify that presshell is activated.
    */
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -41,16 +41,17 @@ class HTMLImageMapAccessible;
 class HTMLLIAccessible;
 class HyperTextAccessible;
 class ImageAccessible;
 class KeyBinding;
 class Relation;
 class TableAccessible;
 class TableCellAccessible;
 class TextLeafAccessible;
+class XULLabelAccessible;
 class XULTreeAccessible;
 
 /**
  * Name type flags.
  */
 enum ENameValueFlag {
   /**
    * Name either
@@ -518,16 +519,19 @@ public:
   const TableCellAccessible* AsTableCell() const
     { return const_cast<Accessible*>(this)->AsTableCell(); }
 
   bool IsTableRow() const { return HasGenericType(eTableRow); }
 
   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; }
   XULTreeAccessible* AsXULTree();
 
   /**
    * Return true if the accessible belongs to the given accessible type.
    */
@@ -887,17 +891,17 @@ protected:
 
   // Data Members
   nsRefPtr<Accessible> mParent;
   nsTArray<nsRefPtr<Accessible> > mChildren;
   int32_t mIndexInParent;
 
   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;
 
   /**
    * Keep in sync with ChildrenFlags, StateFlags and AccTypes.
    */
   uint32_t mChildrenFlags : kChildrenFlagsBits;
   uint32_t mStateFlags : kStateFlagsBits;
   uint32_t mType : kTypeBits;
--- a/accessible/src/xul/XULElementAccessibles.cpp
+++ b/accessible/src/xul/XULElementAccessibles.cpp
@@ -2,47 +2,81 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "XULElementAccessibles.h"
 
 #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 "nsNetUtil.h"
 #include "nsString.h"
-#include "nsNetUtil.h"
+#include "nsTextBoxFrame.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULLabelAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 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
 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;
 }
 
 role
 XULLabelAccessible::NativeRole()
 {
   return roles::LABEL;
 }
@@ -67,16 +101,63 @@ XULLabelAccessible::RelationByType(uint3
       if (parent && parent->Role() == roles::GROUPING)
         rel.AppendTarget(parent);
     }
   }
 
   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
 ////////////////////////////////////////////////////////////////////////////////
 
 XULTooltipAccessible::
   XULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   LeafAccessible(aContent, aDoc)
--- a/accessible/src/xul/XULElementAccessibles.h
+++ b/accessible/src/xul/XULElementAccessibles.h
@@ -1,40 +1,75 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #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.
  */
 class XULLabelAccessible : public HyperTextAccessibleWrap
 {
 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.
  */
 class XULTooltipAccessible : public LeafAccessible
 {
 
 public:
   XULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc);
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -577,23 +577,23 @@ function getLoadContext() {
 /**
  * Return text from clipboard.
  */
 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);
 
   var str = new Object();
   var strLength = new Object();
   trans.getTransferData("text/unicode", str, strLength);
 
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -1543,26 +1543,31 @@ function nofocusChecker(aID)
 /**
  * Text inserted/removed events checker.
  * @param aFromUser  [in, optional] kNotFromUserInput or kFromUserInput
  */
 function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted, aFromUser)
 {
   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,
        "Text was " + changeInfo + " for " + prettyName(aID));
     is(aEvent.modifiedText, modifiedText,
        "Wrong " + changeInfo + " text for " + prettyName(aID));
     if (typeof aFromUser != "undefined")
       is(aEvent.isFromUserInput, aFromUser,
--- a/accessible/tests/mochitest/events/Makefile.in
+++ b/accessible/tests/mochitest/events/Makefile.in
@@ -41,16 +41,17 @@ MOCHITEST_A11Y_FILES =\
 		test_focus_general.xul \
 		test_focus_listcontrols.xul \
 		test_focus_menu.xul \
 		test_focus_name.html \
 		test_focus_selects.html \
 		test_focus_tabbox.xul \
 		test_focus_tree.xul \
 		test_fromUserInput.html \
+		test_label.xul \
 		test_menu.xul \
 		test_mutation.html \
 		test_mutation.xhtml \
 		test_scroll.xul \
 		test_selection_aria.html \
 		test_selection.html \
 		test_selection.xul \
 		test_statechange.html \
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_label.xul
@@ -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>
+
--- a/accessible/tests/mochitest/text/Makefile.in
+++ b/accessible/tests/mochitest/text/Makefile.in
@@ -10,16 +10,17 @@ VPATH		= @srcdir@
 relativesrcdir  = accessible/text
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_A11Y_FILES = \
 		doc.html \
 		test_doc.html \
 		test_hypertext.html \
+		test_label.xul \
 		test_passwords.html \
 		test_selection.html \
 		test_singleline.html \
 		test_whitespaces.html \
 		test_words.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_label.xul
@@ -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>
--- a/accessible/tests/mochitest/tree/test_groupbox.xul
+++ b/accessible/tests/mochitest/tree/test_groupbox.xul
@@ -16,30 +16,23 @@
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     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()
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   ]]>
--- a/layout/xul/base/src/nsTextBoxFrame.cpp
+++ b/layout/xul/base/src/nsTextBoxFrame.cpp
@@ -26,16 +26,20 @@
 #include "nsDisplayList.h"
 #include "nsCSSRendering.h"
 #include "nsIReflowCallback.h"
 #include "nsBoxFrame.h"
 #include "mozilla/Preferences.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Attributes.h"
 
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
 #ifdef IBMBIDI
 #include "nsBidiUtils.h"
 #include "nsBidiPresUtils.h"
 #endif // IBMBIDI
 
 using namespace mozilla;
 
 class nsAccessKeyInfo
@@ -54,16 +58,19 @@ bool nsTextBoxFrame::gInsertSeparatorPre
 nsIFrame*
 NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
     return new (aPresShell) nsTextBoxFrame (aPresShell, 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,
                                  nsIAtom*        aAttribute,
                                  int32_t         aModType)
 {
     bool aResize;
     bool aRedraw;
@@ -591,18 +598,20 @@ nsTextBoxFrame::CalculateUnderline(nsRen
     }
 }
 
 nscoord
 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));
     aRenderingContext.SetFont(fm);
 
     // see if the text will completely fit in the width given
     nscoord titleWidth = nsLayoutUtils::GetStringWidth(this, &aRenderingContext,
                                                        mTitle.get(), mTitle.Length());
@@ -988,16 +997,27 @@ nsTextBoxFrame::CalcDrawRect(nsRendering
     GetBorderAndPadding(borderPadding);
     textRect.Deflate(borderPadding);
 
     // determine (cropped) title and underline position
     nsPresContext* presContext = PresContext();
     // 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();
 
     // make the rect as small as our (cropped) text.
     nscoord outerWidth = textRect.width;
     textRect.width = titleWidth;
 
     // Align our text within the overall rect by checking our text-align property.
--- a/layout/xul/base/src/nsTextBoxFrame.h
+++ b/layout/xul/base/src/nsTextBoxFrame.h
@@ -9,16 +9,18 @@
 
 class nsAccessKeyInfo;
 class nsAsyncAccesskeyUpdate;
 
 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);
   virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState);
   virtual nscoord GetBoxAscent(nsBoxLayoutState& aBoxLayoutState);
   NS_IMETHOD DoLayout(nsBoxLayoutState& aBoxLayoutState);
   virtual void MarkIntrinsicWidthsDirty();
 
@@ -54,16 +56,18 @@ public:
                   const nsRect&        aDirtyRect,
                   nsPoint              aPt,
                   const nscolor*       aOverrideColor);
 
   nsRect GetComponentAlphaBounds();
 
   virtual bool ComputesOwnOverflowArea();
 
+  void GetCroppedTitle(nsString& aTitle) const { aTitle = mCroppedTitle; }
+
 protected:
   friend class nsAsyncAccesskeyUpdate;
   friend class nsDisplayXULTextBox;
   // Should be called only by nsAsyncAccesskeyUpdate.
   // Returns true if accesskey was updated.
   bool UpdateAccesskey(nsWeakFrame& aWeakThis);
   void UpdateAccessTitle();
   void UpdateAccessIndex();