Bug 634004 - Implement accessibility API support for html:details and html:summary elements, r=surkov
authorMarco Zehe <mzehe@mozilla.com>
Wed, 01 Jun 2016 17:07:56 +0200
changeset 338972 f77dcc75a1295c0caff5bb9fd6698185a1355d23
parent 338971 8cac3360b8c7438b635d98d6e0ff77437453af46
child 338973 0a25f9f94e89cc4e31cf312f065b5df2a0f49e12
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov
bugs634004
milestone49.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 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;