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 129876 5aa0d1771419443d93a661b9c4ab4f36529d3eb4
parent 129875 1f5912993f1111d978d3d5901b6f7789daa35c1b
child 129877 ba681ef61a3aaba3fc89d3be375029a8c9c190e1
push idunknown
push userunknown
push dateunknown
reviewerstbsaunde, roc
bugs396166
milestone22.0a1
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();