1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "XULTabAccessible.h"
7 
8 #include "ARIAMap.h"
9 #include "nsAccUtils.h"
10 #include "Relation.h"
11 #include "Role.h"
12 #include "States.h"
13 
14 // NOTE: alphabetically ordered
15 #include "mozilla/dom/Document.h"
16 #include "nsIDOMXULSelectCntrlItemEl.h"
17 #include "nsIDOMXULRelatedElement.h"
18 #include "nsXULElement.h"
19 
20 #include "mozilla/dom/BindingDeclarations.h"
21 
22 using namespace mozilla::a11y;
23 
24 ////////////////////////////////////////////////////////////////////////////////
25 // XULTabAccessible
26 ////////////////////////////////////////////////////////////////////////////////
27 
XULTabAccessible(nsIContent * aContent,DocAccessible * aDoc)28 XULTabAccessible::XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc)
29     : HyperTextAccessibleWrap(aContent, aDoc) {}
30 
31 ////////////////////////////////////////////////////////////////////////////////
32 // XULTabAccessible: Accessible
33 
ActionCount() const34 uint8_t XULTabAccessible::ActionCount() const { return 1; }
35 
ActionNameAt(uint8_t aIndex,nsAString & aName)36 void XULTabAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
37   if (aIndex == eAction_Switch) aName.AssignLiteral("switch");
38 }
39 
DoAction(uint8_t index) const40 bool XULTabAccessible::DoAction(uint8_t index) const {
41   if (index == eAction_Switch) {
42     // XXXbz Could this just FromContent?
43     RefPtr<nsXULElement> tab = nsXULElement::FromNodeOrNull(mContent);
44     if (tab) {
45       tab->Click(mozilla::dom::CallerType::System);
46       return true;
47     }
48   }
49   return false;
50 }
51 
52 ////////////////////////////////////////////////////////////////////////////////
53 // XULTabAccessible: Accessible
54 
NativeRole() const55 role XULTabAccessible::NativeRole() const { return roles::PAGETAB; }
56 
NativeState() const57 uint64_t XULTabAccessible::NativeState() const {
58   // Possible states: focused, focusable, unavailable(disabled), offscreen.
59 
60   // get focus and disable status from base class
61   uint64_t state = AccessibleWrap::NativeState();
62 
63   // Check whether the tab is selected and/or pinned
64   nsCOMPtr<nsIDOMXULSelectControlItemElement> tab =
65       Elm()->AsXULSelectControlItem();
66   if (tab) {
67     bool selected = false;
68     if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected)
69       state |= states::SELECTED;
70 
71     if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::pinned,
72                                            nsGkAtoms::_true, eCaseMatters))
73       state |= states::PINNED;
74   }
75 
76   return state;
77 }
78 
NativeInteractiveState() const79 uint64_t XULTabAccessible::NativeInteractiveState() const {
80   uint64_t state = Accessible::NativeInteractiveState();
81   return (state & states::UNAVAILABLE) ? state : state | states::SELECTABLE;
82 }
83 
RelationByType(RelationType aType) const84 Relation XULTabAccessible::RelationByType(RelationType aType) const {
85   Relation rel = AccessibleWrap::RelationByType(aType);
86   if (aType != RelationType::LABEL_FOR) return rel;
87 
88   // Expose 'LABEL_FOR' relation on tab accessible for tabpanel accessible.
89   ErrorResult rv;
90   nsIContent* parent =
91       mContent->AsElement()->Closest(NS_LITERAL_STRING("tabs"), rv);
92   if (!parent) return rel;
93 
94   nsCOMPtr<nsIDOMXULRelatedElement> tabsElm =
95       parent->AsElement()->AsXULRelated();
96   if (!tabsElm) return rel;
97 
98   RefPtr<mozilla::dom::Element> tabpanelElement;
99   tabsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabpanelElement));
100   if (!tabpanelElement) return rel;
101 
102   rel.AppendTarget(mDoc, tabpanelElement);
103   return rel;
104 }
105 
ApplyARIAState(uint64_t * aState) const106 void XULTabAccessible::ApplyARIAState(uint64_t* aState) const {
107   HyperTextAccessibleWrap::ApplyARIAState(aState);
108   // XUL tab has an implicit ARIA role of tab, so support aria-selected.
109   // Don't use aria::MapToState because that will set the SELECTABLE state
110   // even if the tab is disabled.
111   if (nsAccUtils::IsARIASelected(this)) {
112     *aState |= states::SELECTED;
113   }
114 }
115 
116 ////////////////////////////////////////////////////////////////////////////////
117 // XULTabsAccessible
118 ////////////////////////////////////////////////////////////////////////////////
119 
XULTabsAccessible(nsIContent * aContent,DocAccessible * aDoc)120 XULTabsAccessible::XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc)
121     : XULSelectControlAccessible(aContent, aDoc) {}
122 
NativeRole() const123 role XULTabsAccessible::NativeRole() const { return roles::PAGETABLIST; }
124 
ActionCount() const125 uint8_t XULTabsAccessible::ActionCount() const { return 0; }
126 
Value(nsString & aValue) const127 void XULTabsAccessible::Value(nsString& aValue) const { aValue.Truncate(); }
128 
NativeName(nsString & aName) const129 ENameValueFlag XULTabsAccessible::NativeName(nsString& aName) const {
130   // no name
131   return eNameOK;
132 }
133 
ApplyARIAState(uint64_t * aState) const134 void XULTabsAccessible::ApplyARIAState(uint64_t* aState) const {
135   XULSelectControlAccessible::ApplyARIAState(aState);
136   // XUL tabs has an implicit ARIA role of tablist, so support
137   // aria-multiselectable.
138   MOZ_ASSERT(Elm());
139   aria::MapToState(aria::eARIAMultiSelectable, Elm(), aState);
140 }
141 
142 // XUL tabs is a single selection control and doesn't allow ARIA selection.
143 // However, if aria-multiselectable is used, it becomes a multiselectable
144 // control, where both native and ARIA markup are used to set selection.
145 // Therefore, if aria-multiselectable is set, use the base implementation of
146 // the selection retrieval methods in order to support ARIA selection.
147 // We don't bother overriding the selection setting methods because
148 // current front-end code using XUL tabs doesn't support setting of
149 //  aria-selected by the a11y engine and we still want to be able to set the
150 // primary selected item according to XUL.
151 
SelectedItems(nsTArray<Accessible * > * aItems)152 void XULTabsAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
153   if (nsAccUtils::IsARIAMultiSelectable(this)) {
154     AccessibleWrap::SelectedItems(aItems);
155   } else {
156     XULSelectControlAccessible::SelectedItems(aItems);
157   }
158 }
159 
GetSelectedItem(uint32_t aIndex)160 Accessible* XULTabsAccessible::GetSelectedItem(uint32_t aIndex) {
161   if (nsAccUtils::IsARIAMultiSelectable(this)) {
162     return AccessibleWrap::GetSelectedItem(aIndex);
163   }
164 
165   return XULSelectControlAccessible::GetSelectedItem(aIndex);
166 }
167 
SelectedItemCount()168 uint32_t XULTabsAccessible::SelectedItemCount() {
169   if (nsAccUtils::IsARIAMultiSelectable(this)) {
170     return AccessibleWrap::SelectedItemCount();
171   }
172 
173   return XULSelectControlAccessible::SelectedItemCount();
174 }
175 
IsItemSelected(uint32_t aIndex)176 bool XULTabsAccessible::IsItemSelected(uint32_t aIndex) {
177   if (nsAccUtils::IsARIAMultiSelectable(this)) {
178     return AccessibleWrap::IsItemSelected(aIndex);
179   }
180 
181   return XULSelectControlAccessible::IsItemSelected(aIndex);
182 }
183 
184 ////////////////////////////////////////////////////////////////////////////////
185 // XULTabpanelsAccessible
186 ////////////////////////////////////////////////////////////////////////////////
187 
NativeRole() const188 role XULTabpanelsAccessible::NativeRole() const { return roles::PANE; }
189 
190 ////////////////////////////////////////////////////////////////////////////////
191 // XULTabpanelAccessible
192 ////////////////////////////////////////////////////////////////////////////////
193 
XULTabpanelAccessible(nsIContent * aContent,DocAccessible * aDoc)194 XULTabpanelAccessible::XULTabpanelAccessible(nsIContent* aContent,
195                                              DocAccessible* aDoc)
196     : AccessibleWrap(aContent, aDoc) {}
197 
NativeRole() const198 role XULTabpanelAccessible::NativeRole() const { return roles::PROPERTYPAGE; }
199 
RelationByType(RelationType aType) const200 Relation XULTabpanelAccessible::RelationByType(RelationType aType) const {
201   Relation rel = AccessibleWrap::RelationByType(aType);
202   if (aType != RelationType::LABELLED_BY) return rel;
203 
204   // Expose 'LABELLED_BY' relation on tabpanel accessible for tab accessible.
205   if (!mContent->GetParent()) return rel;
206 
207   nsCOMPtr<nsIDOMXULRelatedElement> tabpanelsElm =
208       mContent->GetParent()->AsElement()->AsXULRelated();
209   if (!tabpanelsElm) return rel;
210 
211   RefPtr<mozilla::dom::Element> tabElement;
212   tabpanelsElm->GetRelatedElement(GetNode(), getter_AddRefs(tabElement));
213   if (!tabElement) return rel;
214 
215   rel.AppendTarget(mDoc, tabElement);
216   return rel;
217 }
218