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 "XULMenuParentElement.h"
10 #include "XULPopupElement.h"
11 #include "mozilla/Assertions.h"
12 #include "nsAccessibilityService.h"
13 #include "nsAccUtils.h"
14 #include "DocAccessible.h"
17 #include "XULFormControlAccessible.h"
19 #include "nsIContentInlines.h"
20 #include "nsIDOMXULContainerElement.h"
21 #include "nsIDOMXULSelectCntrlEl.h"
22 #include "nsIDOMXULSelectCntrlItemEl.h"
23 #include "nsIContent.h"
24 #include "nsMenuBarFrame.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(kNameSpaceID_None
, 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(kNameSpaceID_None
, nsGkAtoms::label
, aName
);
135 void XULMenuitemAccessible::Description(nsString
& aDescription
) const {
136 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::description
,
140 KeyBinding
XULMenuitemAccessible::AccessKey() const {
141 // Return menu accesskey: N or Alt+F.
142 static int32_t gMenuAccesskeyModifier
=
143 -1; // magic value of -1 indicates unitialized state
145 // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for
146 // menu are't registered by EventStateManager.
147 nsAutoString accesskey
;
148 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
,
150 if (accesskey
.IsEmpty()) return KeyBinding();
152 uint32_t modifierKey
= 0;
154 LocalAccessible
* parentAcc
= LocalParent();
156 if (parentAcc
->NativeRole() == roles::MENUBAR
) {
157 // If top level menu item, add Alt+ or whatever modifier text to string
158 // No need to cache pref service, this happens rarely
159 if (gMenuAccesskeyModifier
== -1) {
160 // Need to initialize cached global accesskey pref
161 gMenuAccesskeyModifier
= Preferences::GetInt("ui.key.menuAccessKey", 0);
164 switch (gMenuAccesskeyModifier
) {
165 case dom::KeyboardEvent_Binding::DOM_VK_CONTROL
:
166 modifierKey
= KeyBinding::kControl
;
168 case dom::KeyboardEvent_Binding::DOM_VK_ALT
:
169 modifierKey
= KeyBinding::kAlt
;
171 case dom::KeyboardEvent_Binding::DOM_VK_META
:
172 modifierKey
= KeyBinding::kMeta
;
174 case dom::KeyboardEvent_Binding::DOM_VK_WIN
:
175 modifierKey
= KeyBinding::kOS
;
181 return KeyBinding(accesskey
[0], modifierKey
);
184 KeyBinding
XULMenuitemAccessible::KeyboardShortcut() const {
185 nsAutoString keyElmId
;
186 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::key
, keyElmId
);
187 if (keyElmId
.IsEmpty()) return KeyBinding();
189 dom::Element
* keyElm
= mContent
->OwnerDoc()->GetElementById(keyElmId
);
190 if (!keyElm
) return KeyBinding();
195 keyElm
->GetAttr(kNameSpaceID_None
, nsGkAtoms::key
, keyStr
);
196 if (keyStr
.IsEmpty()) {
197 nsAutoString keyCodeStr
;
198 keyElm
->GetAttr(kNameSpaceID_None
, nsGkAtoms::keycode
, keyCodeStr
);
200 key
= keyStr
.ToInteger(&errorCode
, /* aRadix = */ 10);
201 if (NS_FAILED(errorCode
)) {
202 key
= keyStr
.ToInteger(&errorCode
, /* aRadix = */ 16);
208 nsAutoString modifiersStr
;
209 keyElm
->GetAttr(kNameSpaceID_None
, nsGkAtoms::modifiers
, modifiersStr
);
211 uint32_t modifierMask
= 0;
212 if (modifiersStr
.Find(u
"shift") != -1) modifierMask
|= KeyBinding::kShift
;
213 if (modifiersStr
.Find(u
"alt") != -1) modifierMask
|= KeyBinding::kAlt
;
214 if (modifiersStr
.Find(u
"meta") != -1) modifierMask
|= KeyBinding::kMeta
;
215 if (modifiersStr
.Find(u
"os") != -1) modifierMask
|= KeyBinding::kOS
;
216 if (modifiersStr
.Find(u
"control") != -1) modifierMask
|= KeyBinding::kControl
;
217 if (modifiersStr
.Find(u
"accel") != -1) {
218 modifierMask
|= KeyBinding::AccelModifier();
221 return KeyBinding(key
, modifierMask
);
224 role
XULMenuitemAccessible::NativeRole() const {
225 nsCOMPtr
<nsIDOMXULContainerElement
> xulContainer
= Elm()->AsXULContainer();
226 if (xulContainer
) return roles::PARENT_MENUITEM
;
228 LocalAccessible
* widget
= ContainerWidget();
229 if (widget
&& widget
->Role() == roles::COMBOBOX_LIST
) {
230 return roles::COMBOBOX_OPTION
;
233 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
234 nsGkAtoms::radio
, eCaseMatters
)) {
235 return roles::RADIO_MENU_ITEM
;
238 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
239 nsGkAtoms::checkbox
, eCaseMatters
)) {
240 return roles::CHECK_MENU_ITEM
;
243 return roles::MENUITEM
;
246 int32_t XULMenuitemAccessible::GetLevel(bool aFast
) const {
247 return nsAccUtils::GetLevelForXULContainerItem(mContent
);
250 void XULMenuitemAccessible::ActionNameAt(uint8_t aIndex
, nsAString
& aName
) {
251 if (aIndex
== eAction_Click
) aName
.AssignLiteral("click");
254 bool XULMenuitemAccessible::HasPrimaryAction() const { return true; }
256 ////////////////////////////////////////////////////////////////////////////////
257 // XULMenuitemAccessible: Widgets
259 bool XULMenuitemAccessible::IsActiveWidget() const {
260 // Parent menu item is a widget, it's active when its popup is open.
261 // Typically the <menupopup> is included in the document markup, and
262 // <menu> prepends content in front of it.
263 nsIContent
* menuPopupContent
= mContent
->GetLastChild();
264 if (menuPopupContent
) {
265 nsMenuPopupFrame
* menuPopupFrame
=
266 do_QueryFrame(menuPopupContent
->GetPrimaryFrame());
267 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
272 bool XULMenuitemAccessible::AreItemsOperable() const {
273 // Parent menu item is a widget, its items are operable when its popup is
275 nsIContent
* menuPopupContent
= mContent
->GetLastChild();
276 if (menuPopupContent
) {
277 nsMenuPopupFrame
* menuPopupFrame
=
278 do_QueryFrame(menuPopupContent
->GetPrimaryFrame());
279 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
284 LocalAccessible
* XULMenuitemAccessible::ContainerWidget() const {
285 if (auto* button
= dom::XULButtonElement::FromNode(GetContent())) {
286 if (auto* popup
= button
->GetMenuParent()) {
287 // We use GetAccessibleOrContainer instead of just GetAccessible because
288 // we strip menupopups from the tree for ATK.
289 return mDoc
->GetAccessibleOrContainer(popup
);
295 ////////////////////////////////////////////////////////////////////////////////
296 // XULMenuSeparatorAccessible
297 ////////////////////////////////////////////////////////////////////////////////
299 XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent
* aContent
,
301 : XULMenuitemAccessible(aContent
, aDoc
) {}
303 uint64_t XULMenuSeparatorAccessible::NativeState() const {
304 // Isn't focusable, but can be offscreen/invisible -- only copy those states
305 return XULMenuitemAccessible::NativeState() &
306 (states::OFFSCREEN
| states::INVISIBLE
);
309 ENameValueFlag
XULMenuSeparatorAccessible::NativeName(nsString
& aName
) const {
313 role
XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR
; }
315 bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; }
317 ////////////////////////////////////////////////////////////////////////////////
318 // XULMenupopupAccessible
319 ////////////////////////////////////////////////////////////////////////////////
321 XULMenupopupAccessible::XULMenupopupAccessible(nsIContent
* aContent
,
323 : XULSelectControlAccessible(aContent
, aDoc
) {
324 if (nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame())) {
325 if (menuPopupFrame
->GetPopupType() == widget::PopupType::Menu
) {
326 mType
= eMenuPopupType
;
330 // May be the anonymous <menupopup> inside <menulist> (a combobox)
331 auto* parent
= mContent
->GetParentElement();
332 nsCOMPtr
<nsIDOMXULSelectControlElement
> selectControl
=
333 parent
? parent
->AsXULSelectControl() : nullptr;
335 mSelectControl
= parent
;
337 mSelectControl
= nullptr;
338 mGenericTypes
&= ~eSelect
;
342 uint64_t XULMenupopupAccessible::NativeState() const {
343 uint64_t state
= LocalAccessible::NativeState();
346 // We are onscreen if our parent is active
348 mContent
->AsElement()->HasAttr(kNameSpaceID_None
, nsGkAtoms::menuactive
);
350 LocalAccessible
* parent
= LocalParent();
352 nsIContent
* parentContent
= parent
->GetContent();
353 if (parentContent
&& parentContent
->IsElement())
354 isActive
= parentContent
->AsElement()->HasAttr(kNameSpaceID_None
,
359 NS_ASSERTION(isActive
|| (state
& states::INVISIBLE
),
360 "XULMenupopup doesn't have INVISIBLE when it's inactive");
363 if (state
& states::INVISIBLE
) state
|= states::OFFSCREEN
| states::COLLAPSED
;
368 ENameValueFlag
XULMenupopupAccessible::NativeName(nsString
& aName
) const {
369 nsIContent
* content
= mContent
;
370 while (content
&& aName
.IsEmpty()) {
371 if (content
->IsElement()) {
372 content
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
, aName
);
374 content
= content
->GetFlattenedTreeParent();
380 role
XULMenupopupAccessible::NativeRole() const {
381 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
382 if (menuPopupFrame
&& menuPopupFrame
->IsContextMenu()) {
383 return roles::MENUPOPUP
;
387 if (mParent
->IsCombobox()) {
388 return roles::COMBOBOX_LIST
;
392 // If accessible is not bound to the tree (this happens while children are
393 // cached) return general role.
394 return roles::MENUPOPUP
;
397 ////////////////////////////////////////////////////////////////////////////////
398 // XULMenupopupAccessible: Widgets
400 bool XULMenupopupAccessible::IsWidget() const { return true; }
402 bool XULMenupopupAccessible::IsActiveWidget() const {
403 // If menupopup is a widget (the case of context menus) then active when open.
404 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
405 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
408 bool XULMenupopupAccessible::AreItemsOperable() const {
409 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
410 return menuPopupFrame
&& menuPopupFrame
->IsOpen();
413 LocalAccessible
* XULMenupopupAccessible::ContainerWidget() const {
414 DocAccessible
* document
= Document();
416 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetFrame());
417 MOZ_ASSERT(menuPopupFrame
);
418 if (!menuPopupFrame
) {
422 auto* cur
= dom::XULPopupElement::FromNode(GetContent());
424 auto* menu
= cur
->GetContainingMenu();
426 // <panel> / <tooltip> / etc.
429 dom::XULMenuParentElement
* parent
= menu
->GetMenuParent();
431 LocalAccessible
* menuPopup
= document
->GetAccessible(cur
);
432 MOZ_ASSERT(menuPopup
);
433 return menuPopup
? menuPopup
->LocalParent() : nullptr;
436 if (parent
->IsMenuBar()) {
437 return document
->GetAccessible(parent
);
440 cur
= dom::XULPopupElement::FromNode(parent
);
441 MOZ_ASSERT(cur
, "Should be a popup");
444 MOZ_ASSERT_UNREACHABLE("How did we get out of the loop without returning?");
448 ////////////////////////////////////////////////////////////////////////////////
449 // XULMenubarAccessible
450 ////////////////////////////////////////////////////////////////////////////////
452 XULMenubarAccessible::XULMenubarAccessible(nsIContent
* aContent
,
454 : AccessibleWrap(aContent
, aDoc
) {}
456 ENameValueFlag
XULMenubarAccessible::NativeName(nsString
& aName
) const {
457 aName
.AssignLiteral("Application");
461 role
XULMenubarAccessible::NativeRole() const { return roles::MENUBAR
; }
463 ////////////////////////////////////////////////////////////////////////////////
464 // XULMenubarAccessible: Widgets
466 bool XULMenubarAccessible::IsActiveWidget() const {
467 nsMenuBarFrame
* menuBarFrame
= do_QueryFrame(GetFrame());
468 return menuBarFrame
&& menuBarFrame
->IsActive();
471 bool XULMenubarAccessible::AreItemsOperable() const { return true; }
473 LocalAccessible
* XULMenubarAccessible::CurrentItem() const {
474 auto* content
= dom::XULMenuParentElement::FromNode(GetContent());
476 if (!content
|| !content
->GetActiveMenuChild()) {
479 return mDoc
->GetAccessible(content
->GetActiveMenuChild());
482 void XULMenubarAccessible::SetCurrentItem(const LocalAccessible
* aItem
) {
483 NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");