tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

XULMenuAccessible.cpp (16673B)


      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/. */
      5 
      6 #include "XULMenuAccessible.h"
      7 
      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"
     19 
     20 #include "nsIContentInlines.h"
     21 #include "nsIDOMXULContainerElement.h"
     22 #include "nsIDOMXULSelectCntrlEl.h"
     23 #include "nsIDOMXULSelectCntrlItemEl.h"
     24 #include "nsIContent.h"
     25 #include "nsMenuPopupFrame.h"
     26 
     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"
     33 
     34 using namespace mozilla;
     35 using namespace mozilla::a11y;
     36 
     37 ////////////////////////////////////////////////////////////////////////////////
     38 // XULMenuitemAccessible
     39 ////////////////////////////////////////////////////////////////////////////////
     40 
     41 XULMenuitemAccessible::XULMenuitemAccessible(nsIContent* aContent,
     42                                             DocAccessible* aDoc)
     43    : AccessibleWrap(aContent, aDoc) {}
     44 
     45 uint64_t XULMenuitemAccessible::NativeState() const {
     46  uint64_t state = LocalAccessible::NativeState();
     47 
     48  // Has Popup?
     49  if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) {
     50    state |= states::HASPOPUP | states::EXPANDABLE;
     51    if (mContent->AsElement()->HasAttr(nsGkAtoms::open)) {
     52      state |= states::EXPANDED;
     53    }
     54  }
     55 
     56  // Checkable/checked?
     57  static dom::Element::AttrValuesArray strings[] = {
     58      nsGkAtoms::radio, nsGkAtoms::checkbox, nullptr};
     59 
     60  if (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
     61                                             strings, eCaseMatters) >= 0) {
     62    // Checkable?
     63    state |= states::CHECKABLE;
     64 
     65    // Checked?
     66    if (mContent->AsElement()->GetBoolAttr(nsGkAtoms::checked)) {
     67      state |= states::CHECKED;
     68    }
     69  }
     70 
     71  // Combo box listitem
     72  bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION);
     73  if (isComboboxOption) {
     74    // Is selected?
     75    bool isSelected = false;
     76    nsCOMPtr<nsIDOMXULSelectControlItemElement> item =
     77        Elm()->AsXULSelectControlItem();
     78    NS_ENSURE_TRUE(item, state);
     79    item->GetSelected(&isSelected);
     80 
     81    // Is collapsed?
     82    bool isCollapsed = false;
     83    LocalAccessible* parent = LocalParent();
     84    if (parent && parent->State() & states::INVISIBLE) isCollapsed = true;
     85 
     86    if (isSelected) {
     87      state |= states::SELECTED;
     88 
     89      // Selected and collapsed?
     90      if (isCollapsed) {
     91        // Set selected option offscreen/invisible according to combobox state
     92        LocalAccessible* grandParent = parent->LocalParent();
     93        if (!grandParent) return state;
     94        NS_ASSERTION(grandParent->IsCombobox(),
     95                     "grandparent of combobox listitem is not combobox");
     96        uint64_t grandParentState = grandParent->State();
     97        state &= ~(states::OFFSCREEN | states::INVISIBLE);
     98        state |= (grandParentState & states::OFFSCREEN) |
     99                 (grandParentState & states::INVISIBLE) |
    100                 (grandParentState & states::OPAQUE1);
    101      }  // isCollapsed
    102    }  // isSelected
    103  }  // ROLE_COMBOBOX_OPTION
    104 
    105  return state;
    106 }
    107 
    108 uint64_t XULMenuitemAccessible::NativeInteractiveState() const {
    109  if (NativelyUnavailable()) {
    110    // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic.
    111    auto* button = dom::XULButtonElement::FromNode(GetContent());
    112    bool skipNavigatingDisabledMenuItem = true;
    113    if (!button || !button->IsOnMenuBar()) {
    114      skipNavigatingDisabledMenuItem = LookAndFeel::GetInt(
    115          LookAndFeel::IntID::SkipNavigatingDisabledMenuItem);
    116    }
    117 
    118    if (skipNavigatingDisabledMenuItem) return states::UNAVAILABLE;
    119 
    120    return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE;
    121  }
    122 
    123  return states::FOCUSABLE | states::SELECTABLE;
    124 }
    125 
    126 ENameValueFlag XULMenuitemAccessible::NativeName(nsString& aName) const {
    127  mContent->AsElement()->GetAttr(nsGkAtoms::label, aName);
    128  return eNameOK;
    129 }
    130 
    131 ENameValueFlag XULMenuitemAccessible::DirectName(nsString& aName) const {
    132  ENameValueFlag flag = AccessibleWrap::DirectName(aName);
    133  if (!aName.IsEmpty()) {
    134    // We can't handle this in NativeName() because some menuitems use
    135    // aria-label rather than label, and aria-label is returned by
    136    // LocalAccessible::name().
    137    nsAutoString badge;
    138    mContent->AsElement()->GetAttr(nsGkAtoms::badge, badge);
    139    if (!badge.IsEmpty()) {
    140      aName += ' ';
    141      aName.Append(badge);
    142    }
    143  }
    144  return flag;
    145 }
    146 
    147 EDescriptionValueFlag XULMenuitemAccessible::Description(
    148    nsString& aDescription) const {
    149  mContent->AsElement()->GetAttr(nsGkAtoms::description, aDescription);
    150 
    151  return eDescriptionOK;
    152 }
    153 
    154 KeyBinding XULMenuitemAccessible::AccessKey() const {
    155  // Return menu accesskey: N or Alt+F.
    156  static int32_t gMenuAccesskeyModifier =
    157      -1;  // magic value of -1 indicates unitialized state
    158 
    159  // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for
    160  // menu are't registered by EventStateManager.
    161  nsAutoString accesskey;
    162  mContent->AsElement()->GetAttr(nsGkAtoms::accesskey, accesskey);
    163  if (accesskey.IsEmpty()) return KeyBinding();
    164 
    165  uint32_t modifierKey = 0;
    166 
    167  LocalAccessible* parentAcc = LocalParent();
    168  if (parentAcc) {
    169    if (parentAcc->NativeRole() == roles::MENUBAR) {
    170      // If top level menu item, add Alt+ or whatever modifier text to string
    171      // No need to cache pref service, this happens rarely
    172      if (gMenuAccesskeyModifier == -1) {
    173        // Need to initialize cached global accesskey pref
    174        gMenuAccesskeyModifier = Preferences::GetInt("ui.key.menuAccessKey", 0);
    175      }
    176 
    177      switch (gMenuAccesskeyModifier) {
    178        case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
    179          modifierKey = KeyBinding::kControl;
    180          break;
    181        case dom::KeyboardEvent_Binding::DOM_VK_ALT:
    182          modifierKey = KeyBinding::kAlt;
    183          break;
    184        case dom::KeyboardEvent_Binding::DOM_VK_META:
    185        case dom::KeyboardEvent_Binding::DOM_VK_WIN:
    186          modifierKey = KeyBinding::kMeta;
    187          break;
    188      }
    189    }
    190  }
    191 
    192  return KeyBinding(accesskey[0], modifierKey);
    193 }
    194 
    195 KeyBinding XULMenuitemAccessible::KeyboardShortcut() const {
    196  nsAutoString keyElmId;
    197  mContent->AsElement()->GetAttr(nsGkAtoms::key, keyElmId);
    198  if (keyElmId.IsEmpty()) return KeyBinding();
    199 
    200  dom::Element* keyElm = mContent->OwnerDoc()->GetElementById(keyElmId);
    201  if (!keyElm) return KeyBinding();
    202 
    203  uint32_t key = 0;
    204 
    205  nsAutoString keyStr;
    206  keyElm->GetAttr(nsGkAtoms::key, keyStr);
    207  if (keyStr.IsEmpty()) {
    208    nsAutoString keyCodeStr;
    209    keyElm->GetAttr(nsGkAtoms::keycode, keyCodeStr);
    210    nsresult errorCode;
    211    key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10);
    212    if (NS_FAILED(errorCode)) {
    213      key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16);
    214    }
    215  } else {
    216    key = keyStr[0];
    217  }
    218 
    219  nsAutoString modifiersStr;
    220  keyElm->GetAttr(nsGkAtoms::modifiers, modifiersStr);
    221 
    222  uint32_t modifierMask = 0;
    223  if (modifiersStr.Find(u"shift") != -1) modifierMask |= KeyBinding::kShift;
    224  if (modifiersStr.Find(u"alt") != -1) modifierMask |= KeyBinding::kAlt;
    225  if (modifiersStr.Find(u"meta") != -1) modifierMask |= KeyBinding::kMeta;
    226  if (modifiersStr.Find(u"control") != -1) modifierMask |= KeyBinding::kControl;
    227  if (modifiersStr.Find(u"accel") != -1) {
    228    modifierMask |= KeyBinding::AccelModifier();
    229  }
    230 
    231  return KeyBinding(key, modifierMask);
    232 }
    233 
    234 role XULMenuitemAccessible::NativeRole() const {
    235  nsCOMPtr<nsIDOMXULContainerElement> xulContainer = Elm()->AsXULContainer();
    236  if (xulContainer) return roles::PARENT_MENUITEM;
    237 
    238  LocalAccessible* widget = ContainerWidget();
    239  if (widget && widget->Role() == roles::COMBOBOX_LIST) {
    240    return roles::COMBOBOX_OPTION;
    241  }
    242 
    243  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    244                                         nsGkAtoms::radio, eCaseMatters)) {
    245    return roles::RADIO_MENU_ITEM;
    246  }
    247 
    248  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    249                                         nsGkAtoms::checkbox, eCaseMatters)) {
    250    return roles::CHECK_MENU_ITEM;
    251  }
    252 
    253  return roles::MENUITEM;
    254 }
    255 
    256 int32_t XULMenuitemAccessible::GetLevel(bool aFast) const {
    257  return nsAccUtils::GetLevelForXULContainerItem(mContent);
    258 }
    259 
    260 void XULMenuitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
    261  if (aIndex == eAction_Click) aName.AssignLiteral("click");
    262 }
    263 
    264 bool XULMenuitemAccessible::HasPrimaryAction() const { return true; }
    265 
    266 ////////////////////////////////////////////////////////////////////////////////
    267 // XULMenuitemAccessible: Widgets
    268 
    269 bool XULMenuitemAccessible::IsActiveWidget() const {
    270  // Parent menu item is a widget, it's active when its popup is open.
    271  // Typically the <menupopup> is included in the document markup, and
    272  // <menu> prepends content in front of it.
    273  nsIContent* menuPopupContent = mContent->GetLastChild();
    274  if (menuPopupContent) {
    275    nsMenuPopupFrame* menuPopupFrame =
    276        do_QueryFrame(menuPopupContent->GetPrimaryFrame());
    277    return menuPopupFrame && menuPopupFrame->IsOpen();
    278  }
    279  return false;
    280 }
    281 
    282 bool XULMenuitemAccessible::AreItemsOperable() const {
    283  // Parent menu item is a widget, its items are operable when its popup is
    284  // open.
    285  nsIContent* menuPopupContent = mContent->GetLastChild();
    286  if (menuPopupContent) {
    287    nsMenuPopupFrame* menuPopupFrame =
    288        do_QueryFrame(menuPopupContent->GetPrimaryFrame());
    289    return menuPopupFrame && menuPopupFrame->IsOpen();
    290  }
    291  return false;
    292 }
    293 
    294 LocalAccessible* XULMenuitemAccessible::ContainerWidget() const {
    295  if (auto* button = dom::XULButtonElement::FromNode(GetContent())) {
    296    if (auto* popup = button->GetMenuParent()) {
    297      // We use GetAccessibleOrContainer instead of just GetAccessible because
    298      // we strip menupopups from the tree for ATK.
    299      return mDoc->GetAccessibleOrContainer(popup);
    300    }
    301  }
    302  return nullptr;
    303 }
    304 
    305 ////////////////////////////////////////////////////////////////////////////////
    306 // XULMenuSeparatorAccessible
    307 ////////////////////////////////////////////////////////////////////////////////
    308 
    309 XULMenuSeparatorAccessible::XULMenuSeparatorAccessible(nsIContent* aContent,
    310                                                       DocAccessible* aDoc)
    311    : XULMenuitemAccessible(aContent, aDoc) {}
    312 
    313 uint64_t XULMenuSeparatorAccessible::NativeState() const {
    314  // Isn't focusable, but can be offscreen/invisible -- only copy those states
    315  return XULMenuitemAccessible::NativeState() &
    316         (states::OFFSCREEN | states::INVISIBLE);
    317 }
    318 
    319 ENameValueFlag XULMenuSeparatorAccessible::NativeName(nsString& aName) const {
    320  return eNameOK;
    321 }
    322 
    323 role XULMenuSeparatorAccessible::NativeRole() const { return roles::SEPARATOR; }
    324 
    325 bool XULMenuSeparatorAccessible::HasPrimaryAction() const { return false; }
    326 
    327 ////////////////////////////////////////////////////////////////////////////////
    328 // XULMenupopupAccessible
    329 ////////////////////////////////////////////////////////////////////////////////
    330 
    331 XULMenupopupAccessible::XULMenupopupAccessible(nsIContent* aContent,
    332                                               DocAccessible* aDoc)
    333    : XULSelectControlAccessible(aContent, aDoc) {
    334  if (nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame())) {
    335    if (menuPopupFrame->GetPopupType() == widget::PopupType::Menu) {
    336      mType = eMenuPopupType;
    337    }
    338  }
    339 
    340  // May be the anonymous <menupopup> inside <menulist> (a combobox)
    341  auto* parent = mContent->GetParentElement();
    342  nsCOMPtr<nsIDOMXULSelectControlElement> selectControl =
    343      parent ? parent->AsXULSelectControl() : nullptr;
    344  if (selectControl) {
    345    mSelectControl = parent;
    346  } else {
    347    mSelectControl = nullptr;
    348    mGenericTypes &= ~eSelect;
    349  }
    350 }
    351 
    352 uint64_t XULMenupopupAccessible::NativeState() const {
    353  uint64_t state = LocalAccessible::NativeState();
    354 
    355 #ifdef DEBUG
    356  // We are onscreen if our parent is active
    357  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
    358  bool isActive = menuPopupFrame ? menuPopupFrame->IsOpen() : false;
    359  if (!isActive) {
    360    LocalAccessible* parent = LocalParent();
    361    if (parent) {
    362      nsIContent* parentContent = parent->GetContent();
    363      if (parentContent && parentContent->IsElement())
    364        isActive = parentContent->AsElement()->HasAttr(nsGkAtoms::open);
    365    }
    366  }
    367 
    368  NS_ASSERTION(isActive || (state & states::INVISIBLE),
    369               "XULMenupopup doesn't have INVISIBLE when it's inactive");
    370 #endif
    371 
    372  if (state & states::INVISIBLE) {
    373    state |= states::OFFSCREEN;
    374  }
    375 
    376  return state;
    377 }
    378 
    379 ENameValueFlag XULMenupopupAccessible::NativeName(nsString& aName) const {
    380  nsIContent* content = mContent;
    381  while (content && aName.IsEmpty()) {
    382    if (content->IsElement()) {
    383      content->AsElement()->GetAttr(nsGkAtoms::label, aName);
    384    }
    385    content = content->GetFlattenedTreeParent();
    386  }
    387 
    388  return eNameOK;
    389 }
    390 
    391 role XULMenupopupAccessible::NativeRole() const {
    392  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
    393  if (menuPopupFrame && menuPopupFrame->IsContextMenu()) {
    394    return roles::MENUPOPUP;
    395  }
    396 
    397  if (mParent) {
    398    if (mParent->IsCombobox()) {
    399      return roles::COMBOBOX_LIST;
    400    }
    401  }
    402 
    403  // If accessible is not bound to the tree (this happens while children are
    404  // cached) return general role.
    405  return roles::MENUPOPUP;
    406 }
    407 
    408 ////////////////////////////////////////////////////////////////////////////////
    409 // XULMenupopupAccessible: Widgets
    410 
    411 bool XULMenupopupAccessible::IsWidget() const { return true; }
    412 
    413 bool XULMenupopupAccessible::IsActiveWidget() const {
    414  // If menupopup is a widget (the case of context menus) then active when open.
    415  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
    416  return menuPopupFrame && menuPopupFrame->IsOpen();
    417 }
    418 
    419 bool XULMenupopupAccessible::AreItemsOperable() const {
    420  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
    421  return menuPopupFrame && menuPopupFrame->IsOpen();
    422 }
    423 
    424 LocalAccessible* XULMenupopupAccessible::ContainerWidget() const {
    425  DocAccessible* document = Document();
    426 
    427  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
    428  MOZ_ASSERT(menuPopupFrame);
    429  if (!menuPopupFrame) {
    430    return nullptr;
    431  }
    432 
    433  auto* cur = dom::XULPopupElement::FromNode(GetContent());
    434  while (cur) {
    435    auto* menu = cur->GetContainingMenu();
    436    if (!menu) {
    437      // <panel> / <tooltip> / etc.
    438      return nullptr;
    439    }
    440    dom::XULMenuParentElement* parent = menu->GetMenuParent();
    441    if (!parent) {
    442      LocalAccessible* menuPopup = document->GetAccessible(cur);
    443      MOZ_ASSERT(menuPopup);
    444      return menuPopup ? menuPopup->LocalParent() : nullptr;
    445    }
    446 
    447    if (parent->IsMenuBar()) {
    448      return document->GetAccessible(parent);
    449    }
    450 
    451    cur = dom::XULPopupElement::FromNode(parent);
    452    MOZ_ASSERT(cur, "Should be a popup");
    453  }
    454 
    455  MOZ_ASSERT_UNREACHABLE("How did we get out of the loop without returning?");
    456  return nullptr;
    457 }
    458 
    459 ////////////////////////////////////////////////////////////////////////////////
    460 // XULMenubarAccessible
    461 ////////////////////////////////////////////////////////////////////////////////
    462 
    463 XULMenubarAccessible::XULMenubarAccessible(nsIContent* aContent,
    464                                           DocAccessible* aDoc)
    465    : AccessibleWrap(aContent, aDoc) {}
    466 
    467 ENameValueFlag XULMenubarAccessible::NativeName(nsString& aName) const {
    468  aName.AssignLiteral("Application");
    469  return eNameOK;
    470 }
    471 
    472 role XULMenubarAccessible::NativeRole() const { return roles::MENUBAR; }
    473 
    474 ////////////////////////////////////////////////////////////////////////////////
    475 // XULMenubarAccessible: Widgets
    476 
    477 bool XULMenubarAccessible::IsActiveWidget() const {
    478  auto* menuBar = dom::XULMenuBarElement::FromNode(GetContent());
    479  return menuBar && menuBar->IsActive();
    480 }
    481 
    482 bool XULMenubarAccessible::AreItemsOperable() const { return true; }
    483 
    484 LocalAccessible* XULMenubarAccessible::CurrentItem() const {
    485  auto* content = dom::XULMenuParentElement::FromNode(GetContent());
    486  MOZ_ASSERT(content);
    487  if (!content || !content->GetActiveMenuChild()) {
    488    return nullptr;
    489  }
    490  return mDoc->GetAccessible(content->GetActiveMenuChild());
    491 }
    492 
    493 void XULMenubarAccessible::SetCurrentItem(const LocalAccessible* aItem) {
    494  NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");
    495 }