tor-browser

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

AccessibleWrap.cpp (16128B)


      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 "AccessibleWrap.h"
      7 
      8 #include "JavaBuiltins.h"
      9 #include "LocalAccessible-inl.h"
     10 #include "HyperTextAccessible-inl.h"
     11 #include "AccAttributes.h"
     12 #include "AccEvent.h"
     13 #include "AndroidInputType.h"
     14 #include "DocAccessibleWrap.h"
     15 #include "SessionAccessibility.h"
     16 #include "TextLeafAccessible.h"
     17 #include "TraversalRule.h"
     18 #include "Pivot.h"
     19 #include "Platform.h"
     20 #include "nsAccessibilityService.h"
     21 #include "nsEventShell.h"
     22 #include "nsIAccessibleAnnouncementEvent.h"
     23 #include "nsIAccessiblePivot.h"
     24 #include "nsAccUtils.h"
     25 #include "nsTextEquivUtils.h"
     26 #include "nsWhitespaceTokenizer.h"
     27 #include "RootAccessible.h"
     28 #include "TextLeafRange.h"
     29 
     30 #include "mozilla/a11y/PDocAccessibleChild.h"
     31 #include "mozilla/jni/GeckoBundleUtils.h"
     32 #include "mozilla/a11y/DocAccessibleParent.h"
     33 #include "mozilla/Maybe.h"
     34 
     35 // icu TRUE conflicting with java::sdk::Boolean::TRUE()
     36 // https://searchfox.org/mozilla-central/rev/ce02064d8afc8673cef83c92896ee873bd35e7ae/intl/icu/source/common/unicode/umachine.h#265
     37 // https://searchfox.org/mozilla-central/source/__GENERATED__/widget/android/bindings/JavaBuiltins.h#78
     38 #ifdef TRUE
     39 #  undef TRUE
     40 #endif
     41 
     42 using namespace mozilla::a11y;
     43 using mozilla::Maybe;
     44 
     45 //-----------------------------------------------------
     46 // construction
     47 //-----------------------------------------------------
     48 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
     49    : LocalAccessible(aContent, aDoc), mID(SessionAccessibility::kUnsetID) {
     50  if (!IPCAccessibilityActive()) {
     51    MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
     52    SessionAccessibility::RegisterAccessible(this);
     53  }
     54 }
     55 
     56 //-----------------------------------------------------
     57 // destruction
     58 //-----------------------------------------------------
     59 AccessibleWrap::~AccessibleWrap() {}
     60 
     61 nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
     62  auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
     63  NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
     64 
     65  nsresult rv = LocalAccessible::HandleAccEvent(aEvent);
     66  NS_ENSURE_SUCCESS(rv, rv);
     67 
     68  accessible->HandleLiveRegionEvent(aEvent);
     69 
     70  return NS_OK;
     71 }
     72 
     73 void AccessibleWrap::Shutdown() {
     74  if (!IPCAccessibilityActive()) {
     75    MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
     76    SessionAccessibility::UnregisterAccessible(this);
     77  }
     78  LocalAccessible::Shutdown();
     79 }
     80 
     81 bool AccessibleWrap::DoAction(uint8_t aIndex) const {
     82  if (ActionCount()) {
     83    return LocalAccessible::DoAction(aIndex);
     84  }
     85 
     86  if (mContent) {
     87    // We still simulate a click on an accessible even if there is no
     88    // known actions. For the sake of bad markup.
     89    DoCommand();
     90    return true;
     91  }
     92 
     93  return false;
     94 }
     95 
     96 Accessible* AccessibleWrap::DoPivot(Accessible* aAccessible,
     97                                    int32_t aGranularity, bool aForward,
     98                                    bool aInclusive) {
     99  Accessible* pivotRoot = nullptr;
    100  if (aAccessible->IsRemote()) {
    101    // If this is a remote accessible provide the top level
    102    // remote doc as the pivot root for thread safety reasons.
    103    DocAccessibleParent* doc = aAccessible->AsRemote()->Document();
    104    while (doc && !doc->IsTopLevel()) {
    105      doc = doc->ParentDoc();
    106    }
    107    MOZ_ASSERT(doc, "Failed to get top level DocAccessibleParent");
    108    pivotRoot = doc;
    109  }
    110  a11y::Pivot pivot(pivotRoot);
    111  // Depending on the start accessible, the pivot rule will either traverse
    112  // local or remote accessibles exclusively.
    113  TraversalRule rule(aGranularity, aAccessible->IsLocal());
    114  Accessible* result = aForward ? pivot.Next(aAccessible, rule, aInclusive)
    115                                : pivot.Prev(aAccessible, rule, aInclusive);
    116 
    117  if (result && (result != aAccessible || aInclusive)) {
    118    return result;
    119  }
    120 
    121  return nullptr;
    122 }
    123 
    124 Accessible* AccessibleWrap::ExploreByTouch(Accessible* aAccessible, float aX,
    125                                           float aY) {
    126  Accessible* root;
    127  if (LocalAccessible* local = aAccessible->AsLocal()) {
    128    root = local->RootAccessible();
    129  } else {
    130    // If this is a RemoteAccessible, provide the top level
    131    // remote doc as the pivot root for thread safety reasons.
    132    DocAccessibleParent* doc = aAccessible->AsRemote()->Document();
    133    while (doc && !doc->IsTopLevel()) {
    134      doc = doc->ParentDoc();
    135    }
    136    MOZ_ASSERT(doc, "Failed to get top level DocAccessibleParent");
    137    root = doc;
    138  }
    139  a11y::Pivot pivot(root);
    140  TraversalRule rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT,
    141                     aAccessible->IsLocal());
    142  Accessible* result = pivot.AtPoint(aX, aY, rule);
    143  if (result == aAccessible) {
    144    return nullptr;
    145  }
    146  return result;
    147 }
    148 
    149 static TextLeafPoint ToTextLeafPoint(Accessible* aAccessible, int32_t aOffset) {
    150  if (HyperTextAccessibleBase* ht = aAccessible->AsHyperTextBase()) {
    151    return ht->ToTextLeafPoint(aOffset);
    152  }
    153 
    154  return TextLeafPoint(aAccessible, aOffset);
    155 }
    156 
    157 Maybe<std::pair<int32_t, int32_t>> AccessibleWrap::NavigateText(
    158    Accessible* aAccessible, int32_t aGranularity, int32_t aStartOffset,
    159    int32_t aEndOffset, bool aForward, bool aSelect) {
    160  int32_t startOffset = aStartOffset;
    161  int32_t endOffset = aEndOffset;
    162  if (startOffset == -1) {
    163    MOZ_ASSERT(endOffset == -1,
    164               "When start offset is unset, end offset should be too");
    165    startOffset = aForward ? 0 : nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT;
    166    endOffset = aForward ? 0 : nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT;
    167  }
    168 
    169  // If the accessible is an editable, set the virtual cursor position
    170  // to its caret offset. Otherwise use the document's virtual cursor
    171  // position as a starting offset.
    172  if (aAccessible->State() & states::EDITABLE) {
    173    startOffset = endOffset = aAccessible->AsHyperTextBase()->CaretOffset();
    174  }
    175 
    176  TextLeafRange currentRange =
    177      TextLeafRange(ToTextLeafPoint(aAccessible, startOffset),
    178                    ToTextLeafPoint(aAccessible, endOffset));
    179  uint16_t startBoundaryType = nsIAccessibleText::BOUNDARY_LINE_START;
    180  uint16_t endBoundaryType = nsIAccessibleText::BOUNDARY_LINE_END;
    181  switch (aGranularity) {
    182    case 1:  // MOVEMENT_GRANULARITY_CHARACTER
    183      startBoundaryType = nsIAccessibleText::BOUNDARY_CHAR;
    184      endBoundaryType = nsIAccessibleText::BOUNDARY_CHAR;
    185      break;
    186    case 2:  // MOVEMENT_GRANULARITY_WORD
    187      startBoundaryType = nsIAccessibleText::BOUNDARY_WORD_START;
    188      endBoundaryType = nsIAccessibleText::BOUNDARY_WORD_END;
    189      break;
    190    default:
    191      break;
    192  }
    193 
    194  TextLeafRange resultRange;
    195 
    196  if (aForward) {
    197    resultRange.SetEnd(
    198        currentRange.End().FindBoundary(endBoundaryType, eDirNext));
    199    resultRange.SetStart(
    200        resultRange.End().FindBoundary(startBoundaryType, eDirPrevious));
    201  } else {
    202    resultRange.SetStart(
    203        currentRange.Start().FindBoundary(startBoundaryType, eDirPrevious));
    204    resultRange.SetEnd(
    205        resultRange.Start().FindBoundary(endBoundaryType, eDirNext));
    206  }
    207 
    208  if (!resultRange.Crop(aAccessible)) {
    209    // If the new range does not intersect at all with the given
    210    // accessible/container this navigation has failed or reached an edge.
    211    return Nothing();
    212  }
    213 
    214  if (resultRange == currentRange || resultRange.Start() == resultRange.End()) {
    215    // If the result range equals the current range, or if the result range is
    216    // collapsed, we failed or reached an edge.
    217    return Nothing();
    218  }
    219 
    220  if (HyperTextAccessibleBase* ht = aAccessible->AsHyperTextBase()) {
    221    DebugOnly<bool> ok = false;
    222    std::tie(ok, startOffset) = ht->TransformOffset(
    223        resultRange.Start().mAcc, resultRange.Start().mOffset, false);
    224    MOZ_ASSERT(ok, "Accessible of range start should be in container.");
    225 
    226    std::tie(ok, endOffset) = ht->TransformOffset(
    227        resultRange.End().mAcc, resultRange.End().mOffset, false);
    228    MOZ_ASSERT(ok, "Accessible range end should be in container.");
    229  } else {
    230    startOffset = resultRange.Start().mOffset;
    231    endOffset = resultRange.End().mOffset;
    232  }
    233 
    234  return Some(std::make_pair(startOffset, endOffset));
    235 }
    236 
    237 uint32_t AccessibleWrap::GetFlags(Accessible* aAccessible) {
    238  uint32_t flags = 0;
    239  uint64_t state = aAccessible->State();
    240  role role = aAccessible->Role();
    241  if (aAccessible->IsScrollable()) {
    242    flags |= java::SessionAccessibility::FLAG_SCROLLABLE;
    243  }
    244 
    245  if (state & states::CHECKABLE) {
    246    flags |= java::SessionAccessibility::FLAG_CHECKABLE;
    247  }
    248 
    249  if (state & states::CHECKED) {
    250    flags |= java::SessionAccessibility::FLAG_CHECKED;
    251  }
    252 
    253  if (state & states::INVALID) {
    254    flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
    255  }
    256 
    257  if (state & states::EDITABLE) {
    258    flags |= java::SessionAccessibility::FLAG_EDITABLE;
    259  }
    260 
    261  if (aAccessible->ActionCount() && role != roles::TEXT_LEAF) {
    262    flags |= java::SessionAccessibility::FLAG_CLICKABLE;
    263  }
    264 
    265  if (state & states::ENABLED) {
    266    flags |= java::SessionAccessibility::FLAG_ENABLED;
    267  }
    268 
    269  if (state & states::FOCUSABLE) {
    270    flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
    271  }
    272 
    273  if (state & states::FOCUSED) {
    274    flags |= java::SessionAccessibility::FLAG_FOCUSED;
    275  }
    276 
    277  if (state & states::MULTI_LINE) {
    278    flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
    279  }
    280 
    281  if (state & states::SELECTABLE) {
    282    flags |= java::SessionAccessibility::FLAG_SELECTABLE;
    283  }
    284 
    285  if (state & states::SELECTED) {
    286    flags |= java::SessionAccessibility::FLAG_SELECTED;
    287  }
    288 
    289  if (state & states::EXPANDABLE) {
    290    flags |= java::SessionAccessibility::FLAG_EXPANDABLE;
    291  }
    292 
    293  if (state & states::EXPANDED) {
    294    flags |= java::SessionAccessibility::FLAG_EXPANDED;
    295  }
    296 
    297  if ((state & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
    298    flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
    299  }
    300 
    301  if (role == roles::PASSWORD_TEXT) {
    302    flags |= java::SessionAccessibility::FLAG_PASSWORD;
    303  }
    304 
    305  return flags;
    306 }
    307 
    308 void AccessibleWrap::GetRoleDescription(role aRole, AccAttributes* aAttributes,
    309                                        nsAString& aGeckoRole,
    310                                        nsAString& aRoleDescription) {
    311  if (aRole == roles::HEADING && aAttributes) {
    312    // The heading level is an attribute, so we need that.
    313    nsAutoString headingLevel;
    314    if (aAttributes->GetAttribute(nsGkAtoms::level, headingLevel)) {
    315      nsAutoString token(u"heading-");
    316      token.Append(headingLevel);
    317      if (LocalizeString(token, aRoleDescription)) {
    318        return;
    319      }
    320    }
    321  }
    322 
    323  if ((aRole == roles::LANDMARK || aRole == roles::REGION) && aAttributes) {
    324    nsAutoString xmlRoles;
    325    if (aAttributes->GetAttribute(nsGkAtoms::xmlroles, xmlRoles)) {
    326      nsWhitespaceTokenizer tokenizer(xmlRoles);
    327      while (tokenizer.hasMoreTokens()) {
    328        if (LocalizeString(tokenizer.nextToken(), aRoleDescription)) {
    329          return;
    330        }
    331      }
    332    }
    333  }
    334 
    335  GetAccService()->GetStringRole(aRole, aGeckoRole);
    336  LocalizeString(aGeckoRole, aRoleDescription);
    337 }
    338 
    339 int32_t AccessibleWrap::AndroidClass(Accessible* aAccessible) {
    340  return GetVirtualViewID(aAccessible) == SessionAccessibility::kNoID
    341             ? java::SessionAccessibility::CLASSNAME_WEBVIEW
    342             : GetAndroidClass(aAccessible->Role());
    343 }
    344 
    345 int32_t AccessibleWrap::GetVirtualViewID(Accessible* aAccessible) {
    346  if (aAccessible->IsLocal()) {
    347    return static_cast<AccessibleWrap*>(aAccessible)->mID;
    348  }
    349 
    350  return static_cast<int32_t>(aAccessible->AsRemote()->GetWrapper());
    351 }
    352 
    353 void AccessibleWrap::SetVirtualViewID(Accessible* aAccessible,
    354                                      int32_t aVirtualViewID) {
    355  if (aAccessible->IsLocal()) {
    356    static_cast<AccessibleWrap*>(aAccessible)->mID = aVirtualViewID;
    357  } else {
    358    aAccessible->AsRemote()->SetWrapper(static_cast<uintptr_t>(aVirtualViewID));
    359  }
    360 }
    361 
    362 int32_t AccessibleWrap::GetAndroidClass(role aRole) {
    363 #define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
    364             msaaRole, ia2Role, androidClass, iosIsElement, uiaControlType, \
    365             nameRule)                                                      \
    366  case roles::geckoRole:                                                    \
    367    return androidClass;
    368 
    369  switch (aRole) {
    370 #include "RoleMap.h"
    371    default:
    372      return java::SessionAccessibility::CLASSNAME_VIEW;
    373  }
    374 
    375 #undef ROLE
    376 }
    377 
    378 int32_t AccessibleWrap::GetInputType(const nsString& aInputTypeAttr) {
    379  if (aInputTypeAttr.EqualsIgnoreCase("email")) {
    380    return java::sdk::InputType::TYPE_CLASS_TEXT |
    381           java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
    382  }
    383 
    384  if (aInputTypeAttr.EqualsIgnoreCase("number")) {
    385    return java::sdk::InputType::TYPE_CLASS_NUMBER;
    386  }
    387 
    388  if (aInputTypeAttr.EqualsIgnoreCase("password")) {
    389    return java::sdk::InputType::TYPE_CLASS_TEXT |
    390           java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD;
    391  }
    392 
    393  if (aInputTypeAttr.EqualsIgnoreCase("tel")) {
    394    return java::sdk::InputType::TYPE_CLASS_PHONE;
    395  }
    396 
    397  if (aInputTypeAttr.EqualsIgnoreCase("text")) {
    398    return java::sdk::InputType::TYPE_CLASS_TEXT |
    399           java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
    400  }
    401 
    402  if (aInputTypeAttr.EqualsIgnoreCase("url")) {
    403    return java::sdk::InputType::TYPE_CLASS_TEXT |
    404           java::sdk::InputType::TYPE_TEXT_VARIATION_URI;
    405  }
    406 
    407  return 0;
    408 }
    409 
    410 void AccessibleWrap::GetTextEquiv(nsString& aText) {
    411  // 1. Start with the name, since it might have been explicitly specified.
    412  if (Name(aText) != eNameFromSubtree) {
    413    // 2. If the name didn't come from the subtree, add the text from the
    414    // subtree.
    415    if (aText.IsEmpty()) {
    416      nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
    417    } else {
    418      nsAutoString subtree;
    419      nsTextEquivUtils::GetTextEquivFromSubtree(this, subtree);
    420      if (!subtree.IsEmpty()) {
    421        aText.Append(' ');
    422        aText.Append(subtree);
    423      }
    424    }
    425  }
    426 }
    427 
    428 bool AccessibleWrap::HandleLiveRegionEvent(AccEvent* aEvent) {
    429  auto eventType = aEvent->GetEventType();
    430  if (eventType != nsIAccessibleEvent::EVENT_TEXT_INSERTED &&
    431      eventType != nsIAccessibleEvent::EVENT_NAME_CHANGE) {
    432    // XXX: Right now only announce text inserted events. aria-relevant=removals
    433    // is potentially on the chopping block[1]. We also don't support editable
    434    // text because we currently can't descern the source of the change[2].
    435    // 1. https://github.com/w3c/aria/issues/712
    436    // 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189
    437    return false;
    438  }
    439 
    440  if (aEvent->IsFromUserInput()) {
    441    return false;
    442  }
    443 
    444  RefPtr<AccAttributes> attributes = new AccAttributes();
    445  nsAccUtils::SetLiveContainerAttributes(attributes, this);
    446  nsString live;
    447  if (!attributes->GetAttribute(nsGkAtoms::containerLive, live)) {
    448    return false;
    449  }
    450 
    451  uint16_t priority = live.EqualsIgnoreCase("assertive")
    452                          ? nsIAccessibleAnnouncementEvent::ASSERTIVE
    453                          : nsIAccessibleAnnouncementEvent::POLITE;
    454 
    455  Maybe<bool> atomic =
    456      attributes->GetAttribute<bool>(nsGkAtoms::containerAtomic);
    457  LocalAccessible* announcementTarget = this;
    458  nsAutoString announcement;
    459  if (atomic && *atomic) {
    460    LocalAccessible* atomicAncestor = nullptr;
    461    for (LocalAccessible* parent = announcementTarget; parent;
    462         parent = parent->LocalParent()) {
    463      dom::Element* element = parent->Elm();
    464      if (element &&
    465          nsAccUtils::ARIAAttrValueIs(element, nsGkAtoms::aria_atomic,
    466                                      nsGkAtoms::_true, eCaseMatters)) {
    467        atomicAncestor = parent;
    468        break;
    469      }
    470    }
    471 
    472    if (atomicAncestor) {
    473      announcementTarget = atomicAncestor;
    474      static_cast<AccessibleWrap*>(atomicAncestor)->GetTextEquiv(announcement);
    475    }
    476  } else {
    477    GetTextEquiv(announcement);
    478  }
    479 
    480  announcement.CompressWhitespace();
    481  if (announcement.IsEmpty()) {
    482    return false;
    483  }
    484 
    485  announcementTarget->Announce(announcement, priority);
    486  return true;
    487 }