diff options
Diffstat (limited to 'accessible/xul/XULMenuAccessible.cpp')
-rw-r--r-- | accessible/xul/XULMenuAccessible.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/accessible/xul/XULMenuAccessible.cpp b/accessible/xul/XULMenuAccessible.cpp new file mode 100644 index 000000000..f93e9ad99 --- /dev/null +++ b/accessible/xul/XULMenuAccessible.cpp @@ -0,0 +1,591 @@ +/* -*- 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 "nsIDOMElement.h" +#include "nsIDOMXULElement.h" +#include "nsIMutableArray.h" +#include "nsIDOMXULContainerElement.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIDOMXULMultSelectCntrlEl.h" +#include "nsIDOMKeyEvent.h" +#include "nsIServiceManager.h" +#include "nsIPresShell.h" +#include "nsIContent.h" +#include "nsMenuBarFrame.h" +#include "nsMenuPopupFrame.h" + +#include "mozilla/Preferences.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenuitemAccessible:: + XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc) : + AccessibleWrap(aContent, aDoc) +{ + mStateFlags |= eNoXBLKids; +} + +uint64_t +XULMenuitemAccessible::NativeState() +{ + uint64_t state = Accessible::NativeState(); + + // Has Popup? + if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) { + state |= states::HASPOPUP; + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::open)) + state |= states::EXPANDED; + else + state |= states::COLLAPSED; + } + + // Checkable/checked? + static nsIContent::AttrValuesArray strings[] = + { &nsGkAtoms::radio, &nsGkAtoms::checkbox, nullptr }; + + if (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings, + eCaseMatters) >= 0) { + + // Checkable? + state |= states::CHECKABLE; + + // Checked? + if (mContent->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(do_QueryInterface(mContent)); + 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->Role() == roles::COMBOBOX, + "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::eIntID_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) +{ + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + return eNameOK; +} + +void +XULMenuitemAccessible::Description(nsString& aDescription) +{ + mContent->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->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 nsIDOMKeyEvent::DOM_VK_CONTROL: + modifierKey = KeyBinding::kControl; + break; + case nsIDOMKeyEvent::DOM_VK_ALT: + modifierKey = KeyBinding::kAlt; + break; + case nsIDOMKeyEvent::DOM_VK_META: + modifierKey = KeyBinding::kMeta; + break; + case nsIDOMKeyEvent::DOM_VK_WIN: + modifierKey = KeyBinding::kOS; + break; + } + } + } + + return KeyBinding(accesskey[0], modifierKey); +} + +KeyBinding +XULMenuitemAccessible::KeyboardShortcut() const +{ + nsAutoString keyElmId; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyElmId); + if (keyElmId.IsEmpty()) + return KeyBinding(); + + nsIContent* 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, kAutoDetect); + } 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() +{ + nsCOMPtr<nsIDOMXULContainerElement> xulContainer(do_QueryInterface(mContent)); + if (xulContainer) + return roles::PARENT_MENUITEM; + + if (mParent && mParent->Role() == roles::COMBOBOX_LIST) + return roles::COMBOBOX_OPTION; + + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::radio, eCaseMatters)) + return roles::RADIO_MENU_ITEM; + + if (mContent->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) +{ + 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() +{ + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenuitemAccessible: Widgets + +bool +XULMenuitemAccessible::IsActiveWidget() const +{ + // Parent menu item is a widget, it's active when its popup is open. + nsIContent* menuPopupContent = mContent->GetFirstChild(); + 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->GetFirstChild(); + 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) { + if (menuParent->IsMenuBar()) // menubar menu + return mParent; + + // a menupoup or parent menu item + if (menuParent->IsMenu()) + return mParent; + + // 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() +{ + // Isn't focusable, but can be offscreen/invisible -- only copy those states + return XULMenuitemAccessible::NativeState() & + (states::OFFSCREEN | states::INVISIBLE); +} + +ENameValueFlag +XULMenuSeparatorAccessible::NativeName(nsString& aName) +{ + return eNameOK; +} + +role +XULMenuSeparatorAccessible::NativeRole() +{ + return roles::SEPARATOR; +} + +bool +XULMenuSeparatorAccessible::DoAction(uint8_t index) +{ + return false; +} + +void +XULMenuSeparatorAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + aName.Truncate(); +} + +uint8_t +XULMenuSeparatorAccessible::ActionCount() +{ + 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) + mSelectControl = do_QueryInterface(mContent->GetFlattenedTreeParent()); + if (!mSelectControl) + mGenericTypes &= ~eSelect; + + mStateFlags |= eNoXBLKids; +} + +uint64_t +XULMenupopupAccessible::NativeState() +{ + uint64_t state = Accessible::NativeState(); + +#ifdef DEBUG + // We are onscreen if our parent is active + bool isActive = mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive); + if (!isActive) { + Accessible* parent = Parent(); + if (parent) { + nsIContent* parentContent = parent->GetContent(); + if (parentContent) + isActive = parentContent->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) +{ + nsIContent* content = mContent; + while (content && aName.IsEmpty()) { + content->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); + content = content->GetFlattenedTreeParent(); + } + + return eNameOK; +} + +role +XULMenupopupAccessible::NativeRole() +{ + // If accessible is not bound to the tree (this happens while children are + // cached) return general role. + if (mParent) { + roles::Role role = mParent->Role(); + if (role == roles::COMBOBOX || role == roles::AUTOCOMPLETE) + return roles::COMBOBOX_LIST; + + if (role == roles::PUSHBUTTON) { + // Some widgets like the search bar have several popups, owned by buttons. + Accessible* grandParent = mParent->Parent(); + if (grandParent && grandParent->Role() == roles::AUTOCOMPLETE) + return roles::COMBOBOX_LIST; + } + } + + 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); + } + + NS_NOTREACHED("Shouldn't be a real case."); + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// XULMenubarAccessible +//////////////////////////////////////////////////////////////////////////////// + +XULMenubarAccessible:: + XULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc) : + AccessibleWrap(aContent, aDoc) +{ +} + +ENameValueFlag +XULMenubarAccessible::NativeName(nsString& aName) +{ + aName.AssignLiteral("Application"); + return eNameOK; +} + +role +XULMenubarAccessible::NativeRole() +{ + 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() +{ + 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(Accessible* aItem) +{ + NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented"); +} |