no bug - Correct some typos in the comments. a=typo-fix
[gecko.git] / accessible / xul / XULMenuAccessible.cpp
blob6628304a9efc6af6016805225e7b7d96c4b3db88
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"
17 #include "States.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,
42 DocAccessible* aDoc)
43 : AccessibleWrap(aContent, aDoc) {}
45 uint64_t XULMenuitemAccessible::NativeState() const {
46 uint64_t state = LocalAccessible::NativeState();
48 // Has Popup?
49 if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) {
50 state |= states::HASPOPUP;
51 if (mContent->AsElement()->HasAttr(nsGkAtoms::open)) {
52 state |= states::EXPANDED;
53 } else {
54 state |= states::COLLAPSED;
58 // Checkable/checked?
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) {
64 // Checkable?
65 state |= states::CHECKABLE;
67 // Checked?
68 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
69 nsGkAtoms::checked, nsGkAtoms::_true,
70 eCaseMatters)) {
71 state |= states::CHECKED;
75 // Combo box listitem
76 bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION);
77 if (isComboboxOption) {
78 // Is selected?
79 bool isSelected = false;
80 nsCOMPtr<nsIDOMXULSelectControlItemElement> item =
81 Elm()->AsXULSelectControlItem();
82 NS_ENSURE_TRUE(item, state);
83 item->GetSelected(&isSelected);
85 // Is collapsed?
86 bool isCollapsed = false;
87 LocalAccessible* parent = LocalParent();
88 if (parent && parent->State() & states::INVISIBLE) isCollapsed = true;
90 if (isSelected) {
91 state |= states::SELECTED;
93 // Selected and collapsed?
94 if (isCollapsed) {
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);
105 } // isCollapsed
106 } // isSelected
107 } // ROLE_COMBOBOX_OPTION
109 return state;
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);
132 return eNameOK;
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();
153 if (parentAcc) {
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;
165 break;
166 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
167 modifierKey = KeyBinding::kAlt;
168 break;
169 case dom::KeyboardEvent_Binding::DOM_VK_META:
170 case dom::KeyboardEvent_Binding::DOM_VK_WIN:
171 modifierKey = KeyBinding::kMeta;
172 break;
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();
188 uint32_t key = 0;
190 nsAutoString keyStr;
191 keyElm->GetAttr(nsGkAtoms::key, keyStr);
192 if (keyStr.IsEmpty()) {
193 nsAutoString keyCodeStr;
194 keyElm->GetAttr(nsGkAtoms::keycode, keyCodeStr);
195 nsresult errorCode;
196 key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10);
197 if (NS_FAILED(errorCode)) {
198 key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16);
200 } else {
201 key = keyStr[0];
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();
264 return false;
267 bool XULMenuitemAccessible::AreItemsOperable() const {
268 // Parent menu item is a widget, its items are operable when its popup is
269 // open.
270 nsIContent* menuPopupContent = mContent->GetLastChild();
271 if (menuPopupContent) {
272 nsMenuPopupFrame* menuPopupFrame =
273 do_QueryFrame(menuPopupContent->GetPrimaryFrame());
274 return menuPopupFrame && menuPopupFrame->IsOpen();
276 return false;
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);
287 return nullptr;
290 ////////////////////////////////////////////////////////////////////////////////
291 // XULMenuSeparatorAccessible
292 ////////////////////////////////////////////////////////////////////////////////
294 XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent* aContent,
295 DocAccessible* aDoc)
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 {
305 return eNameOK;
308 role XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR; }
310 bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; }
312 ////////////////////////////////////////////////////////////////////////////////
313 // XULMenupopupAccessible
314 ////////////////////////////////////////////////////////////////////////////////
316 XULMenupopupAccessible::XULMenupopupAccessible(nsIContent* aContent,
317 DocAccessible* aDoc)
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;
329 if (selectControl) {
330 mSelectControl = parent;
331 } else {
332 mSelectControl = nullptr;
333 mGenericTypes &= ~eSelect;
337 uint64_t XULMenupopupAccessible::NativeState() const {
338 uint64_t state = LocalAccessible::NativeState();
340 #ifdef DEBUG
341 // We are onscreen if our parent is active
342 bool isActive = mContent->AsElement()->HasAttr(nsGkAtoms::menuactive);
343 if (!isActive) {
344 LocalAccessible* parent = LocalParent();
345 if (parent) {
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");
354 #endif
356 if (state & states::INVISIBLE) state |= states::OFFSCREEN | states::COLLAPSED;
358 return state;
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();
370 return eNameOK;
373 role XULMenupopupAccessible::NativeRole() const {
374 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
375 if (menuPopupFrame && menuPopupFrame->IsContextMenu()) {
376 return roles::MENUPOPUP;
379 if (mParent) {
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) {
412 return nullptr;
415 auto* cur = dom::XULPopupElement::FromNode(GetContent());
416 while (cur) {
417 auto* menu = cur->GetContainingMenu();
418 if (!menu) {
419 // <panel> / <tooltip> / etc.
420 return nullptr;
422 dom::XULMenuParentElement* parent = menu->GetMenuParent();
423 if (!parent) {
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?");
438 return nullptr;
441 ////////////////////////////////////////////////////////////////////////////////
442 // XULMenubarAccessible
443 ////////////////////////////////////////////////////////////////////////////////
445 XULMenubarAccessible::XULMenubarAccessible(nsIContent* aContent,
446 DocAccessible* aDoc)
447 : AccessibleWrap(aContent, aDoc) {}
449 ENameValueFlag XULMenubarAccessible::NativeName(nsString& aName) const {
450 aName.AssignLiteral("Application");
451 return eNameOK;
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());
468 MOZ_ASSERT(content);
469 if (!content || !content->GetActiveMenuChild()) {
470 return nullptr;
472 return mDoc->GetAccessible(content->GetActiveMenuChild());
475 void XULMenubarAccessible::SetCurrentItem(const LocalAccessible* aItem) {
476 NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");