Bug 559764 - make input@type=range accessible, r=tbsaunde, roc, smaug
authorAlexander Surkov <surkov.alexander@gmail.com>
Sun, 28 Apr 2013 09:54:54 +0900
changeset 141133 f6b9554b2b1ca069b17b85e749c84540fdaa8e7e
parent 141132 cf9e342c1e7cd23e184ae0c27f9b5e534f5a62fd
child 141134 b37edb9912247d0f48a7884eef1b4e9f62a4faf5
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde, roc, smaug
bugs559764
milestone23.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 559764 - make input@type=range accessible, r=tbsaunde, roc, smaug
accessible/src/base/AccTypes.h
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibilityService.h
accessible/src/html/HTMLFormControlAccessible.cpp
accessible/src/html/HTMLFormControlAccessible.h
accessible/tests/mochitest/events/test_valuechange.html
accessible/tests/mochitest/tree/test_formctrl.html
accessible/tests/mochitest/value/Makefile.in
accessible/tests/mochitest/value/test_range.html
content/html/content/src/HTMLInputElement.h
layout/forms/nsRangeFrame.cpp
layout/forms/nsRangeFrame.h
--- a/accessible/src/base/AccTypes.h
+++ b/accessible/src/base/AccTypes.h
@@ -29,16 +29,17 @@ enum AccType {
   eHTMLGroupboxType,
   eHTMLHRType,
   eHTMLImageMapType,
   eHTMLLabelType,
   eHTMLLiType,
   eHTMLSelectListType,
   eHTMLMediaType,
   eHTMLRadioButtonType,
+  eHTMLRangeType,
   eHTMLTableType,
   eHTMLTableCellType,
   eHTMLTableRowType,
   eHTMLTextFieldType,
   eHyperTextType,
   eImageType,
   eOuterDocType,
   ePluginType,
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -421,16 +421,30 @@ nsAccessibilityService::TreeViewChanged(
       XULTreeAccessible* treeAcc = accessible->AsXULTree();
       if (treeAcc) 
         treeAcc->TreeViewChanged(aView);
     }
   }
 }
 
 void
