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 // A XUL button can be labelled by a direct child text node, so we need to
135 // allow that as a child so it will be picked up when computing name from
137 (aEl
->IsText() && aEl
->GetParent() == mContent
);
140 ////////////////////////////////////////////////////////////////////////////////
141 // XULButtonAccessible protected
143 bool XULButtonAccessible::ContainsMenu() const {
144 return mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
145 nsGkAtoms::menu
, eCaseMatters
);
148 ////////////////////////////////////////////////////////////////////////////////
149 // XULDropmarkerAccessible
150 ////////////////////////////////////////////////////////////////////////////////
152 XULDropmarkerAccessible::XULDropmarkerAccessible(nsIContent
* aContent
,
154 : LeafAccessible(aContent
, aDoc
) {}
156 bool XULDropmarkerAccessible::HasPrimaryAction() const { return true; }
158 bool XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen
) const {
161 nsIContent
* parent
= mContent
->GetFlattenedTreeParent();
164 nsCOMPtr
<nsIDOMXULButtonElement
> parentButtonElement
=
165 parent
->AsElement()->AsXULButton();
166 if (parentButtonElement
) {
167 parentButtonElement
->GetOpen(&isOpen
);
168 if (aToggleOpen
) parentButtonElement
->SetOpen(!isOpen
);
172 nsCOMPtr
<nsIDOMXULMenuListElement
> parentMenuListElement
=
173 parent
->AsElement()->AsXULMenuList();
174 if (parentMenuListElement
) {
175 parentMenuListElement
->GetOpen(&isOpen
);
176 if (aToggleOpen
) parentMenuListElement
->SetOpen(!isOpen
);
179 parent
= parent
->GetFlattenedTreeParent();
185 void XULDropmarkerAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
187 if (aIndex
== eAction_Click
) {
188 if (DropmarkerOpen(false)) {
189 aName
.AssignLiteral("close");
191 aName
.AssignLiteral("open");
196 bool XULDropmarkerAccessible::DoAction(uint8_t index
) const {
197 if (index
== eAction_Click
) {
198 DropmarkerOpen(true); // Reverse the open attribute
204 role
XULDropmarkerAccessible::NativeRole() const { return roles::PUSHBUTTON
; }
206 uint64_t XULDropmarkerAccessible::NativeState() const {
207 return DropmarkerOpen(false) ? states::PRESSED
: 0;
210 ////////////////////////////////////////////////////////////////////////////////
211 // XULGroupboxAccessible
212 ////////////////////////////////////////////////////////////////////////////////
214 XULGroupboxAccessible::XULGroupboxAccessible(nsIContent
* aContent
,
216 : AccessibleWrap(aContent
, aDoc
) {}
218 role
XULGroupboxAccessible::NativeRole() const { return roles::GROUPING
; }
220 ENameValueFlag
XULGroupboxAccessible::NativeName(nsString
& aName
) const {
221 // XXX: we use the first related accessible only.
222 LocalAccessible
* label
=
223 RelationByType(RelationType::LABELLED_BY
).LocalNext();
224 if (label
) return label
->Name(aName
);
229 Relation
XULGroupboxAccessible::RelationByType(RelationType aType
) const {
230 Relation rel
= AccessibleWrap::RelationByType(aType
);
232 // The label for xul:groupbox is generated from the first xul:label
233 if (aType
== RelationType::LABELLED_BY
&& ChildCount() > 0) {
234 LocalAccessible
* childAcc
= LocalChildAt(0);
235 if (childAcc
->Role() == roles::LABEL
&&
236 childAcc
->GetContent()->IsXULElement(nsGkAtoms::label
)) {
237 rel
.AppendTarget(childAcc
);
244 ////////////////////////////////////////////////////////////////////////////////
245 // XULRadioButtonAccessible
246 ////////////////////////////////////////////////////////////////////////////////
248 XULRadioButtonAccessible::XULRadioButtonAccessible(nsIContent
* aContent
,
250 : RadioButtonAccessible(aContent
, aDoc
) {}
252 uint64_t XULRadioButtonAccessible::NativeState() const {
253 uint64_t state
= LeafAccessible::NativeState();
254 state
|= states::CHECKABLE
;
256 nsCOMPtr
<nsIDOMXULSelectControlItemElement
> radioButton
=
257 Elm()->AsXULSelectControlItem();
259 bool selected
= false; // Radio buttons can be selected
260 radioButton
->GetSelected(&selected
);
262 state
|= states::CHECKED
;
269 uint64_t XULRadioButtonAccessible::NativeInteractiveState() const {
270 return NativelyUnavailable() ? states::UNAVAILABLE
: states::FOCUSABLE
;
273 ////////////////////////////////////////////////////////////////////////////////
274 // XULRadioButtonAccessible: Widgets
276 LocalAccessible
* XULRadioButtonAccessible::ContainerWidget() const {
280 ////////////////////////////////////////////////////////////////////////////////
281 // XULRadioGroupAccessible
282 ////////////////////////////////////////////////////////////////////////////////
286 * The Radio Group proxies for the Radio Buttons themselves. The Group gets
287 * focus whereas the Buttons do not. So we only have an accessible object for
288 * this for the purpose of getting the proper RadioButton. Need this here to
289 * avoid circular reference problems when navigating the accessible tree and
290 * for getting to the radiobuttons.
293 XULRadioGroupAccessible::XULRadioGroupAccessible(nsIContent
* aContent
,
295 : XULSelectControlAccessible(aContent
, aDoc
) {}
297 role
XULRadioGroupAccessible::NativeRole() const { return roles::RADIO_GROUP
; }
299 uint64_t XULRadioGroupAccessible::NativeInteractiveState() const {
300 // The radio group is not focusable. Sometimes the focus controller will
301 // report that it is focused. That means that the actual selected radio button
302 // should be considered focused.
303 return NativelyUnavailable() ? states::UNAVAILABLE
: 0;
306 ////////////////////////////////////////////////////////////////////////////////
307 // XULRadioGroupAccessible: Widgets
309 bool XULRadioGroupAccessible::IsWidget() const { return true; }
311 bool XULRadioGroupAccessible::IsActiveWidget() const {
312 return FocusMgr()->HasDOMFocus(mContent
);
315 bool XULRadioGroupAccessible::AreItemsOperable() const { return true; }
317 LocalAccessible
* XULRadioGroupAccessible::CurrentItem() const {
318 if (!mSelectControl
) {
322 RefPtr
<dom::Element
> currentItemElm
;
323 nsCOMPtr
<nsIDOMXULRadioGroupElement
> group
=
324 mSelectControl
->AsXULRadioGroup();
326 group
->GetFocusedItem(getter_AddRefs(currentItemElm
));
329 if (currentItemElm
) {
330 DocAccessible
* document
= Document();
332 return document
->GetAccessible(currentItemElm
);
339 void XULRadioGroupAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
340 if (!mSelectControl
) {
344 nsCOMPtr
<dom::Element
> itemElm
= aItem
->Elm();
345 nsCOMPtr
<nsIDOMXULRadioGroupElement
> group
=
346 mSelectControl
->AsXULRadioGroup();
348 group
->SetFocusedItem(itemElm
);
352 ////////////////////////////////////////////////////////////////////////////////
353 // XULStatusBarAccessible
354 ////////////////////////////////////////////////////////////////////////////////
356 XULStatusBarAccessible::XULStatusBarAccessible(nsIContent
* aContent
,
358 : AccessibleWrap(aContent
, aDoc
) {}
360 role
XULStatusBarAccessible::NativeRole() const { return roles::STATUSBAR
; }
362 ////////////////////////////////////////////////////////////////////////////////
363 // XULToolbarButtonAccessible
364 ////////////////////////////////////////////////////////////////////////////////
366 XULToolbarButtonAccessible::XULToolbarButtonAccessible(nsIContent
* aContent
,
368 : XULButtonAccessible(aContent
, aDoc
) {}
370 void XULToolbarButtonAccessible::GetPositionAndSetSize(int32_t* aPosInSet
,
373 int32_t posInSet
= 0;
375 LocalAccessible
* parent
= LocalParent();
378 uint32_t childCount
= parent
->ChildCount();
379 for (uint32_t childIdx
= 0; childIdx
< childCount
; childIdx
++) {
380 LocalAccessible
* child
= parent
->LocalChildAt(childIdx
);
381 if (IsSeparator(child
)) { // end of a group of buttons
382 if (posInSet
) break; // we've found our group, so we're done
384 setSize
= 0; // not our group, so start a new group
387 setSize
++; // another button in the group
389 if (child
== this) posInSet
= setSize
; // we've found our button
393 *aPosInSet
= posInSet
;
397 bool XULToolbarButtonAccessible::IsSeparator(LocalAccessible
* aAccessible
) {
398 nsIContent
* content
= aAccessible
->GetContent();
399 return content
&& content
->IsAnyOfXULElements(nsGkAtoms::toolbarseparator
,
400 nsGkAtoms::toolbarspacer
,
401 nsGkAtoms::toolbarspring
);
404 ////////////////////////////////////////////////////////////////////////////////
405 // XULToolbarButtonAccessible: Widgets
407 bool XULToolbarButtonAccessible::IsAcceptableChild(nsIContent
* aEl
) const {
408 return XULButtonAccessible::IsAcceptableChild(aEl
) ||
409 // In addition to the children allowed by buttons, toolbarbuttons can
410 // have labels as children, but only if the label attribute is not
412 (aEl
->IsXULElement(nsGkAtoms::label
) &&
413 !mContent
->AsElement()->HasAttr(nsGkAtoms::label
));
416 ////////////////////////////////////////////////////////////////////////////////
417 // XULToolbarAccessible
418 ////////////////////////////////////////////////////////////////////////////////
420 XULToolbarAccessible::XULToolbarAccessible(nsIContent
* aContent
,
422 : AccessibleWrap(aContent
, aDoc
) {}
424 role
XULToolbarAccessible::NativeRole() const { return roles::TOOLBAR
; }
426 ENameValueFlag
XULToolbarAccessible::NativeName(nsString
& aName
) const {
427 if (mContent
->AsElement()->GetAttr(nsGkAtoms::toolbarname
, aName
)) {
428 aName
.CompressWhitespace();
434 ////////////////////////////////////////////////////////////////////////////////
435 // XULToolbarAccessible
436 ////////////////////////////////////////////////////////////////////////////////
438 XULToolbarSeparatorAccessible::XULToolbarSeparatorAccessible(
439 nsIContent
* aContent
, DocAccessible
* aDoc
)
440 : LeafAccessible(aContent
, aDoc
) {}
442 role
XULToolbarSeparatorAccessible::NativeRole() const {
443 return roles::SEPARATOR
;
446 uint64_t XULToolbarSeparatorAccessible::NativeState() const { return 0; }