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/. */
6 #include "XULFormControlAccessible.h"
8 #include "LocalAccessible-inl.h"
9 #include "HTMLFormControlAccessible.h"
10 #include "nsAccUtils.h"
11 #include "DocAccessible.h"
13 #include "mozilla/a11y/Role.h"
15 #include "TreeWalker.h"
16 #include "XULMenuAccessible.h"
18 #include "nsIDOMXULButtonElement.h"
19 #include "nsIDOMXULMenuListElement.h"
20 #include "nsIDOMXULRadioGroupElement.h"
21 #include "nsIDOMXULSelectCntrlItemEl.h"
23 #include "nsITextControlFrame.h"
24 #include "nsMenuPopupFrame.h"
25 #include "nsNameSpaceManager.h"
26 #include "mozilla/dom/Element.h"
28 using namespace mozilla::a11y
;
30 ////////////////////////////////////////////////////////////////////////////////
31 // XULButtonAccessible
32 ////////////////////////////////////////////////////////////////////////////////
34 XULButtonAccessible::XULButtonAccessible(nsIContent
* aContent
,
36 : AccessibleWrap(aContent
, aDoc
) {
38 mGenericTypes
|= eMenuButton
;
40 mGenericTypes
|= eButton
;
44 XULButtonAccessible::~XULButtonAccessible() {}
46 ////////////////////////////////////////////////////////////////////////////////
47 // XULButtonAccessible: nsISupports
49 ////////////////////////////////////////////////////////////////////////////////
50 // XULButtonAccessible: nsIAccessible
52 bool XULButtonAccessible::HasPrimaryAction() const { return true; }
54 void XULButtonAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
55 if (aIndex
== eAction_Click
) aName
.AssignLiteral("press");
58 ////////////////////////////////////////////////////////////////////////////////
59 // XULButtonAccessible: LocalAccessible
61 role
XULButtonAccessible::NativeRole() const {
62 // Buttons can be checked; they simply appear pressed in rather than checked.
63 // In this case, we must expose them as toggle buttons.
64 nsCOMPtr
<nsIDOMXULButtonElement
> xulButtonElement
= Elm()->AsXULButton();
65 if (xulButtonElement
) {
67 xulButtonElement
->GetType(type
);
68 if (type
.EqualsLiteral("checkbox") || type
.EqualsLiteral("radio")) {
69 return roles::TOGGLE_BUTTON
;
72 return roles::PUSHBUTTON
;
75 uint64_t XULButtonAccessible::NativeState() const {
76 // Possible states: focused, focusable, unavailable(disabled).
78 // get focus and disable status from base class
79 uint64_t state
= LocalAccessible::NativeState();
81 nsCOMPtr
<nsIDOMXULButtonElement
> xulButtonElement
= Elm()->AsXULButton();
82 if (xulButtonElement
) {
83 // Some buttons can have their checked state set without being of type
84 // checkbox or radio. Expose the pressed state unconditionally.
86 xulButtonElement
->GetChecked(&checked
);
88 state
|= states::PRESSED
;
92 if (ContainsMenu()) state
|= states::HASPOPUP
;
94 if (mContent
->AsElement()->HasAttr(nsGkAtoms::_default
)) {
95 state
|= states::DEFAULT
;
101 bool XULButtonAccessible::AttributeChangesState(nsAtom
* aAttribute
) {
102 if (aAttribute
== nsGkAtoms::checked
) {
105 return AccessibleWrap::AttributeChangesState(aAttribute
);
108 ////////////////////////////////////////////////////////////////////////////////
109 // XULButtonAccessible: Widgets
111 bool XULButtonAccessible::IsWidget() const { return true; }
113 bool XULButtonAccessible::IsActiveWidget() const {
114 return FocusMgr()->HasDOMFocus(mContent
);
117 bool XULButtonAccessible::AreItemsOperable() const {
118 if (IsMenuButton()) {
119 LocalAccessible
* menuPopup
= mChildren
.SafeElementAt(0, nullptr);
121 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(menuPopup
->GetFrame());
122 return menuPopupFrame
->IsOpen();
125 return false; // no items
128 bool XULButtonAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
129 // In general XUL buttons should not have accessible children. However:
131 // menu buttons can have popup accessibles (@type="menu" or
133 aEl
->IsXULElement(nsGkAtoms::menupopup
) ||
134 aEl
->IsXULElement(nsGkAtoms::popup
) ||
135 // A XUL button can be labelled by a direct child text node, so we need to
136 // allow that as a child so it will be picked up when computing name from
138 (aEl
->IsText() && aEl
->GetParent() == mContent
);
141 ////////////////////////////////////////////////////////////////////////////////
142 // XULButtonAccessible protected
144 bool XULButtonAccessible::ContainsMenu() const {
145 return mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
146 nsGkAtoms::menu
, eCaseMatters
);
149 ////////////////////////////////////////////////////////////////////////////////
150 // XULDropmarkerAccessible
151 ////////////////////////////////////////////////////////////////////////////////
153 XULDropmarkerAccessible::XULDropmarkerAccessible(nsIContent
* aContent
,
155 : LeafAccessible(aContent
, aDoc
) {}
157 bool XULDropmarkerAccessible::HasPrimaryAction() const { return true; }
159 bool XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen
) const {
162 nsIContent
* parent
= mContent
->GetFlattenedTreeParent();
165 nsCOMPtr
<nsIDOMXULButtonElement
> parentButtonElement
=
166 parent
->AsElement()->AsXULButton();
167 if (parentButtonElement
) {
168 parentButtonElement
->GetOpen(&isOpen
);
169 if (aToggleOpen
) parentButtonElement
->SetOpen(!isOpen
);
173 nsCOMPtr
<nsIDOMXULMenuListElement
> parentMenuListElement
=
174 parent
->AsElement()->AsXULMenuList();
175 if (parentMenuListElement
) {
176 parentMenuListElement
->GetOpen(&isOpen
);
177 if (aToggleOpen
) parentMenuListElement
->SetOpen(!isOpen
);
180 parent
= parent
->GetFlattenedTreeParent();
186 void XULDropmarkerAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
188 if (aIndex
== eAction_Click
) {
189 if (DropmarkerOpen(false)) {
190 aName
.AssignLiteral("close");
192 aName
.AssignLiteral("open");
197 bool XULDropmarkerAccessible::DoAction(uint8_t index
) const {
198 if (index
== eAction_Click
) {
199 DropmarkerOpen(true); // Reverse the open attribute
205 role
XULDropmarkerAccessible::NativeRole() const { return roles::PUSHBUTTON
; }
207 uint64_t XULDropmarkerAccessible::NativeState() const {
208 return DropmarkerOpen(false) ? states::PRESSED
: 0;
211 ////////////////////////////////////////////////////////////////////////////////
212 // XULGroupboxAccessible
213 ////////////////////////////////////////////////////////////////////////////////
215 XULGroupboxAccessible::XULGroupboxAccessible(nsIContent
* aContent
,
217 : AccessibleWrap(aContent
, aDoc
) {}
219 role
XULGroupboxAccessible::NativeRole() const { return roles::GROUPING
; }
221 ENameValueFlag
XULGroupboxAccessible::NativeName(nsString
& aName
) const {
222 // XXX: we use the first related accessible only.
223 LocalAccessible
* label
=
224 RelationByType(RelationType::LABELLED_BY
).LocalNext();
225 if (label
) return label
->Name(aName
);
230 Relation
XULGroupboxAccessible::RelationByType(RelationType aType
) const {
231 Relation rel
= AccessibleWrap::RelationByType(aType
);
233 // The label for xul:groupbox is generated from the first xul:label
234 if (aType
== RelationType::LABELLED_BY
&& ChildCount() > 0) {
235 LocalAccessible
* childAcc
= LocalChildAt(0);
236 if (childAcc
->Role() == roles::LABEL
&&
237 childAcc
->GetContent()->IsXULElement(nsGkAtoms::label
)) {
238 rel
.AppendTarget(childAcc
);
245 ////////////////////////////////////////////////////////////////////////////////
246 // XULRadioButtonAccessible
247 ////////////////////////////////////////////////////////////////////////////////
249 XULRadioButtonAccessible::XULRadioButtonAccessible(nsIContent
* aContent
,
251 : RadioButtonAccessible(aContent
, aDoc
) {}
253 uint64_t XULRadioButtonAccessible::NativeState() const {
254 uint64_t state
= LeafAccessible::NativeState();
255 state
|= states::CHECKABLE
;
257 nsCOMPtr
<nsIDOMXULSelectControlItemElement
> radioButton
=
258 Elm()->AsXULSelectControlItem();
260 bool selected
= false; // Radio buttons can be selected
261 radioButton
->GetSelected(&selected
);
263 state
|= states::CHECKED
;
270 uint64_t XULRadioButtonAccessible::NativeInteractiveState() const {
271 return NativelyUnavailable() ? states::UNAVAILABLE
: states::FOCUSABLE
;
274 ////////////////////////////////////////////////////////////////////////////////
275 // XULRadioButtonAccessible: Widgets
277 LocalAccessible
* XULRadioButtonAccessible::ContainerWidget() const {
281 ////////////////////////////////////////////////////////////////////////////////
282 // XULRadioGroupAccessible
283 ////////////////////////////////////////////////////////////////////////////////
287 * The Radio Group proxies for the Radio Buttons themselves. The Group gets
288 * focus whereas the Buttons do not. So we only have an accessible object for
289 * this for the purpose of getting the proper RadioButton. Need this here to
290 * avoid circular reference problems when navigating the accessible tree and
291 * for getting to the radiobuttons.
294 XULRadioGroupAccessible::XULRadioGroupAccessible(nsIContent
* aContent
,
296 : XULSelectControlAccessible(aContent
, aDoc
) {}
298 role
XULRadioGroupAccessible::NativeRole() const { return roles::RADIO_GROUP
; }
300 uint64_t XULRadioGroupAccessible::NativeInteractiveState() const {
301 // The radio group is not focusable. Sometimes the focus controller will
302 // report that it is focused. That means that the actual selected radio button
303 // should be considered focused.
304 return NativelyUnavailable() ? states::UNAVAILABLE
: 0;
307 ////////////////////////////////////////////////////////////////////////////////
308 // XULRadioGroupAccessible: Widgets
310 bool XULRadioGroupAccessible::IsWidget() const { return true; }
312 bool XULRadioGroupAccessible::IsActiveWidget() const {
313 return FocusMgr()->HasDOMFocus(mContent
);
316 bool XULRadioGroupAccessible::AreItemsOperable() const { return true; }
318 LocalAccessible
* XULRadioGroupAccessible::CurrentItem() const {
319 if (!mSelectControl
) {
323 RefPtr
<dom::Element
> currentItemElm
;
324 nsCOMPtr
<nsIDOMXULRadioGroupElement
> group
=
325 mSelectControl
->AsXULRadioGroup();
327 group
->GetFocusedItem(getter_AddRefs(currentItemElm
));
330 if (currentItemElm
) {
331 DocAccessible
* document
= Document();
333 return document
->GetAccessible(currentItemElm
);
340 void XULRadioGroupAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
341 if (!mSelectControl
) {
345 nsCOMPtr
<dom::Element
> itemElm
= aItem
->Elm();
346 nsCOMPtr
<nsIDOMXULRadioGroupElement
> group
=
347 mSelectControl
->AsXULRadioGroup();
349 group
->SetFocusedItem(itemElm
);
353 ////////////////////////////////////////////////////////////////////////////////
354 // XULStatusBarAccessible
355 ////////////////////////////////////////////////////////////////////////////////
357 XULStatusBarAccessible::XULStatusBarAccessible(nsIContent
* aContent
,
359 : AccessibleWrap(aContent
, aDoc
) {}
361 role
XULStatusBarAccessible::NativeRole() const { return roles::STATUSBAR
; }
363 ////////////////////////////////////////////////////////////////////////////////
364 // XULToolbarButtonAccessible
365 ////////////////////////////////////////////////////////////////////////////////
367 XULToolbarButtonAccessible::XULToolbarButtonAccessible(nsIContent
* aContent
,
369 : XULButtonAccessible(aContent
, aDoc
) {}
371 void XULToolbarButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet
,
374 int32_t posInSet
= 0;
376 LocalAccessible
* parent
= LocalParent();
379 uint32_t childCount
= parent
->ChildCount();
380 for (uint32_t childIdx
= 0; childIdx
< childCount
; childIdx
++) {
381 LocalAccessible
* child
= parent
->LocalChildAt(childIdx
);
382 if (IsSeparator(child
)) { // end of a group of buttons
383 if (posInSet
) break; // we've found our group, so we're done
385 setSize
= 0; // not our group, so start a new group
388 setSize
++; // another button in the group
390 if (child
== this) posInSet
= setSize
; // we've found our button
394 *aPosInSet
= posInSet
;
398 bool XULToolbarButtonAccessible::IsSeparator(LocalAccessible
* aAccessible
) {
399 nsIContent
* content
= aAccessible
->GetContent();
400 return content
&& content
->IsAnyOfXULElements(nsGkAtoms::toolbarseparator
,
401 nsGkAtoms::toolbarspacer
,
402 nsGkAtoms::toolbarspring
);
405 ////////////////////////////////////////////////////////////////////////////////
406 // XULToolbarButtonAccessible: Widgets
408 bool XULToolbarButtonAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
409 return XULButtonAccessible::IsAcceptableChild(aEl
) ||
410 // In addition to the children allowed by buttons, toolbarbuttons can
411 // have labels as children, but only if the label attribute is not
413 (aEl
->IsXULElement(nsGkAtoms::label
) &&
414 !mContent
->AsElement()->HasAttr(nsGkAtoms::label
));
417 ////////////////////////////////////////////////////////////////////////////////
418 // XULToolbarAccessible
419 ////////////////////////////////////////////////////////////////////////////////
421 XULToolbarAccessible::XULToolbarAccessible(nsIContent
* aContent
,
423 : AccessibleWrap(aContent
, aDoc
) {}
425 role
XULToolbarAccessible::NativeRole() const { return roles::TOOLBAR
; }
427 ENameValueFlag
XULToolbarAccessible::NativeName(nsString
& aName
) const {
428 if (mContent
->AsElement()->GetAttr(nsGkAtoms::toolbarname
, aName
)) {
429 aName
.CompressWhitespace();
435 ////////////////////////////////////////////////////////////////////////////////
436 // XULToolbarAccessible
437 ////////////////////////////////////////////////////////////////////////////////
439 XULToolbarSeparatorAccessible::XULToolbarSeparatorAccessible(
440 nsIContent
* aContent
, DocAccessible
* aDoc
)
441 : LeafAccessible(aContent
, aDoc
) {}
443 role
XULToolbarSeparatorAccessible::NativeRole() const {
444 return roles::SEPARATOR
;
447 uint64_t XULToolbarSeparatorAccessible::NativeState() const { return 0; }