tor-browser

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

HTMLSelectAccessible.cpp (16190B)


      1 /* -*- Mode: C++; tab-width: 4; 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 "HTMLSelectAccessible.h"
      7 
      8 #include "LocalAccessible-inl.h"
      9 #include "DocAccessible-inl.h"
     10 #include "nsAccessibilityService.h"
     11 #include "nsAccUtils.h"
     12 #include "DocAccessible.h"
     13 #include "mozilla/a11y/Role.h"
     14 #include "States.h"
     15 
     16 #include "nsCOMPtr.h"
     17 #include "mozilla/dom/HTMLOptionElement.h"
     18 #include "mozilla/dom/HTMLOptGroupElement.h"
     19 #include "mozilla/dom/HTMLSelectElement.h"
     20 #include "nsComboboxControlFrame.h"
     21 #include "nsContainerFrame.h"
     22 #include "nsListControlFrame.h"
     23 
     24 using namespace mozilla::a11y;
     25 using namespace mozilla::dom;
     26 
     27 ////////////////////////////////////////////////////////////////////////////////
     28 // HTMLSelectListAccessible
     29 ////////////////////////////////////////////////////////////////////////////////
     30 
     31 HTMLSelectListAccessible::HTMLSelectListAccessible(nsIContent* aContent,
     32                                                   DocAccessible* aDoc)
     33    : AccessibleWrap(aContent, aDoc) {
     34  mGenericTypes |= eListControl | eSelect;
     35 }
     36 
     37 ////////////////////////////////////////////////////////////////////////////////
     38 // HTMLSelectListAccessible: LocalAccessible public
     39 
     40 uint64_t HTMLSelectListAccessible::NativeState() const {
     41  uint64_t state = AccessibleWrap::NativeState();
     42  if (mContent->AsElement()->HasAttr(nsGkAtoms::multiple)) {
     43    state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
     44  }
     45 
     46  return state;
     47 }
     48 
     49 role HTMLSelectListAccessible::NativeRole() const { return roles::LISTBOX; }
     50 
     51 ////////////////////////////////////////////////////////////////////////////////
     52 // HTMLSelectListAccessible: SelectAccessible
     53 
     54 bool HTMLSelectListAccessible::SelectAll() {
     55  return mContent->AsElement()->HasAttr(nsGkAtoms::multiple)
     56             ? AccessibleWrap::SelectAll()
     57             : false;
     58 }
     59 
     60 bool HTMLSelectListAccessible::UnselectAll() {
     61  return mContent->AsElement()->HasAttr(nsGkAtoms::multiple)
     62             ? AccessibleWrap::UnselectAll()
     63             : false;
     64 }
     65 
     66 ////////////////////////////////////////////////////////////////////////////////
     67 // HTMLSelectListAccessible: Widgets
     68 
     69 bool HTMLSelectListAccessible::IsWidget() const { return true; }
     70 
     71 bool HTMLSelectListAccessible::IsActiveWidget() const {
     72  return FocusMgr()->HasDOMFocus(mContent);
     73 }
     74 
     75 bool HTMLSelectListAccessible::AreItemsOperable() const { return true; }
     76 
     77 LocalAccessible* HTMLSelectListAccessible::CurrentItem() const {
     78  nsListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
     79  if (listControlFrame) {
     80    nsCOMPtr<nsIContent> activeOptionNode =
     81        listControlFrame->GetCurrentOption();
     82    if (activeOptionNode) {
     83      DocAccessible* document = Document();
     84      if (document) return document->GetAccessible(activeOptionNode);
     85    }
     86  }
     87  return nullptr;
     88 }
     89 
     90 void HTMLSelectListAccessible::SetCurrentItem(const LocalAccessible* aItem) {
     91  if (!aItem->GetContent()->IsElement()) return;
     92 
     93  aItem->GetContent()->AsElement()->SetAttr(
     94      kNameSpaceID_None, nsGkAtoms::selected, u"true"_ns, true);
     95 }
     96 
     97 bool HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const {
     98  return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
     99 }
    100 
    101 bool HTMLSelectListAccessible::AttributeChangesState(nsAtom* aAttribute) {
    102  return aAttribute == nsGkAtoms::multiple ||
    103         LocalAccessible::AttributeChangesState(aAttribute);
    104 }
    105 
    106 ////////////////////////////////////////////////////////////////////////////////
    107 // HTMLSelectOptionAccessible
    108 ////////////////////////////////////////////////////////////////////////////////
    109 
    110 HTMLSelectOptionAccessible::HTMLSelectOptionAccessible(nsIContent* aContent,
    111                                                       DocAccessible* aDoc)
    112    : HyperTextAccessible(aContent, aDoc) {}
    113 
    114 ////////////////////////////////////////////////////////////////////////////////
    115 // HTMLSelectOptionAccessible: LocalAccessible public
    116 
    117 role HTMLSelectOptionAccessible::NativeRole() const {
    118  if (GetCombobox()) return roles::COMBOBOX_OPTION;
    119 
    120  return roles::OPTION;
    121 }
    122 
    123 ENameValueFlag HTMLSelectOptionAccessible::NativeName(nsString& aName) const {
    124  if (auto* option = dom::HTMLOptionElement::FromNode(mContent)) {
    125    option->GetAttr(nsGkAtoms::label, aName);
    126    if (!aName.IsEmpty()) {
    127      return eNameOK;
    128    }
    129    option->GetText(aName);
    130    return eNameFromSubtree;
    131  }
    132  if (auto* group = dom::HTMLOptGroupElement::FromNode(mContent)) {
    133    group->GetLabel(aName);
    134    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
    135  }
    136  MOZ_ASSERT_UNREACHABLE("What content do we have?");
    137  return eNameFromSubtree;
    138 }
    139 
    140 void HTMLSelectOptionAccessible::DOMAttributeChanged(
    141    int32_t aNameSpaceID, nsAtom* aAttribute, AttrModType aModType,
    142    const nsAttrValue* aOldValue, uint64_t aOldState) {
    143  HyperTextAccessible::DOMAttributeChanged(aNameSpaceID, aAttribute, aModType,
    144                                           aOldValue, aOldState);
    145 
    146  if (aAttribute == nsGkAtoms::label) {
    147    dom::Element* elm = Elm();
    148    if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_labelledby) &&
    149        !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label)) {
    150      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
    151    }
    152  }
    153 }
    154 
    155 uint64_t HTMLSelectOptionAccessible::NativeState() const {
    156  // As a HTMLSelectOptionAccessible we can have the following states:
    157  // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
    158  // Upcall to LocalAccessible, but skip HyperTextAccessible impl
    159  // because we don't want EDITABLE or SELECTABLE_TEXT
    160  uint64_t state = LocalAccessible::NativeState();
    161 
    162  LocalAccessible* select = GetSelect();
    163  if (!select) return state;
    164 
    165  uint64_t selectState = select->State();
    166  if (selectState & states::INVISIBLE) return state;
    167 
    168  // Are we selected?
    169  HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
    170  bool selected = option && option->Selected();
    171  if (selected) state |= states::SELECTED;
    172 
    173  if (selectState & states::OFFSCREEN) {
    174    state |= states::OFFSCREEN;
    175  } else if (selectState & states::COLLAPSED) {
    176    // <select> is COLLAPSED: add OFFSCREEN, if not the currently
    177    // visible option
    178    if (!selected) {
    179      state |= states::OFFSCREEN;
    180      // Ensure the invisible state is removed. Otherwise, group info will skip
    181      // this option. Furthermore, this gets cached and this doesn't get
    182      // invalidated even once the select is expanded.
    183      state &= ~states::INVISIBLE;
    184    } else {
    185      // Clear offscreen and invisible for currently showing option
    186      state &= ~(states::OFFSCREEN | states::INVISIBLE);
    187      state |= selectState & states::OPAQUE1;
    188    }
    189  } else {
    190    // XXX list frames are weird, don't rely on LocalAccessible's general
    191    // visibility implementation unless they get reimplemented in layout
    192    state &= ~states::OFFSCREEN;
    193    // <select> is not collapsed: compare bounds to calculate OFFSCREEN
    194    LocalAccessible* listAcc = LocalParent();
    195    if (listAcc) {
    196      LayoutDeviceIntRect optionRect = Bounds();
    197      LayoutDeviceIntRect listRect = listAcc->Bounds();
    198      if (optionRect.Y() < listRect.Y() ||
    199          optionRect.YMost() > listRect.YMost()) {
    200        state |= states::OFFSCREEN;
    201      }
    202    }
    203  }
    204 
    205  return state;
    206 }
    207 
    208 uint64_t HTMLSelectOptionAccessible::NativeInteractiveState() const {
    209  return NativelyUnavailable() ? states::UNAVAILABLE
    210                               : states::FOCUSABLE | states::SELECTABLE;
    211 }
    212 
    213 nsRect HTMLSelectOptionAccessible::RelativeBounds(
    214    nsIFrame** aBoundingFrame) const {
    215  LocalAccessible* combobox = GetCombobox();
    216  if (combobox && (combobox->State() & states::COLLAPSED)) {
    217    return combobox->RelativeBounds(aBoundingFrame);
    218  }
    219 
    220  return HyperTextAccessible::RelativeBounds(aBoundingFrame);
    221 }
    222 
    223 void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex,
    224                                              nsAString& aName) {
    225  if (aIndex == eAction_Select) aName.AssignLiteral("select");
    226 }
    227 
    228 bool HTMLSelectOptionAccessible::HasPrimaryAction() const { return true; }
    229 
    230 void HTMLSelectOptionAccessible::SetSelected(bool aSelect) {
    231  HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
    232  if (option) option->SetSelected(aSelect);
    233 }
    234 
    235 ////////////////////////////////////////////////////////////////////////////////
    236 // HTMLSelectOptionAccessible: Widgets
    237 
    238 LocalAccessible* HTMLSelectOptionAccessible::ContainerWidget() const {
    239  LocalAccessible* parent = LocalParent();
    240  if (parent && parent->IsHTMLOptGroup()) {
    241    parent = parent->LocalParent();
    242  }
    243 
    244  return parent && parent->IsListControl() ? parent : nullptr;
    245 }
    246 
    247 ////////////////////////////////////////////////////////////////////////////////
    248 // HTMLSelectOptGroupAccessible
    249 ////////////////////////////////////////////////////////////////////////////////
    250 
    251 role HTMLSelectOptGroupAccessible::NativeRole() const {
    252  return roles::GROUPING;
    253 }
    254 
    255 uint64_t HTMLSelectOptGroupAccessible::NativeInteractiveState() const {
    256  return NativelyUnavailable() ? states::UNAVAILABLE : 0;
    257 }
    258 
    259 bool HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent* aEl) const {
    260  return aEl->IsCharacterData() || aEl->IsHTMLElement(nsGkAtoms::option);
    261 }
    262 
    263 bool HTMLSelectOptGroupAccessible::HasPrimaryAction() const { return false; }
    264 
    265 ////////////////////////////////////////////////////////////////////////////////
    266 // HTMLComboboxAccessible
    267 ////////////////////////////////////////////////////////////////////////////////
    268 
    269 HTMLComboboxAccessible::HTMLComboboxAccessible(nsIContent* aContent,
    270                                               DocAccessible* aDoc)
    271    : AccessibleWrap(aContent, aDoc) {
    272  mType = eHTMLComboboxType;
    273  mGenericTypes |= eCombobox;
    274  mStateFlags |= eNoKidsFromDOM;
    275 
    276  if ((nsComboboxControlFrame*)do_QueryFrame(GetFrame())) {
    277    mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
    278    Document()->BindToDocument(mListAccessible, nullptr);
    279    AppendChild(mListAccessible);
    280  }
    281 }
    282 
    283 ////////////////////////////////////////////////////////////////////////////////
    284 // HTMLComboboxAccessible: LocalAccessible
    285 
    286 role HTMLComboboxAccessible::NativeRole() const { return roles::COMBOBOX; }
    287 
    288 bool HTMLComboboxAccessible::RemoveChild(LocalAccessible* aChild) {
    289  MOZ_ASSERT(aChild == mListAccessible);
    290  if (AccessibleWrap::RemoveChild(aChild)) {
    291    mListAccessible = nullptr;
    292    return true;
    293  }
    294  return false;
    295 }
    296 
    297 void HTMLComboboxAccessible::Shutdown() {
    298  MOZ_ASSERT(!mDoc || mDoc->IsDefunct() || !mListAccessible);
    299  if (mListAccessible) {
    300    mListAccessible->Shutdown();
    301    mListAccessible = nullptr;
    302  }
    303 
    304  AccessibleWrap::Shutdown();
    305 }
    306 
    307 uint64_t HTMLComboboxAccessible::NativeState() const {
    308  // As a HTMLComboboxAccessible we can have the following states:
    309  // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
    310  // Get focus status from base class
    311  uint64_t state = LocalAccessible::NativeState();
    312 
    313  nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
    314  if (comboFrame && comboFrame->IsDroppedDown()) {
    315    state |= states::EXPANDED;
    316  }
    317 
    318  state |= states::HASPOPUP | states::EXPANDABLE;
    319  return state;
    320 }
    321 
    322 EDescriptionValueFlag HTMLComboboxAccessible::Description(
    323    nsString& aDescription) const {
    324  aDescription.Truncate();
    325  // First check to see if combo box itself has a description, perhaps through
    326  // tooltip (title attribute) or via aria-describedby
    327  EDescriptionValueFlag descFlag = LocalAccessible::Description(aDescription);
    328  if (!aDescription.IsEmpty()) {
    329    return descFlag;
    330  }
    331 
    332  // Otherwise use description of selected option.
    333  LocalAccessible* option = SelectedOption();
    334  if (option) {
    335    return option->Description(aDescription);
    336  }
    337 
    338  return eDescriptionOK;
    339 }
    340 
    341 void HTMLComboboxAccessible::Value(nsString& aValue) const {
    342  // Use accessible name of selected option.
    343  LocalAccessible* option = SelectedOption();
    344  if (option) option->Name(aValue);
    345 }
    346 
    347 bool HTMLComboboxAccessible::HasPrimaryAction() const { return true; }
    348 
    349 void HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
    350  if (aIndex != HTMLComboboxAccessible::eAction_Click) return;
    351 
    352  nsComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
    353  if (!comboFrame) return;
    354 
    355  if (comboFrame->IsDroppedDown()) {
    356    aName.AssignLiteral("close");
    357  } else {
    358    aName.AssignLiteral("open");
    359  }
    360 }
    361 
    362 bool HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const {
    363  return false;
    364 }
    365 
    366 ////////////////////////////////////////////////////////////////////////////////
    367 // HTMLComboboxAccessible: Widgets
    368 
    369 bool HTMLComboboxAccessible::IsWidget() const { return true; }
    370 
    371 bool HTMLComboboxAccessible::IsActiveWidget() const {
    372  return FocusMgr()->HasDOMFocus(mContent);
    373 }
    374 
    375 bool HTMLComboboxAccessible::AreItemsOperable() const {
    376  nsComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
    377  return comboboxFrame && comboboxFrame->IsDroppedDown();
    378 }
    379 
    380 LocalAccessible* HTMLComboboxAccessible::CurrentItem() const {
    381  return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
    382 }
    383 
    384 void HTMLComboboxAccessible::SetCurrentItem(const LocalAccessible* aItem) {
    385  if (AreItemsOperable()) mListAccessible->SetCurrentItem(aItem);
    386 }
    387 
    388 ////////////////////////////////////////////////////////////////////////////////
    389 // HTMLComboboxAccessible: protected
    390 
    391 LocalAccessible* HTMLComboboxAccessible::SelectedOption() const {
    392  HTMLSelectElement* select = HTMLSelectElement::FromNode(mContent);
    393  int32_t selectedIndex = select->SelectedIndex();
    394 
    395  if (selectedIndex >= 0) {
    396    HTMLOptionElement* option = select->Item(selectedIndex);
    397    if (option) {
    398      DocAccessible* document = Document();
    399      if (document) return document->GetAccessible(option);
    400    }
    401  }
    402 
    403  return nullptr;
    404 }
    405 
    406 ////////////////////////////////////////////////////////////////////////////////
    407 // HTMLComboboxListAccessible
    408 ////////////////////////////////////////////////////////////////////////////////
    409 
    410 HTMLComboboxListAccessible::HTMLComboboxListAccessible(LocalAccessible* aParent,
    411                                                       nsIContent* aContent,
    412                                                       DocAccessible* aDoc)
    413    : HTMLSelectListAccessible(aContent, aDoc) {
    414  mStateFlags |= eSharedNode;
    415 }
    416 
    417 ////////////////////////////////////////////////////////////////////////////////
    418 // HTMLComboboxAccessible: LocalAccessible
    419 
    420 role HTMLComboboxListAccessible::NativeRole() const {
    421  return roles::COMBOBOX_LIST;
    422 }
    423 
    424 uint64_t HTMLComboboxListAccessible::NativeState() const {
    425  // As a HTMLComboboxListAccessible we can have the following states:
    426  // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
    427  // Get focus status from base class
    428  uint64_t state = LocalAccessible::NativeState();
    429 
    430  nsComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
    431  if (comboFrame && comboFrame->IsDroppedDown()) {
    432    state |= states::FLOATING;
    433  } else {
    434    state |= states::INVISIBLE;
    435  }
    436 
    437  return state;
    438 }
    439 
    440 nsRect HTMLComboboxListAccessible::RelativeBounds(
    441    nsIFrame** aBoundingFrame) const {
    442  *aBoundingFrame = nullptr;
    443 
    444  LocalAccessible* comboAcc = LocalParent();
    445  if (!comboAcc) return nsRect();
    446 
    447  if (0 == (comboAcc->State() & states::COLLAPSED)) {
    448    return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
    449  }
    450 
    451  // Get the first option.
    452  nsIContent* content = mContent->GetFirstChild();
    453  if (!content) return nsRect();
    454 
    455  nsIFrame* frame = content->GetPrimaryFrame();
    456  if (!frame) {
    457    *aBoundingFrame = nullptr;
    458    return nsRect();
    459  }
    460 
    461  *aBoundingFrame = frame->GetParent();
    462  return (*aBoundingFrame)->GetRect();
    463 }
    464 
    465 bool HTMLComboboxListAccessible::IsAcceptableChild(nsIContent* aEl) const {
    466  return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
    467 }
    468 
    469 ////////////////////////////////////////////////////////////////////////////////
    470 // HTMLComboboxListAccessible: Widgets
    471 
    472 bool HTMLComboboxListAccessible::IsActiveWidget() const {
    473  return mParent && mParent->IsActiveWidget();
    474 }
    475 
    476 bool HTMLComboboxListAccessible::AreItemsOperable() const {
    477  return mParent && mParent->AreItemsOperable();
    478 }