Bug 634004 - Implement accessibility API support for html:details and html:summary elements, r=surkov draft
authorMarco Zehe <mzehe@mozilla.com>
Wed, 01 Jun 2016 17:07:56 +0200
changeset 373956 9b65bc28483238e2339561e6494ccc2884cb7c42
parent 373814 958fde8511872c5fbf59c3b27a120fbb31a6f9ae
child 522510 ac31e7fc9116e74055b8c471c8a9bb40781800d0
push id19886
push usermzehe@mozilla.com
push dateWed, 01 Jun 2016 15:10:25 +0000
reviewerssurkov
bugs634004
milestone49.0a1
Bug 634004 - Implement accessibility API support for html:details and html:summary elements, r=surkov This implements the roles, states, and action names, but omits the state change event part that is currently made impossible by us recreating the html:summary accessible once it toggles the html:details open state. This is probably due to some reframing causing us to recreate the accessible. Suggest to move that to a separate bug and implement the basics now and the event later. MozReview-Commit-ID: FEi5RIXdkG0
accessible/base/MarkupMap.h
accessible/base/Role.h
accessible/base/RoleMap.h
accessible/base/nsAccessibilityService.cpp
accessible/html/HTMLElementAccessibles.cpp
accessible/html/HTMLElementAccessibles.h
accessible/interfaces/nsIAccessibleRole.idl
accessible/mac/mozAccessible.mm
accessible/tests/mochitest/elm/test_HTMLSpec.html
accessible/tests/mochitest/role.js
--- a/accessible/base/MarkupMap.h
+++ b/accessible/base/MarkupMap.h
@@ -29,16 +29,20 @@ MARKUPMAP(aside,
 MARKUPMAP(blockquote,
           New_HyperText,
           roles::SECTION)
 
 MARKUPMAP(dd,
           New_HTMLDefinition,
           roles::DEFINITION)
 
+MARKUPMAP(details,
+          New_HyperText,
+          roles::DETAILS)
+
 MARKUPMAP(div,
           nullptr,
           roles::SECTION)
 
 MARKUPMAP(dl,
           New_HTMLList,
           roles::DEFINITION_LIST)
 
@@ -308,16 +312,20 @@ MARKUPMAP(q,
           New_HyperText,
           0)
 
 MARKUPMAP(section,
           New_HyperText,
           roles::SECTION,
           Attr(xmlroles, region))
 
+MARKUPMAP(summary,
+          New_HTMLSummary,
+          roles::SUMMARY)
+
 MARKUPMAP(time,
           New_HyperText,
           0,
           Attr(xmlroles, time),
           AttrFromDOM(datetime, datetime))
 
 MARKUPMAP(td,
           New_HTMLTableHeaderCellIfScope,
--- a/accessible/base/Role.h
+++ b/accessible/base/Role.h
@@ -967,17 +967,27 @@ enum Role {
   RADIO_GROUP = 165,
 
   /**
    * A text container exposing brief amount of information. See related
    * TEXT_CONTAINER role.
    */
   TEXT = 166,
 
-  LAST_ROLE = TEXT
+  /**
+   * The html:details element.
+   */
+  DETAILS = 167,
+
+  /**
+   * The html:summary element.
+   */
+  SUMMARY = 168,
+
+  LAST_ROLE = SUMMARY
 };
 
 } // namespace role
 
 typedef enum mozilla::a11y::roles::Role role;
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/base/RoleMap.h
+++ b/accessible/base/RoleMap.h
@@ -1347,8 +1347,24 @@ ROLE(RADIO_GROUP,
 ROLE(TEXT,
      "text",
      ATK_ROLE_STATIC,
      NSAccessibilityGroupRole,
      USE_ROLE_STRING,
      IA2_ROLE_TEXT_FRAME,
      eNameFromSubtreeIfReqRule)
 
+ROLE(DETAILS,
+     "details",
+     ATK_ROLE_PANEL,
+     NSAccessibilityGroupRole,
+     ROLE_SYSTEM_GROUPING,
+     ROLE_SYSTEM_GROUPING,
+     eNoNameRule)
+
+ROLE(SUMMARY,
+     "summary",
+     ATK_ROLE_PUSH_BUTTON,
+     NSAccessibilityGroupRole,
+     ROLE_SYSTEM_PUSHBUTTON,
+     ROLE_SYSTEM_PUSHBUTTON,
+     eNameFromSubtreeRule)
+
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -196,16 +196,19 @@ static Accessible* New_HTMLLabel(nsICont
   { return new HTMLLabelAccessible(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
   { return new HTMLOutputAccessible(aContent, aContext->Document()); }
 
 static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
   { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); }
 
+static Accessible* New_HTMLSummary(nsIContent* aContent, Accessible* aContext)
+  { return new HTMLSummaryAccessible(aContent, aContext->Document()); }
+
 static Accessible*
 New_HTMLTableAccessible(nsIContent* aContent, Accessible* aContext)
   { return new HTMLTableAccessible(aContent, aContext->Document()); }
 
 static Accessible*
 New_HTMLTableRowAccessible(nsIContent* aContent, Accessible* aContext)
   { return new HTMLTableRowAccessible(aContent, aContext->Document()); }
 
--- a/accessible/html/HTMLElementAccessibles.cpp
+++ b/accessible/html/HTMLElementAccessibles.cpp
@@ -9,16 +9,18 @@
 #include "nsAccUtils.h"
 #include "nsIPersistentProperties2.h"
 #include "nsTextEquivUtils.h"
 #include "Relation.h"
 #include "Role.h"
 #include "States.h"
 
 #include "mozilla/dom/HTMLLabelElement.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLHRAccessible
 ////////////////////////////////////////////////////////////////////////////////
 
 role
@@ -111,8 +113,92 @@ Relation
 HTMLOutputAccessible::RelationByType(RelationType aType)
 {
   Relation rel = AccessibleWrap::RelationByType(aType);
   if (aType == RelationType::CONTROLLED_BY)
     rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for));
 
   return rel;
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSummaryAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSummaryAccessible::
+  HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+  HyperTextAccessibleWrap(aContent, aDoc)
+{
+  mGenericTypes |= eButton;
+}
+
+uint8_t
+HTMLSummaryAccessible::ActionCount()
+{
+  return 1;
+}
+
+void
+HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+  if (aIndex != eAction_Click) {
+    return;
+  }
+
+  dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent);
+  if (!summary) {
+    return;
+  }
+
+  dom::HTMLDetailsElement* details = summary->GetDetails();
+  if (!details) {
+    return;
+  }
+
+  if (details->Open()) {
+    aName.AssignLiteral("collapse");
+  } else {
+    aName.AssignLiteral("expand");
+  }
+}
+
+bool
+HTMLSummaryAccessible::DoAction(uint8_t aIndex)
+{
+  if (aIndex != eAction_Click)
+    return false;
+
+  DoCommand();
+  return true;
+}
+
+uint64_t
+HTMLSummaryAccessible::NativeState()
+{
+  uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+  dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent);
+  if (!summary) {
+    return state;
+  }
+
+  dom::HTMLDetailsElement* details = summary->GetDetails();
+  if (!details) {
+    return state;
+  }
+
+  if (details->Open()) {
+    state |= states::EXPANDED;
+  } else {
+    state |= states::COLLAPSED;
+  }
+
+  return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSummaryAccessible: Widgets
+
+bool
+HTMLSummaryAccessible::IsWidget() const
+{
+  return true;
+}
--- a/accessible/html/HTMLElementAccessibles.h
+++ b/accessible/html/HTMLElementAccessibles.h
@@ -87,12 +87,35 @@ public:
 
   // Accessible
   virtual Relation RelationByType(RelationType aType) override;
 
 protected:
   virtual ~HTMLOutputAccessible() {}
 };
 