+nsAccessibilityService::RangeValueChanged(nsIPresShell* aPresShell,
+                                          nsIContent* aContent)
+{
+  DocAccessible* document = GetDocAccessible(aPresShell);
+  if (document) {
+    Accessible* accessible = document->GetAccessible(aContent);
+    if (accessible) {
+      document->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
+                                 accessible);
+    }
+  }
+}
+
+void
 nsAccessibilityService::UpdateListBullet(nsIPresShell* aPresShell,
                                          nsIContent* aHTMLListItemContent,
                                          bool aHasBullet)
 {
   DocAccessible* document = GetDocAccessible(aPresShell);
   if (document) {
     Accessible* accessible = document->GetAccessible(aHTMLListItemContent);
     if (accessible) {
@@ -1504,16 +1518,19 @@ nsAccessibilityService::CreateAccessible
       newAcc = new HTMLSelectListAccessible(aContent, document);
       break;
     case eHTMLMediaType:
       newAcc = new EnumRoleAccessible(aContent, document, roles::GROUPING);
       break;
     case eHTMLRadioButtonType:
       newAcc = new HTMLRadioButtonAccessible(aContent, document);
       break;
+    case eHTMLRangeType:
+      newAcc = new HTMLRangeAccessible(aContent, document);
+      break;
     case eHTMLTableType:
       newAcc = new HTMLTableAccessibleWrap(aContent, document);
       break;
     case eHTMLTableCellType:
       // Accessible HTML table cell should be a child of accessible HTML table
       // or its row (CSS HTML tables are polite to the used markup at
       // certain degree).
       if (aContext->IsHTMLTableRow() || aContext->IsHTMLTable())
--- a/accessible/src/base/nsAccessibilityService.h
+++ b/accessible/src/base/nsAccessibilityService.h
@@ -95,16 +95,21 @@ public:
 
   /**
    * Update XUL:tree accessible tree when treeview is changed.
    */
   void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent,
                        nsITreeView* aView);
 
   /**
+   * Notify of input@type="element" value change.
+   */
+  void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent);
+
+  /**
    * Update list bullet accessible.
    */
   virtual void UpdateListBullet(nsIPresShell* aPresShell,
                                 nsIContent* aHTMLListItemContent,
                                 bool aHasBullet);
 
   /**
    * Update the image map.
--- a/accessible/src/html/HTMLFormControlAccessible.cpp
+++ b/accessible/src/html/HTMLFormControlAccessible.cpp
@@ -538,16 +538,101 @@ HTMLFileInputAccessible::HandleAccEvent(
                                                           : eNoUserInput));
       nsEventShell::FireEvent(childEvent);
     }
   }
 
   return NS_OK;
 }
 
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLRangeAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED1(HTMLRangeAccessible, LeafAccessible,
+                             nsIAccessibleValue)
+
+role
+HTMLRangeAccessible::NativeRole()
+{
+  return roles::SLIDER;
+}
+
+bool
+HTMLRangeAccessible::IsWidget() const
+{
+  return true;
+}
+
+void
+HTMLRangeAccessible::Value(nsString& aValue)
+{
+  LeafAccessible::Value(aValue);
+  if (!aValue.IsEmpty())
+    return;
+
+  HTMLInputElement::FromContent(mContent)->GetValue(aValue);
+}
+
+NS_IMETHODIMP
+HTMLRangeAccessible::GetMaximumValue(double* aMaximumValue)
+{
+  nsresult rv = LeafAccessible::GetMaximumValue(aMaximumValue);
+  if (rv != NS_OK_NO_ARIA_VALUE)
+    return rv;
+
+  *aMaximumValue = HTMLInputElement::FromContent(mContent)->GetMaximum();
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+HTMLRangeAccessible::GetMinimumValue(double* aMinimumValue)
+{
+  nsresult rv = LeafAccessible::GetMinimumValue(aMinimumValue);
+  if (rv != NS_OK_NO_ARIA_VALUE)
+    return rv;
+
+  *aMinimumValue = HTMLInputElement::FromContent(mContent)->GetMinimum();
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP
+HTMLRangeAccessible::GetMinimumIncrement(double* aMinimumIncrement)
+{
+  nsresult rv = LeafAccessible::GetMinimumIncrement(aMinimumIncrement);
+  if (rv != NS_OK_NO_ARIA_VALUE)
+    return rv;
+
+  *aMinimumIncrement = HTMLInputElement::FromContent(mContent)->GetStep();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLRangeAccessible::GetCurrentValue(double* aCurrentValue)
+{
+  nsresult rv = LeafAccessible::GetCurrentValue(aCurrentValue);
+  if (rv != NS_OK_NO_ARIA_VALUE)
+    return rv;
+
+  *aCurrentValue = HTMLInputElement::FromContent(mContent)->GetValueAsDouble();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLRangeAccessible::SetCurrentValue(double aValue)
+{
+  ErrorResult er;
+  HTMLInputElement::FromContent(mContent)->SetValueAsNumber(aValue, er);
+  return er.ErrorCode();
+}
+
+
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLGroupboxAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 HTMLGroupboxAccessible::
   HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   HyperTextAccessibleWrap(aContent, aDoc)
 {
--- a/accessible/src/html/HTMLFormControlAccessible.h
+++ b/accessible/src/html/HTMLFormControlAccessible.h
@@ -140,16 +140,41 @@ class HTMLFileInputAccessible : public H
 public:
   HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
 };
 
+
+/**
+  * Used for input@type="range" element.
+  */
+class HTMLRangeAccessible : public LeafAccessible
+{
+public:
+  HTMLRangeAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+    LeafAccessible(aContent, aDoc)
+  {
+    mStateFlags |= eHasNumericValue;
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIACCESSIBLEVALUE
+
+  // Accessible
+  virtual void Value(nsString& aValue);
+  virtual mozilla::a11y::role NativeRole();
+
+  // Widgets
+  virtual bool IsWidget() const;
+};
+
+
 /**
  * Accessible for HTML fieldset element.
  */
 class HTMLGroupboxAccessible : public HyperTextAccessibleWrap
 {
 public:
   HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
--- a/accessible/tests/mochitest/events/test_valuechange.html
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -3,16 +3,18 @@
 <head>
   <title>Accessible value change events testing</title>
 
   <link rel="stylesheet" type="text/css"
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <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="../events.js"></script>
 
   <script type="application/javascript"
           src="../value.js"></script>
@@ -74,53 +76,80 @@
       }
 
       this.getID = function changeValue_getID()
       {
         return prettyName(aID) + " value changed";
       }
     }
 
-    function changeProcessValue(aID, aValue) {
-        this.DOMNode = getNode(aID);
+    function changeProgressValue(aID, aValue)
+    {
+      this.DOMNode = getNode(aID);
 
-        this.invoke = function changeProcessValue_invoke() {
-            this.DOMNode.value = aValue;
-        }
+      this.invoke = function changeProgressValue_invoke()
+      {
+         this.DOMNode.value = aValue;
+      }
+
+      this.check = function changeProgressValue_check()
+      {
+        var acc = getAccessible(this.DOMNode);
+        is(acc.value, aValue+"%", "Wrong value for " + prettyName(aID));
+      }
 
-        this.check = function changeProcessValue_check() {
-            var acc = getAccessible(this.DOMNode);
-            is(acc.value, aValue+"%", "Wrong value for " + prettyName(aID));
-        }
+      this.getID = function changeProgressValue_getID()
+      {
+        return prettyName(aID) + " value changed";
+      }
+    }
+
+    function changeRangeValue(aID)
+    {
+      this.DOMNode = getNode(aID);
 
-        this.getID = function changeProcessValue_getID() {
-            return prettyName(aID) + " value changed";
-        }
+      this.invoke = function changeRangeValue_invoke()
+      {
+        synthesizeMouse(getNode(aID), 5, 5, { });
+      }
+
+      this.finalCheck = function changeRangeValue_finalCheck()
+      {
+        var acc = getAccessible(this.DOMNode);
+        is(acc.value, "0", "Wrong value for " + prettyName(aID));
+      }
+
+      this.getID = function changeRangeValue_getID()
+      {
+        return prettyName(aID) + " range value changed";
+      }
     }
 
     function doTests()
     {
       // Test initial values
       testValue("slider_vn", "5", 5, 0, 1000, 0);
       testValue("slider_vnvt", "plain", 0, 0, 5, 0);
       testValue("slider_vt", "hi", 0, 0, 3, 0);
       testValue("scrollbar", "5", 5, 0, 1000, 0);
       testValue("progress", "22%", 22, 0, 100, 0);
+      testValue("range", "6", 6, 0, 10, 1);
 
       // Test value change events
       gQueue = new eventQueue(nsIAccessibleEvent.EVENT_VALUE_CHANGE);
 
       gQueue.push(new changeARIAValue("slider_vn", "6", undefined));
       gQueue.push(new changeARIAValue("slider_vt", undefined, "hey!"));
       gQueue.push(new changeARIAValue("slider_vnvt", "3", "sweet"));
       gQueue.push(new changeARIAValue("scrollbar", "6", undefined));
 
       gQueue.push(new changeValue("combobox", "hello"));
 
-      gQueue.push(new changeProcessValue("progress", "50"));
+      gQueue.push(new changeProgressValue("progress", "50"));
+      gQueue.push(new changeRangeValue("range"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
@@ -133,16 +162,21 @@
     Mozilla Bug 478032
   </a>
   <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289"
     title="We dont expose new aria role 'scrollbar' and property aria-orientation">
    Mozilla Bug 529289
   </a>
   <a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+    title="Make HTML5 input@type=range element accessible">
+   Mozilla Bug 559764
+  </a>
+  <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=703202"
     title="ARIA comboboxes don't fire value change events">
    Mozilla Bug 703202
   </a>
   <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=761901"
     title=" HTML5 progress accessible should fire value change event">
    Mozilla Bug 761901
@@ -170,10 +204,13 @@
        aria-valuemin="0" aria-valuemax="1000">slider</div>
 
   <!-- ARIA combobox -->
   <input id="combobox" role="combobox" aria-autocomplete="inline">
 
   <!-- progress bar -->
   <progress id="progress" value="22" max="100"></progress>
 
+  <!-- input@type="range" -->
+  <input type="range" id="range" min="0" max="10" value="6">
+
 </body>
 </html>
--- a/accessible/tests/mochitest/tree/test_formctrl.html
+++ b/accessible/tests/mochitest/tree/test_formctrl.html
@@ -54,16 +54,21 @@
         children: [
           {
             role: ROLE_STATICTEXT
           }
         ]
       };
       testAccessibleTree("image_submit", accTree);
 
+      // input@type="range"
+      accTree = { SLIDER: [ ] };
+      testAccessibleTree("range", accTree);
+
+      // output
       accTree = {
         role: ROLE_SECTION,
         children: [
           {
             role: ROLE_TEXT_LEAF
           }
         ]
       };
@@ -76,35 +81,42 @@
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
 
   <a target="_blank"
      title="Fix O(n^2) access to all the children of a container"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
-    Mozilla Bug 342045
+    Bug 342045
   </a>
   <a target="_blank"
     title="add test for role of input type='image'"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=524521">
-     Mozilla Bug 524521
+     Bug 524521
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
      title="make HTML <output> accessible">
-    Mozilla Bug 558036
+    Bug 558036
+  </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+     title="make HTML5 input@type=range element accessible">
+    Bug 559764
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <input type="checkbox" id="checkbox">
   <input type="radio" id="radio">
   <input type="button" value="button" id="btn1">
   <button id="btn2">button</button>
 
   <input type="submit" id="submit">
   <input type="image" id="image_submit">
+  <input type="range" id="range">
   <output id="output">1337</output>
+
 </body>
 </html>
--- a/accessible/tests/mochitest/value/Makefile.in
+++ b/accessible/tests/mochitest/value/Makefile.in
@@ -10,11 +10,12 @@ VPATH		= @srcdir@
 relativesrcdir	= @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_A11Y_FILES =\
 		test_general.html \
 		test_progress.html \
 		test_progress.xul \
+		test_range.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_range.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+  <title>nsIAccessible value testing for input@type=range element</title>
+
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <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="../value.js"></script>
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/chrome-harness.js"></script>
+
+  <script type="application/javascript">
+    function doTest()
+    {
+      // HTML5 progress element tests
+      testValue("range", "50", 50, 0, 100, 1);
+      testValue("range_value", "1", 1, 0, 100, 1);
+      testValue("range_step", "50", 50, 0, 100, 1);
+      testValue("range_min42", "71", 71, 42, 100, 1);
+      testValue("range_max42", "21", 21, 0, 42, 1);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+     title="make HTML5 input@type=range element accessible">
+    Bug 559764
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+  <pre id="test">
+  </pre>
+
+  <!-- HTML5 input@type=range element -->
+  <input type="range" id="range">
+  <input type="range" id="range_value" value="1">
+  <input type="range" id="range_step" step="1">
+  <input type="range" id="range_min42" min="42">
+  <input type="range" id="range_max42" max="42">
+</body>
+</html>
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -597,16 +597,24 @@ public:
     aRv = ApplyStep(aN);
   }
 
   void StepDown(int32_t aN, ErrorResult& aRv)
   {
     aRv = ApplyStep(-aN);
   }
 
+  /**
+   * Returns the current step value.
+   * Returns kStepAny if the current step is "any" string.
+   *
+   * @return the current step value.
+   */
+  double GetStep() const;
+
   void GetValidationMessage(nsAString& aValidationMessage, ErrorResult& aRv);
 
   // XPCOM GetCustomVisibility() is OK
 
   // XPCOM Select() is OK
 
   int32_t GetSelectionStart(ErrorResult& aRv);
   void SetSelectionStart(int32_t aValue, ErrorResult& aRv);
@@ -1023,24 +1031,16 @@ protected:
    /**
     * Get the step scale value for the current type.
     * See:
     * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
     */
   double GetStepScaleFactor() const;
 
   /**
-   * Returns the current step value.
-   * Returns kStepAny if the current step is "any" string.
-   *
-   * @return the current step value.
-   */
-  double GetStep() const;
-
-  /**
    * Return the base used to compute if a value matches step.
    * Basically, it's the min attribute if present and a default value otherwise.
    *
    * @return The step base.
    */
   double GetStepBase() const;
 
   /**
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -21,16 +21,20 @@
 #include "nsPresContext.h"
 #include "nsNodeInfoManager.h"
 #include "nsRenderingContext.h"
 #include "mozilla/dom/Element.h"
 #include "prtypes.h"
 
 #include <algorithm>
 
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
 #define LONG_SIDE_TO_SHORT_SIDE_RATIO 10
 
 using namespace mozilla;
 
 nsIFrame*
 NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsRangeFrame(aContext);
@@ -344,16 +348,24 @@ nsRangeFrame::ReflowAnonymousContent(nsP
 
     DoUpdateRangeProgressFrame(rangeProgressFrame, nsSize(aDesiredSize.width,
                                                           aDesiredSize.height));
   }
 
   return NS_OK;
 }
 
+#ifdef ACCESSIBILITY
+a11y::AccType
+nsRangeFrame::AccessibleType()
+{
+  return a11y::eHTMLRangeType;
+}
+#endif
+
 double
 nsRangeFrame::GetValueAsFractionOfRange()
 {
   MOZ_ASSERT(mContent->IsHTML(nsGkAtoms::input), "bad cast");
   dom::HTMLInputElement* input = static_cast<dom::HTMLInputElement*>(mContent);
 
   MOZ_ASSERT(input->GetType() == NS_FORM_INPUT_RANGE);
 
@@ -484,16 +496,24 @@ nsRangeFrame::UpdateForValueChange()
   if (thumbFrame) {
     DoUpdateThumbPosition(thumbFrame, GetSize());
   }
   if (IsThemed()) {
     // We don't know the exact dimensions or location of the thumb when native
     // theming is applied, so we just repaint the entire range.
     InvalidateFrame();
   }
+
+#ifdef ACCESSIBILITY
+  nsAccessibilityService* accService = nsIPresShell::AccService();
+  if (accService) {
+    accService->RangeValueChanged(PresContext()->PresShell(), mContent);
+  }
+#endif
+
   SchedulePaint();
 }
 
 void
 nsRangeFrame::DoUpdateThumbPosition(nsIFrame* aThumbFrame,
                                     const nsSize& aRangeSize)
 {
   MOZ_ASSERT(aThumbFrame);
--- a/layout/forms/nsRangeFrame.h
+++ b/layout/forms/nsRangeFrame.h
@@ -47,16 +47,20 @@ public:
 #ifdef DEBUG
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE {
     return MakeFrameName(NS_LITERAL_STRING("Range"), aResult);
   }
 #endif
 
   virtual bool IsLeaf() const MOZ_OVERRIDE { return true; }
 
+#ifdef ACCESSIBILITY
+  virtual mozilla::a11y::AccType AccessibleType() MOZ_OVERRIDE;
+#endif
+
   // nsIAnonymousContentCreator
   virtual nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) MOZ_OVERRIDE;
   virtual void AppendAnonymousContentTo(nsBaseContentList& aElements,
                                         uint32_t aFilter) MOZ_OVERRIDE;
 
   NS_IMETHOD AttributeChanged(int32_t  aNameSpaceID,
                               nsIAtom* aAttribute,
                               int32_t  aModType) MOZ_OVERRIDE;