Bug 1799258 - Do color-management on Windows+DComp via IDCompositionFilterEffects...
[gecko.git] / accessible / xul / XULMenuAccessible.cpp
blobb01061876767d06b61ea842d4d99e296d18c70ff
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"
15 #include "Role.h"
16 #include "States.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,
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(kNameSpaceID_None, 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(kNameSpaceID_None, nsGkAtoms::label, aName);
132 return eNameOK;
135 void XULMenuitemAccessible::Description(nsString& aDescription) const {
136 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::description,
137 aDescription);
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,
149 accesskey);
150 if (accesskey.IsEmpty()) return KeyBinding();
152 uint32_t modifierKey = 0;
154 LocalAccessible* parentAcc = LocalParent();
155 if (parentAcc) {
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;
167 break;
168 case dom::KeyboardEvent_Binding::DOM_VK_ALT:
169 modifierKey = KeyBinding::kAlt;
170 break;
171 case dom::KeyboardEvent_Binding::DOM_VK_META:
172 modifierKey = KeyBinding::kMeta;
173 break;
174 case dom::KeyboardEvent_Binding::DOM_VK_WIN:
175 modifierKey = KeyBinding::kOS;
176 break;
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();
192 uint32_t key = 0;
194 nsAutoString keyStr;
195 keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
196 if (keyStr.IsEmpty()) {
197 nsAutoString keyCodeStr;
198 keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeStr);
199 nsresult errorCode;
200 key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10);
201 if (NS_FAILED(errorCode)) {
202 key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16);
204 } else {
205 key = keyStr[0];
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();
269 return false;
272 bool XULMenuitemAccessible::AreItemsOperable() const {
273 // Parent menu item is a widget, its items are operable when its popup is
274 // open.
275 nsIContent* menuPopupContent = mContent->GetLastChild();
276 if (menuPopupContent) {
277 nsMenuPopupFrame* menuPopupFrame =
278 do_QueryFrame(menuPopupContent->GetPrimaryFrame());
279 return menuPopupFrame && menuPopupFrame->IsOpen();
281 return false;
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);
292 return nullptr;
295 ////////////////////////////////////////////////////////////////////////////////
296 // XULMenuSeparatorAccessible
297 ////////////////////////////////////////////////////////////////////////////////
299 XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent* aContent,
300 DocAccessible* aDoc)
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 {
310 return eNameOK;
313 role XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR; }
315 bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; }
317 ////////////////////////////////////////////////////////////////////////////////
318 // XULMenupopupAccessible
319 ////////////////////////////////////////////////////////////////////////////////
321 XULMenupopupAccessible::XULMenupopupAccessible(nsIContent* aContent,
322 DocAccessible* aDoc)
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;
334 if (selectControl) {
335 mSelectControl = parent;
336 } else {
337 mSelectControl = nullptr;
338 mGenericTypes &= ~eSelect;
342 uint64_t XULMenupopupAccessible::NativeState() const {
343 uint64_t state = LocalAccessible::NativeState();
345 #ifdef DEBUG
346 // We are onscreen if our parent is active
347 bool isActive =
348 mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive);
349 if (!isActive) {
350 LocalAccessible* parent = LocalParent();
351 if (parent) {
352 nsIContent* parentContent = parent->GetContent();
353 if (parentContent && parentContent->IsElement())
354 isActive = parentContent->AsElement()->HasAttr(kNameSpaceID_None,
355 nsGkAtoms::open);
359 NS_ASSERTION(isActive || (state & states::INVISIBLE),
360 "XULMenupopup doesn't have INVISIBLE when it's inactive");
361 #endif
363 if (state & states::INVISIBLE) state |= states::OFFSCREEN | states::COLLAPSED;
365 return state;
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();
377 return eNameOK;
380 role XULMenupopupAccessible::NativeRole() const {
381 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
382 if (menuPopupFrame && menuPopupFrame->IsContextMenu()) {
383 return roles::MENUPOPUP;
386 if (mParent) {
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) {
419 return nullptr;
422 auto* cur = dom::XULPopupElement::FromNode(GetContent());
423 while (cur) {
424 auto* menu = cur->GetContainingMenu();
425 if (!menu) {
426 // <panel> / <tooltip> / etc.
427 return nullptr;
429 dom::XULMenuParentElement* parent = menu->GetMenuParent();
430 if (!parent) {
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?");
445 return nullptr;
448 ////////////////////////////////////////////////////////////////////////////////
449 // XULMenubarAccessible
450 ////////////////////////////////////////////////////////////////////////////////
452 XULMenubarAccessible::XULMenubarAccessible(nsIContent* aContent,
453 DocAccessible* aDoc)
454 : AccessibleWrap(aContent, aDoc) {}
456 ENameValueFlag XULMenubarAccessible::NativeName(nsString& aName) const {
457 aName.AssignLiteral("Application");
458 return eNameOK;
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());
475 MOZ_ASSERT(content);
476 if (!content || !content->GetActiveMenuChild()) {
477 return nullptr;
479 return mDoc->GetAccessible(content->GetActiveMenuChild());
482 void XULMenubarAccessible::SetCurrentItem(const LocalAccessible* aItem) {
483 NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");