+/**
+ * Accessible for the HTML summary element.
+ */
+class HTMLSummaryAccessible : public HyperTextAccessibleWrap
+{
+
+public:
+  enum { eAction_Click = 0 };
+
+  HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+  // Accessible
+  virtual uint64_t NativeState() override;
+
+  // ActionAccessible
+  virtual uint8_t ActionCount() override;
+  virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+  virtual bool DoAction(uint8_t aIndex) override;
+
+  // Widgets
+  virtual bool IsWidget() const override;
+};
+
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/interfaces/nsIAccessibleRole.idl
+++ b/accessible/interfaces/nsIAccessibleRole.idl
@@ -3,17 +3,17 @@
  * 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 "nsISupports.idl"
 
 /**
  * Defines cross platform (Gecko) roles.
  */
-[scriptable, uuid(da0c7824-147c-11e5-917c-60a44c717042)]
+[scriptable, uuid(05a9f33f-dcfd-4e7b-b825-138ba784c1f5)]
 interface nsIAccessibleRole : nsISupports
 {
   /**
    * Used when accessible hans't strong defined role.
    */
   const unsigned long ROLE_NOTHING = 0;
 
   /**
@@ -960,9 +960,21 @@ interface nsIAccessibleRole : nsISupport
    */
   const unsigned long ROLE_RADIO_GROUP = 165;
 
   /**
    * A text container exposing brief amount of information. See related
    * TEXT_CONTAINER role.
    */
   const unsigned long ROLE_TEXT = 166;
+
+  /**
+   * A text container exposing brief amount of information. See related
+   * DETAILS role.
+   */
+  const unsigned long ROLE_DETAILS = 167;
+
+  /**
+   * A text container exposing brief amount of information. See related
+   * SUMMARY role.
+   */
+  const unsigned long ROLE_SUMMARY = 168;
 };
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -939,16 +939,22 @@ ConvertToNSArray(nsTArray<ProxyAccessibl
       return @"AXApplicationAlert";
 
     case roles::SEPARATOR:
       return @"AXContentSeparator";
 
     case roles::PROPERTYPAGE:
       return @"AXTabPanel";
 
+    case roles::DETAILS:
+      return @"AXDetails";
+
+    case roles::SUMMARY:
+      return @"AXSummary";
+
     default:
       break;
   }
 
   return nil;
 }
 
 struct RoleDescrMap
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -367,19 +367,45 @@
         children: [
           { role: ROLE_TEXT_LEAF }, // plain text
           { role: ROLE_TEXT_LEAF } // HTML:del text
         ]
       };
       testElm("del_container", obj);
 
       //////////////////////////////////////////////////////////////////////////
