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 "XULMenuAccessible.h"
8 #include "LocalAccessible-inl.h"
9 #include "XULMenuBarElement.h"
10 #include "XULMenuParentElement.h"
11 #include "XULPopupElement.h"
12 #include "mozilla/Assertions.h"
13 #include "nsAccessibilityService.h"
14 #include "nsAccUtils.h"
15 #include "DocAccessible.h"
16 #include "mozilla/a11y/Role.h"
18 #include "XULFormControlAccessible.h"
20 #include "nsIContentInlines.h"
21 #include "nsIDOMXULContainerElement.h"
22 #include "nsIDOMXULSelectCntrlEl.h"
23 #include "nsIDOMXULSelectCntrlItemEl.h"
24 #include "nsIContent.h"
25 #include "nsMenuPopupFrame.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/LookAndFeel.h"
29 #include "mozilla/dom/Document.h"
30 #include "mozilla/dom/Element.h"
31 #include "mozilla/dom/XULButtonElement.h"
32 #include "mozilla/dom/KeyboardEventBinding.h"
34 using namespace mozilla
;
35 using namespace mozilla::a11y
;
37 ////////////////////////////////////////////////////////////////////////////////
38 // XULMenuitemAccessible
39 ////////////////////////////////////////////////////////////////////////////////
41 XULMenuitemAccessible::XULMenuitemAccessible(nsIContent
* aContent
,
43 : AccessibleWrap(aContent
, aDoc
) {}
45 uint64_t XULMenuitemAccessible::NativeState() const {
46 uint64_t state
= LocalAccessible::NativeState();
49 if (mContent
->NodeInfo()->Equals(nsGkAtoms::menu
, kNameSpaceID_XUL
)) {
50 state
|= states::HASPOPUP
;
51 if (mContent
->AsElement()->HasAttr(nsGkAtoms::open
)) {
52 state
|= states::EXPANDED
;
54 state
|= states::COLLAPSED
;
59 static dom::Element::AttrValuesArray strings
[] = {
60 nsGkAtoms::radio
, nsGkAtoms::checkbox
, nullptr};
62 if (mContent
->AsElement()->FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::type
,
63 strings
, eCaseMatters
) >= 0) {
65 state
|= states::CHECKABLE
;
68 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
69 nsGkAtoms::checked
, nsGkAtoms::_true
,
71 state
|= states::CHECKED
;
76 bool isComboboxOption
= (Role() == roles::COMBOBOX_OPTION
);
77 if (isComboboxOption
) {
79 bool isSelected
= false;
80 nsCOMPtr
<nsIDOMXULSelectControlItemElement
> item
=
81 Elm()->AsXULSelectControlItem();
82 NS_ENSURE_TRUE(item
, state
);
83 item
->GetSelected(&isSelected
);
86 bool isCollapsed
= false;
87 LocalAccessible
* parent
= LocalParent();
88 if (parent
&& parent
->State() & states::INVISIBLE
) isCollapsed
= true;
91 state
|= states::SELECTED
;
93 // Selected and collapsed?
95 // Set selected option offscreen/invisible according to combobox state
96 LocalAccessible
* grandParent
= parent
->LocalParent();
97 if (!grandParent
) return state
;
98 NS_ASSERTION(grandParent
->IsCombobox(),
99 "grandparent of combobox listitem is not combobox");
100 uint64_t grandParentState
= grandParent
->State();
101 state
&= ~(states::OFFSCREEN
| states::INVISIBLE
);
102 state
|= (grandParentState
& states::OFFSCREEN
) |
103 (grandParentState
& states::INVISIBLE
) |
104 (grandParentState
& states::OPAQUE1
);
107 } // ROLE_COMBOBOX_OPTION
112 uint64_t XULMenuitemAccessible::NativeInteractiveState() const {
113 if (NativelyUnavailable()) {
114 // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic.
115 auto* button
= dom::XULButtonElement::FromNode(GetContent());
116 bool skipNavigatingDisabledMenuItem
= true;
117 if (!button
|| !button
->IsOnMenuBar()) {
118 skipNavigatingDisabledMenuItem
= LookAndFeel::GetInt(
119 LookAndFeel::IntID::SkipNavigatingDisabledMenuItem
);
122 if (skipNavigatingDisabledMenuItem
) return states::UNAVAILABLE
;
124 return states::UNAVAILABLE
| states::FOCUSABLE
| states::SELECTABLE
;
127 return states::FOCUSABLE
| states::SELECTABLE
;
130 ENameValueFlag
XULMenuitemAccessible::NativeName(nsString
& aName
) const {
131 mContent
->AsElement()->GetAttr(nsGkAtoms::label
, aName
);
135 void XULMenuitemAccessible::Description(nsString
& aDescription
) const {
136 mContent
->AsElement()->GetAttr(nsGkAtoms::description
, aDescription
);
139 KeyBinding
XULMenuitemAccessible::AccessKey() const {
140 // Return menu accesskey: N or Alt+F.
141 static int32_t gMenuAccesskeyModifier
=
142 -1; // magic value of -1 indicates unitialized state
144 // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for
145 // menu are't registered by EventStateManager.
146 nsAutoString accesskey
;
147 mContent
->AsElement()->GetAttr(nsGkAtoms::accesskey
, accesskey
);
148 if (accesskey
.IsEmpty()) return KeyBinding();
150 uint32_t modifierKey
= 0;
152 LocalAccessible
* parentAcc
= LocalParent();
154 if (parentAcc
->NativeRole() == roles::MENUBAR
) {
155 // If top level menu item, add Alt+ or whatever modifier text to string
156 // No need to cache pref service, this happens rarely
157 if (gMenuAccesskeyModifier
== -1) {
158 // Need to initialize cached global accesskey pref
159 gMenuAccesskeyModifier
= Preferences::GetInt("ui.key.menuAccessKey", 0);
162 switch (gMenuAccesskeyModifier
) {
163 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
164 modifierKey
= KeyBinding::kControl
;
166 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
167 modifierKey
= KeyBinding::kAlt
;
169 case dom::KeyboardEvent_Binding::DOM_VK_META
:
170 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
171 modifierKey
= KeyBinding::kMeta
;
177 return KeyBinding(accesskey
[0], modifierKey
);
180 KeyBinding
XULMenuitemAccessible::KeyboardShortcut() const {
181 nsAutoString keyElmId
;
182 mContent
->AsElement()->GetAttr(nsGkAtoms::key
, keyElmId
);
183 if (keyElmId
.IsEmpty()) return KeyBinding();
185 dom::Element
* keyElm
= mContent
->OwnerDoc()->GetElementById(keyElmId
);
186 if (!keyElm
) return KeyBinding();
191 keyElm
->GetAttr(nsGkAtoms::key
, keyStr
);
192 if (keyStr
.IsEmpty()) {
193 nsAutoString keyCodeStr
;
194 keyElm
->GetAttr(nsGkAtoms::keycode
, keyCodeStr
);
196 key
= keyStr
.ToInteger(&errorCode
, /* aRadix = */ 10);
197 if (NS_FAILED(errorCode
)) {
198 key
= keyStr
.ToInteger(&errorCode
, /* aRadix = */ 16);
204 nsAutoString modifiersStr
;
205 keyElm
->GetAttr(nsGkAtoms::modifiers
, modifiersStr
);
207 uint32_t modifierMask
= 0;
208 if (modifiersStr
.Find(u
"shift") != -1) modifierMask
|= KeyBinding::kShift
;
209 if (modifiersStr
.Find(u
"alt") != -1) modifierMask
|= KeyBinding::kAlt
;
210 if (modifiersStr
.Find(u
"meta") != -1) modifierMask
|= KeyBinding::kMeta
;
211 if (modifiersStr
.Find(u
"control") != -1) modifierMask
|= KeyBinding::kControl
;
212 if (modifiersStr
.Find(u
"accel") != -1) {
213 modifierMask
|= KeyBinding::AccelModifier();
216 return KeyBinding(key
, modifierMask
);
219 role
XULMenuitemAccessible::NativeRole() const {
220 nsCOMPtr
<nsIDOMXULContainerElement
> xulContainer
= Elm()->AsXULContainer();
221 if (xulContainer
) return roles::PARENT_MENUITEM
;
223 LocalAccessible
* widget
= ContainerWidget();
224 if (widget
&& widget
->Role() == roles::COMBOBOX_LIST
) {
225 return roles::COMBOBOX_OPTION
;
228 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
229 nsGkAtoms::radio
, eCaseMatters
)) {
230 return roles::RADIO_MENU_ITEM
;
233 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
234 nsGkAtoms::checkbox
, eCaseMatters
)) {
235 return roles::CHECK_MENU_ITEM
;
238 return roles::MENUITEM
;
241 int32_t XULMenuitemAccessible::GetLevel(bool aFast
) const {
242 return nsAccUtils::GetLevelForXULContainerItem(mContent
);
245 void XULMenuitemAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
246 if (aIndex
== eAction_Click
) aName
.AssignLiteral("click");
249 bool XULMenuitemAccessible::HasPrimaryAction() const { return true; }
251 ////////////////////////////////////////////////////////////////////////////////
252 // XULMenuitemAccessible: Widgets
254 bool XULMenuitemAccessible::IsActiveWidget() const {
255 // Parent menu item is a widget, it's active when its popup is open.
256 // Typically the <menupopup> is included in the document markup, and
257 // <menu> prepends content in front of it.
258 nsIContent
* menuPopupContent
= mContent
->GetLastChild();
259 if (menuPopupContent
) {
260 nsMenuPopupFrame
* menuPopupFrame
=
261 do_QueryFrame(menuPopupContent
->GetPrimaryFrame());
262 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
267 bool XULMenuitemAccessible::AreItemsOperable() const {
268 // Parent menu item is a widget, its items are operable when its popup is
270 nsIContent
* menuPopupContent
= mContent
->GetLastChild();
271 if (menuPopupContent
) {
272 nsMenuPopupFrame
* menuPopupFrame
=
273 do_QueryFrame(menuPopupContent
->GetPrimaryFrame());
274 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
279 LocalAccessible
* XULMenuitemAccessible::ContainerWidget() const {
280 if (auto* button
= dom::XULButtonElement::FromNode(GetContent())) {
281 if (auto* popup
= button
->GetMenuParent()) {
282 // We use GetAccessibleOrContainer instead of just GetAccessible because
283 // we strip menupopups from the tree for ATK.
284 return mDoc
->GetAccessibleOrContainer(popup
);
290 ////////////////////////////////////////////////////////////////////////////////
291 // XULMenuSeparatorAccessible
292 ////////////////////////////////////////////////////////////////////////////////
294 XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent
* aContent
,
296 : XULMenuitemAccessible(aContent
, aDoc
) {}
298 uint64_t XULMenuSeparatorAccessible::NativeState() const {
299 // Isn't focusable, but can be offscreen/invisible -- only copy those states
300 return XULMenuitemAccessible::NativeState() &
301 (states::OFFSCREEN
| states::INVISIBLE
);
304 ENameValueFlag
XULMenuSeparatorAccessible::NativeName(nsString
& aName
) const {
308 role
XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR
; }
310 bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; }
312 ////////////////////////////////////////////////////////////////////////////////
313 // XULMenupopupAccessible
314 ////////////////////////////////////////////////////////////////////////////////
316 XULMenupopupAccessible::XULMenupopupAccessible(nsIContent
* aContent
,
318 : XULSelectControlAccessible(aContent
, aDoc
) {
319 if (nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame())) {
320 if (menuPopupFrame
->GetPopupType() == widget::PopupType::Menu
) {
321 mType
= eMenuPopupType
;
325 // May be the anonymous <menupopup> inside <menulist> (a combobox)
326 auto* parent
= mContent
->GetParentElement();
327 nsCOMPtr
<nsIDOMXULSelectControlElement
> selectControl
=
328 parent
? parent
->AsXULSelectControl() : nullptr;
330 mSelectControl
= parent
;
332 mSelectControl
= nullptr;
333 mGenericTypes
&= ~eSelect
;
337 uint64_t XULMenupopupAccessible::NativeState() const {
338 uint64_t state
= LocalAccessible::NativeState();
341 // We are onscreen if our parent is active
342 bool isActive
= mContent
->AsElement()->HasAttr(nsGkAtoms::menuactive
);
344 LocalAccessible
* parent
= LocalParent();
346 nsIContent
* parentContent
= parent
->GetContent();
347 if (parentContent
&& parentContent
->IsElement())
348 isActive
= parentContent
->AsElement()->HasAttr(nsGkAtoms::open
);
352 NS_ASSERTION(isActive
|| (state
& states::INVISIBLE
),
353 "XULMenupopup doesn't have INVISIBLE when it's inactive");
356 if (state
& states::INVISIBLE
) state
|= states::OFFSCREEN
| states::COLLAPSED
;
361 ENameValueFlag
XULMenupopupAccessible::NativeName(nsString
& aName
) const {
362 nsIContent
* content
= mContent
;
363 while (content
&& aName
.IsEmpty()) {
364 if (content
->IsElement()) {
365 content
->AsElement()->GetAttr(nsGkAtoms::label
, aName
);
367 content
= content
->GetFlattenedTreeParent();
373 role
XULMenupopupAccessible::NativeRole() const {
374 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
375 if (menuPopupFrame
&& menuPopupFrame
->IsContextMenu()) {
376 return roles::MENUPOPUP
;
380 if (mParent
->IsCombobox()) {
381 return roles::COMBOBOX_LIST
;
385 // If accessible is not bound to the tree (this happens while children are
386 // cached) return general role.
387 return roles::MENUPOPUP
;
390 ////////////////////////////////////////////////////////////////////////////////
391 // XULMenupopupAccessible: Widgets
393 bool XULMenupopupAccessible::IsWidget() const { return true; }
395 bool XULMenupopupAccessible::IsActiveWidget() const {
396 // If menupopup is a widget (the case of context menus) then active when open.
397 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
398 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
401 bool XULMenupopupAccessible::AreItemsOperable() const {
402 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
403 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
406 LocalAccessible
* XULMenupopupAccessible::ContainerWidget() const {
407 DocAccessible
* document
= Document();
409 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
410 MOZ_ASSERT(menuPopupFrame
);
411 if (!menuPopupFrame
) {
415 auto* cur
= dom::XULPopupElement::FromNode(GetContent());
417 auto* menu
= cur
->GetContainingMenu();
419 // <panel> / <tooltip> / etc.
422 dom::XULMenuParentElement
* parent
= menu
->GetMenuParent();
424 LocalAccessible
* menuPopup
= document
->GetAccessible(cur
);
425 MOZ_ASSERT(menuPopup
);
426 return menuPopup
? menuPopup
->LocalParent() : nullptr;
429 if (parent
->IsMenuBar()) {
430 return document
->GetAccessible(parent
);
433 cur
= dom::XULPopupElement::FromNode(parent
);
434 MOZ_ASSERT(cur
, "Should be a popup");
437 MOZ_ASSERT_UNREACHABLE("How did we get out of the loop without returning?");
441 ////////////////////////////////////////////////////////////////////////////////
442 // XULMenubarAccessible
443 ////////////////////////////////////////////////////////////////////////////////
445 XULMenubarAccessible::XULMenubarAccessible(nsIContent
* aContent
,
447 : AccessibleWrap(aContent
, aDoc
) {}
449 ENameValueFlag
XULMenubarAccessible::NativeName(nsString
& aName
) const {
450 aName
.AssignLiteral("Application");
454 role
XULMenubarAccessible::NativeRole() const { return roles::MENUBAR
; }
456 ////////////////////////////////////////////////////////////////////////////////
457 // XULMenubarAccessible: Widgets
459 bool XULMenubarAccessible::IsActiveWidget() const {
460 auto* menuBar
= dom::XULMenuBarElement::FromNode(GetContent());
461 return menuBar
&& menuBar
->IsActive();
464 bool XULMenubarAccessible::AreItemsOperable() const { return true; }
466 LocalAccessible
* XULMenubarAccessible::CurrentItem() const {
467 auto* content
= dom::XULMenuParentElement::FromNode(GetContent());
469 if (!content
|| !content
->GetActiveMenuChild()) {
472 return mDoc
->GetAccessible(content
->GetActiveMenuChild());
475 void XULMenubarAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
476 NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");