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
--- 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;