-      // HTML:details
+      // HTML:details with open state
+
+      obj = {
+        role: ROLE_DETAILS,
+        children: [
+          {
+            role: ROLE_SUMMARY,
+            states: STATE_EXPANDED,
+            actions: "collapse"
+         },
+         { role: ROLE_PARAGRAPH }
+        ]
+      };
+      testElm("details", obj);
 
-      ok(isAccessible("details"), "details element is not accessible");
+      //////////////////////////////////////////////////////////////////////////
+      // HTML:details with closed (default) state
+
+      obj = {
+        role: ROLE_DETAILS,
+        children: [
+          {
+            role: ROLE_SUMMARY,
+            states: STATE_COLLAPSED,
+            actions: "expand"
+         }
+        ]
+      };
+      testElm("details_closed", obj);
 
       //////////////////////////////////////////////////////////////////////////
       // HTML:dfn contained by paragraph
 
       obj = {
         role: ROLE_PARAGRAPH,
         textAttrs: {
           0: { "font-style": "italic" },
@@ -1398,16 +1424,21 @@
 
   <p id="del_container">normal<del>Removed</del></p>
 
   <details id="details" open="open">
     <summary>Information</summary>
     <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
   </details>
 
+  <details id="details_closed">
+    <summary>Information</summary>
+    <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
+  </details>
+
   <p id="dfn_container"><dfn id="def-internet">The Internet</dfn> is a global
     system of interconnected networks that use the Internet Protocol Suite (TCP/IP)
     to serve billions of users worldwide.</p>
 
   <dialog id="dialog" open="true">This is a dialog</dialog>
 
   <div id="div">div</div>
 
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -14,16 +14,17 @@ const ROLE_CHECKBUTTON = nsIAccessibleRo
 const ROLE_CHECK_MENU_ITEM = nsIAccessibleRole.ROLE_CHECK_MENU_ITEM;
 const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW;
 const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX;
 const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST;
 const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION;
 const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER;
 const ROLE_DEFINITION = nsIAccessibleRole.ROLE_DEFINITION;
 const ROLE_DEFINITION_LIST = nsIAccessibleRole.ROLE_DEFINITION_LIST;
+const ROLE_DETAILS = nsIAccessibleRole.ROLE_DETAILS;
 const ROLE_DIAGRAM = nsIAccessibleRole.ROLE_DIAGRAM;
 const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG;
 const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT;
 const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT;
 const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY;
 const ROLE_EQUATION = nsIAccessibleRole.ROLE_EQUATION;
 const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE;
 const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER;
@@ -100,16 +101,17 @@ const ROLE_ROW = nsIAccessibleRole.ROLE_
 const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER;
 const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR;
 const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
 const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR;
 const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON;
 const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT;
 const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR;
+const ROLE_SUMMARY = nsIAccessibleRole.ROLE_SUMMARY;
 const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH;
 const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
 const ROLE_TERM = nsIAccessibleRole.ROLE_TERM;
 const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT;
 const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
 const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF;
 const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
 const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR;