diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/xul/XULMenuAccessible.cpp | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/accessible/xul/XULMenuAccessible.cpp b/accessible/xul/XULMenuAccessible.cpp new file mode 100644 index 0000000000..a0d98d1aaf --- /dev/null +++ b/accessible/xul/XULMenuAccessible.cpp @@ -0,0 +1,513 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "XULMenuAccessible.h" + +#include "Accessible-inl.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "Role.h" +#include "States.h" +#include "XULFormControlAccessible.h" + +#include "nsIDOMXULContainerElement.h" +#include "nsIDOMXULSelectCntrlEl.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIContent.h" +#include "nsMenuBarFrame.h" +#include "nsMenuPopupFrame.h" + +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/KeyboardEventBinding.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenuitemAccessible::XULMenuitemAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +uint64_t XULMenuitemAccessible::NativeState() const { + uint64_t state = Accessible::NativeState(); + + // Has Popup? + if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) { + state |= states::HASPOPUP; + if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::open)) + state |= states::EXPANDED; + else + state |= states::COLLAPSED; + } + + // Checkable/checked? + static dom::Element::AttrValuesArray strings[] = { + nsGkAtoms::radio, nsGkAtoms::checkbox, nullptr}; + + if (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + strings, eCaseMatters) >= 0) { + // Checkable? + state |= states::CHECKABLE; + + // Checked? + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::checked, nsGkAtoms::_true, + eCaseMatters)) + state |= states::CHECKED; + } + + // Combo box listitem + bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION); + if (isComboboxOption) { + // Is selected? + bool isSelected = false; + nsCOMPtr<nsIDOMXULSelectControlItemElement> item = + Elm()->AsXULSelectControlItem(); + NS_ENSURE_TRUE(item, state); + item->GetSelected(&isSelected); + + // Is collapsed? + bool isCollapsed = false; + Accessible* parent = Parent(); + if (parent && parent->State() & states::INVISIBLE) isCollapsed = true; + + if (isSelected) { + state |= states::SELECTED; + + // Selected and collapsed? + if (isCollapsed) { + // Set selected option offscreen/invisible according to combobox state + Accessible* grandParent = parent->Parent(); + if (!grandParent) return state; + NS_ASSERTION(grandParent->IsCombobox(), + "grandparent of combobox listitem is not combobox"); + uint64_t grandParentState = grandParent->State(); + state &= ~(states::OFFSCREEN | states::INVISIBLE); + state |= (grandParentState & states::OFFSCREEN) | + (grandParentState & states::INVISIBLE) | + (grandParentState & states::OPAQUE1); + } // isCollapsed + } // isSelected + } // ROLE_COMBOBOX_OPTION + + return state; +} + +uint64_t XULMenuitemAccessible::NativeInteractiveState() const { + if (NativelyUnavailable()) { + // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic. + bool skipNavigatingDisabledMenuItem = true; + nsMenuFrame* menuFrame = do_QueryFrame(GetFrame()); + if (!menuFrame || !menuFrame->IsOnMenuBar()) { + skipNavigatingDisabledMenuItem = + LookAndFeel::GetInt( + LookAndFeel::IntID::SkipNavigatingDisabledMenuItem, 0) != 0; + } + + if (skipNavigatingDisabledMenuItem) return states::UNAVAILABLE; + + return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE; + } + + return states::FOCUSABLE | states::SELECTABLE; +} + +ENameValueFlag XULMenuitemAccessible::NativeName(nsString& aName) const { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + return eNameOK; +} + +void XULMenuitemAccessible::Description(nsString& aDescription) { + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::description, + aDescription); +} + +KeyBinding XULMenuitemAccessible::AccessKey() const { + // Return menu accesskey: N or Alt+F. + static int32_t gMenuAccesskeyModifier = + -1; // magic value of -1 indicates unitialized state + + // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for + // menu are't registered by EventStateManager. + nsAutoString accesskey; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, + accesskey); + if (accesskey.IsEmpty()) return KeyBinding(); + + uint32_t modifierKey = 0; + + Accessible* parentAcc = Parent(); + if (parentAcc) { + if (parentAcc->NativeRole() == roles::MENUBAR) { + // If top level menu item, add Alt+ or whatever modifier text to string + // No need to cache pref service, this happens rarely + if (gMenuAccesskeyModifier == -1) { + // Need to initialize cached global accesskey pref + gMenuAccesskeyModifier = Preferences::GetInt("ui.key.menuAccessKey", 0); + } + + switch (gMenuAccesskeyModifier) { + case dom::KeyboardEvent_Binding::DOM_VK_CONTROL: + modifierKey = KeyBinding::kControl; + break; + case dom::KeyboardEvent_Binding::DOM_VK_ALT: + modifierKey = KeyBinding::kAlt; + break; + case dom::KeyboardEvent_Binding::DOM_VK_META: + modifierKey = KeyBinding::kMeta; + break; + case dom::KeyboardEvent_Binding::DOM_VK_WIN: + modifierKey = KeyBinding::kOS; + break; + } + } + } + + return KeyBinding(accesskey[0], modifierKey); +} + +KeyBinding XULMenuitemAccessible::KeyboardShortcut() const { + nsAutoString keyElmId; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyElmId); + if (keyElmId.IsEmpty()) return KeyBinding(); + + dom::Element* keyElm = mContent->OwnerDoc()->GetElementById(keyElmId); + if (!keyElm) return KeyBinding(); + + uint32_t key = 0; + + nsAutoString keyStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr); + if (keyStr.IsEmpty()) { + nsAutoString keyCodeStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeStr); + nsresult errorCode; + key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10); + if (NS_FAILED(errorCode)) { + key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16); + } + } else { + key = keyStr[0]; + } + + nsAutoString modifiersStr; + keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr); + + uint32_t modifierMask = 0; + if (modifiersStr.Find("shift") != -1) modifierMask |= KeyBinding::kShift; + if (modifiersStr.Find("alt") != -1) modifierMask |= KeyBinding::kAlt; + if (modifiersStr.Find("meta") != -1) modifierMask |= KeyBinding::kMeta; + if (modifiersStr.Find("os") != -1) modifierMask |= KeyBinding::kOS; + if (modifiersStr.Find("control") != -1) modifierMask |= KeyBinding::kControl; + if (modifiersStr.Find("accel") != -1) { + modifierMask |= KeyBinding::AccelModifier(); + } + + return KeyBinding(key, modifierMask); +} + +role XULMenuitemAccessible::NativeRole() const { + nsCOMPtr<nsIDOMXULContainerElement> xulContainer = Elm()->AsXULContainer(); + if (xulContainer) return roles::PARENT_MENUITEM; + + Accessible* widget = ContainerWidget(); + if (widget && widget->Role() == roles::COMBOBOX_LIST) { + return roles::COMBOBOX_OPTION; + } + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::radio, eCaseMatters)) + return roles::RADIO_MENU_ITEM; + + if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::checkbox, eCaseMatters)) + return roles::CHECK_MENU_ITEM; + + return roles::MENUITEM; +} + +int32_t XULMenuitemAccessible::GetLevelInternal() { + return nsAccUtils::GetLevelForXULContainerItem(mContent); +} + +bool XULMenuitemAccessible::DoAction(uint8_t index) const { + if (index == eAction_Click) { // default action + DoCommand(); + return true; + } + + return false; +} + +void XULMenuitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { + if (aIndex == eAction_Click) aName.AssignLiteral("click"); +} + +uint8_t XULMenuitemAccessible::ActionCount() const { return 1; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible: Widgets + +bool XULMenuitemAccessible::IsActiveWidget() const { + // Parent menu item is a widget, it's active when its popup is open. + // Typically the <menupopup> is included in the document markup, and + // <menu> prepends content in front of it. + nsIContent* menuPopupContent = mContent->GetLastChild(); + if (menuPopupContent) { + nsMenuPopupFrame* menuPopupFrame = + do_QueryFrame(menuPopupContent->GetPrimaryFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); + } + return false; +} + +bool XULMenuitemAccessible::AreItemsOperable() const { + // Parent menu item is a widget, its items are operable when its popup is + // open. + nsIContent* menuPopupContent = mContent->GetLastChild(); + if (menuPopupContent) { + nsMenuPopupFrame* menuPopupFrame = + do_QueryFrame(menuPopupContent->GetPrimaryFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); + } + return false; +} + +Accessible* XULMenuitemAccessible::ContainerWidget() const { + nsMenuFrame* menuFrame = do_QueryFrame(GetFrame()); + if (menuFrame) { + nsMenuParent* menuParent = menuFrame->GetMenuParent(); + if (menuParent) { + nsBoxFrame* frame = nullptr; + if (menuParent->IsMenuBar()) { // menubar menu + frame = static_cast<nsMenuBarFrame*>(menuParent); + } else if (menuParent->IsMenu()) { // a menupopup or parent menu item + frame = static_cast<nsMenuPopupFrame*>(menuParent); + } + if (frame) { + nsIContent* content = frame->GetContent(); + if (content) { + MOZ_ASSERT(mDoc); + // We use GetAccessibleOrContainer instead of just GetAccessible + // because we strip menupopups from the tree for ATK. + return mDoc->GetAccessibleOrContainer(content); + } + } + + // otherwise it's different kind of popups (like panel or tooltip), it + // shouldn't be a real case. + } + } + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuSeparatorAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULMenuitemAccessible(aContent, aDoc) {} + +uint64_t XULMenuSeparatorAccessible::NativeState() const { + // Isn't focusable, but can be offscreen/invisible -- only copy those states + return XULMenuitemAccessible::NativeState() & + (states::OFFSCREEN | states::INVISIBLE); +} + +ENameValueFlag XULMenuSeparatorAccessible::NativeName(nsString& aName) const { + return eNameOK; +} + +role XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR; } + +bool XULMenuSeparatorAccessible::DoAction(uint8_t index) const { return false; } + +void XULMenuSeparatorAccessible::ActionNameAt(uint8_t aIndex, + nsAString& aName) { + aName.Truncate(); +} + +uint8_t XULMenuSeparatorAccessible::ActionCount() const { return 0; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenupopupAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenupopupAccessible::XULMenupopupAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : XULSelectControlAccessible(aContent, aDoc) { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + if (menuPopupFrame && menuPopupFrame->IsMenu()) mType = eMenuPopupType; + + // May be the anonymous <menupopup> inside <menulist> (a combobox) + auto* parent = mContent->GetParentElement(); + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl = + parent ? parent->AsXULSelectControl() : nullptr; + if (selectControl) { + mSelectControl = parent; + } else { + mSelectControl = nullptr; + mGenericTypes &= ~eSelect; + } +} + +uint64_t XULMenupopupAccessible::NativeState() const { + uint64_t state = Accessible::NativeState(); + +#ifdef DEBUG + // We are onscreen if our parent is active + bool isActive = + mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive); + if (!isActive) { + Accessible* parent = Parent(); + if (parent) { + nsIContent* parentContent = parent->GetContent(); + if (parentContent && parentContent->IsElement()) + isActive = parentContent->AsElement()->HasAttr(kNameSpaceID_None, + nsGkAtoms::open); + } + } + + NS_ASSERTION(isActive || (state & states::INVISIBLE), + "XULMenupopup doesn't have INVISIBLE when it's inactive"); +#endif + + if (state & states::INVISIBLE) state |= states::OFFSCREEN | states::COLLAPSED; + + return state; +} + +ENameValueFlag XULMenupopupAccessible::NativeName(nsString& aName) const { + nsIContent* content = mContent; + while (content && aName.IsEmpty()) { + if (content->IsElement()) { + content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + } + content = content->GetFlattenedTreeParent(); + } + + return eNameOK; +} + +role XULMenupopupAccessible::NativeRole() const { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + if (menuPopupFrame && menuPopupFrame->IsContextMenu()) { + return roles::MENUPOPUP; + } + + if (mParent) { + if (mParent->IsCombobox() || mParent->IsAutoComplete()) + return roles::COMBOBOX_LIST; + + if (mParent->Role() == roles::PUSHBUTTON) { + // Some widgets like the search bar have several popups, owned by buttons. + Accessible* grandParent = mParent->Parent(); + if (grandParent && grandParent->IsAutoComplete()) + return roles::COMBOBOX_LIST; + } + } + + // If accessible is not bound to the tree (this happens while children are + // cached) return general role. + return roles::MENUPOPUP; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenupopupAccessible: Widgets + +bool XULMenupopupAccessible::IsWidget() const { return true; } + +bool XULMenupopupAccessible::IsActiveWidget() const { + // If menupopup is a widget (the case of context menus) then active when open. + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); +} + +bool XULMenupopupAccessible::AreItemsOperable() const { + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + return menuPopupFrame && menuPopupFrame->IsOpen(); +} + +Accessible* XULMenupopupAccessible::ContainerWidget() const { + DocAccessible* document = Document(); + + nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame()); + while (menuPopupFrame) { + Accessible* menuPopup = + document->GetAccessible(menuPopupFrame->GetContent()); + if (!menuPopup) // shouldn't be a real case + return nullptr; + + nsMenuFrame* menuFrame = do_QueryFrame(menuPopupFrame->GetParent()); + if (!menuFrame) // context menu or popups + return nullptr; + + nsMenuParent* menuParent = menuFrame->GetMenuParent(); + if (!menuParent) // menulist or menubutton + return menuPopup->Parent(); + + if (menuParent->IsMenuBar()) { // menubar menu + nsMenuBarFrame* menuBarFrame = static_cast<nsMenuBarFrame*>(menuParent); + return document->GetAccessible(menuBarFrame->GetContent()); + } + + // different kind of popups like panel or tooltip + if (!menuParent->IsMenu()) return nullptr; + + menuPopupFrame = static_cast<nsMenuPopupFrame*>(menuParent); + } + + MOZ_ASSERT_UNREACHABLE("Shouldn't be a real case."); + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenubarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenubarAccessible::XULMenubarAccessible(nsIContent* aContent, + DocAccessible* aDoc) + : AccessibleWrap(aContent, aDoc) {} + +ENameValueFlag XULMenubarAccessible::NativeName(nsString& aName) const { + aName.AssignLiteral("Application"); + return eNameOK; +} + +role XULMenubarAccessible::NativeRole() const { return roles::MENUBAR; } + +//////////////////////////////////////////////////////////////////////////////// +// XULMenubarAccessible: Widgets + +bool XULMenubarAccessible::IsActiveWidget() const { + nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame()); + return menuBarFrame && menuBarFrame->IsActive(); +} + +bool XULMenubarAccessible::AreItemsOperable() const { return true; } + +Accessible* XULMenubarAccessible::CurrentItem() const { + nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame()); + if (menuBarFrame) { + nsMenuFrame* menuFrame = menuBarFrame->GetCurrentMenuItem(); + if (menuFrame) { + nsIContent* menuItemNode = menuFrame->GetContent(); + return mDoc->GetAccessible(menuItemNode); + } + } + return nullptr; +} + +void XULMenubarAccessible::SetCurrentItem(const Accessible* aItem) { + NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented"); +} |