tor-browser

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

SessionAccessibility.cpp (33036B)


      1 /* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
      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 "SessionAccessibility.h"
      7 #include "LocalAccessible-inl.h"
      8 #include "AndroidUiThread.h"
      9 #include "AndroidBridge.h"
     10 #include "DocAccessibleParent.h"
     11 #include "IDSet.h"
     12 #include "nsThreadUtils.h"
     13 #include "AccAttributes.h"
     14 #include "AccessibilityEvent.h"
     15 #include "DocAccessibleWrap.h"
     16 #include "JavaBuiltins.h"
     17 #include "nsAccessibilityService.h"
     18 #include "nsAccUtils.h"
     19 
     20 #include "mozilla/PresShell.h"
     21 #include "mozilla/dom/BrowserParent.h"
     22 #include "mozilla/dom/CanonicalBrowsingContext.h"
     23 #include "mozilla/dom/Document.h"
     24 #include "mozilla/dom/DocumentInlines.h"
     25 #include "mozilla/a11y/Accessible.h"
     26 #include "mozilla/a11y/DocAccessibleParent.h"
     27 #include "mozilla/a11y/DocManager.h"
     28 #include "mozilla/a11y/HyperTextAccessibleBase.h"
     29 #include "mozilla/jni/GeckoBundleUtils.h"
     30 #include "mozilla/jni/NativesInlines.h"
     31 #include "mozilla/widget/GeckoViewSupport.h"
     32 #include "mozilla/MouseEvents.h"
     33 #include "mozilla/dom/MouseEventBinding.h"
     34 
     35 #ifdef DEBUG
     36 #  include <android/log.h>
     37 #  define AALOG(args...) \
     38    __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
     39 #else
     40 #  define AALOG(args...) \
     41    do {                 \
     42    } while (0)
     43 #endif
     44 
     45 using namespace mozilla::a11y;
     46 
     47 // IDs should be a positive 32bit integer.
     48 IDSet sIDSet(31UL);
     49 
     50 class Settings final
     51    : public mozilla::java::SessionAccessibility::Settings::Natives<Settings> {
     52 public:
     53  static void ToggleNativeAccessibility(bool aEnable) {
     54    if (aEnable) {
     55      GetOrCreateAccService();
     56    } else {
     57      MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
     58    }
     59  }
     60 };
     61 
     62 SessionAccessibility::SessionAccessibility(
     63    jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow,
     64    java::SessionAccessibility::NativeProvider::Param aSessionAccessibility)
     65    : mWindow(aWindow), mSessionAccessibility(aSessionAccessibility) {}
     66 
     67 void SessionAccessibility::SetAttached(bool aAttached,
     68                                       already_AddRefed<Runnable> aRunnable) {
     69  if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
     70    uiThread->Dispatch(NS_NewRunnableFunction(
     71        "SessionAccessibility::Attach",
     72        [aAttached,
     73         sa = java::SessionAccessibility::NativeProvider::GlobalRef(
     74             mSessionAccessibility),
     75         runnable = RefPtr<Runnable>(aRunnable)] {
     76          sa->SetAttached(aAttached);
     77          if (runnable) {
     78            runnable->Run();
     79          }
     80        }));
     81  }
     82 }
     83 
     84 void SessionAccessibility::Init() {
     85  java::SessionAccessibility::NativeProvider::Natives<
     86      SessionAccessibility>::Init();
     87  Settings::Init();
     88 }
     89 
     90 void SessionAccessibility::GetNodeInfo(int32_t aID,
     91                                       mozilla::jni::Object::Param aNodeInfo) {
     92  MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
     93  ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
     94  java::GeckoBundle::GlobalRef ret = nullptr;
     95  RefPtr<SessionAccessibility> self(this);
     96  if (Accessible* acc = GetAccessibleByID(aID)) {
     97    if (acc->IsLocal()) {
     98      mal.Unlock();
     99      nsAppShell::SyncRunEvent(
    100          [this, self, aID, aNodeInfo = jni::Object::GlobalRef(aNodeInfo)] {
    101            if (Accessible* acc = GetAccessibleByID(aID)) {
    102              PopulateNodeInfo(acc, aNodeInfo);
    103            } else {
    104              AALOG("oops, nothing for %d", aID);
    105            }
    106          });
    107    } else {
    108      PopulateNodeInfo(acc, aNodeInfo);
    109    }
    110  } else {
    111    AALOG("oops, nothing for %d", aID);
    112  }
    113 }
    114 
    115 int SessionAccessibility::GetNodeClassName(int32_t aID) {
    116  MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
    117  ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    118  int32_t classNameEnum = java::SessionAccessibility::CLASSNAME_VIEW;
    119  RefPtr<SessionAccessibility> self(this);
    120  if (Accessible* acc = GetAccessibleByID(aID)) {
    121    if (acc->IsLocal()) {
    122      mal.Unlock();
    123      nsAppShell::SyncRunEvent([this, self, aID, &classNameEnum] {
    124        if (Accessible* acc = GetAccessibleByID(aID)) {
    125          classNameEnum = AccessibleWrap::AndroidClass(acc);
    126        }
    127      });
    128    } else {
    129      classNameEnum = AccessibleWrap::AndroidClass(acc);
    130    }
    131  }
    132 
    133  return classNameEnum;
    134 }
    135 
    136 void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) {
    137  if (Accessible* acc = GetAccessibleByID(aID)) {
    138    if (acc->IsRemote()) {
    139      acc->AsRemote()->ReplaceText(PromiseFlatString(aText->ToString()));
    140    } else if (acc->AsLocal()->IsHyperText()) {
    141      acc->AsLocal()->AsHyperText()->ReplaceText(aText->ToString());
    142    }
    143  }
    144 }
    145 
    146 void SessionAccessibility::Click(int32_t aID) {
    147  MOZ_ASSERT(NS_IsMainThread());
    148  MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    149  if (Accessible* acc = GetAccessibleByID(aID)) {
    150    acc->DoAction(0);
    151  }
    152 }
    153 
    154 bool SessionAccessibility::Pivot(int32_t aID, int32_t aGranularity,
    155                                 bool aForward, bool aInclusive) {
    156  MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
    157  MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    158  RefPtr<SessionAccessibility> self(this);
    159  if (Accessible* acc = GetAccessibleByID(aID)) {
    160    if (acc->IsLocal()) {
    161      nsAppShell::PostEvent(
    162          [this, self, aID, aGranularity, aForward, aInclusive] {
    163            MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    164            if (Accessible* _acc = GetAccessibleByID(aID)) {
    165              MOZ_ASSERT(_acc->IsLocal());
    166              if (Accessible* result = AccessibleWrap::DoPivot(
    167                      _acc, aGranularity, aForward, aInclusive)) {
    168                SendAccessibilityFocusedEvent(result, true);
    169              }
    170            }
    171          });
    172      return true;
    173    }
    174    Accessible* result =
    175        AccessibleWrap::DoPivot(acc, aGranularity, aForward, aInclusive);
    176    if (result) {
    177      int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(result);
    178      nsAppShell::PostEvent([this, self, virtualViewID] {
    179        MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    180        if (Accessible* acc = GetAccessibleByID(virtualViewID)) {
    181          SendAccessibilityFocusedEvent(acc, true);
    182        }
    183      });
    184      return true;
    185    }
    186  }
    187 
    188  return false;
    189 }
    190 
    191 void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) {
    192  MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
    193  MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    194  RefPtr<SessionAccessibility> self(this);
    195  if (Accessible* origin = GetAccessibleByID(aID)) {
    196    if (origin->IsLocal()) {
    197      nsAppShell::PostEvent([this, self, aID, aX, aY] {
    198        MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    199        if (Accessible* origin = GetAccessibleByID(aID)) {
    200          if (Accessible* result =
    201                  AccessibleWrap::ExploreByTouch(origin, aX, aY)) {
    202            SendHoverEnterEvent(result);
    203          }
    204        }
    205      });
    206    } else {
    207      if (Accessible* result = AccessibleWrap::ExploreByTouch(origin, aX, aY)) {
    208        int32_t resultID = AccessibleWrap::GetVirtualViewID(result);
    209        nsAppShell::PostEvent([this, self, resultID] {
    210          MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    211          if (Accessible* result = GetAccessibleByID(resultID)) {
    212            SendHoverEnterEvent(result);
    213          }
    214        });
    215      }
    216    }
    217  }
    218 }
    219 
    220 static void GetSelectionOrCaret(HyperTextAccessibleBase* aHyperTextAcc,
    221                                int32_t* aStartOffset, int32_t* aEndOffset) {
    222  if (!aHyperTextAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) {
    223    *aStartOffset = *aEndOffset = aHyperTextAcc->CaretOffset();
    224  }
    225 }
    226 
    227 static void AdjustCaretToTextNavigation(Accessible* aAccessible,
    228                                        int32_t aStartOffset,
    229                                        int32_t aEndOffset, bool aForward,
    230                                        bool aSelect) {
    231  MOZ_ASSERT(NS_IsMainThread());
    232  if (!(aAccessible->State() & states::EDITABLE)) {
    233    return;
    234  }
    235 
    236  HyperTextAccessibleBase* editable = aAccessible->AsHyperTextBase();
    237  MOZ_ASSERT(editable);
    238  if (!editable) {
    239    return;
    240  }
    241 
    242  int32_t newOffset = aForward ? aEndOffset : aStartOffset;
    243  if (aSelect) {
    244    int32_t anchor = editable->CaretOffset();
    245    if (editable->SelectionCount()) {
    246      int32_t startSel, endSel;
    247      GetSelectionOrCaret(editable, &startSel, &endSel);
    248      anchor = startSel == anchor ? endSel : startSel;
    249    }
    250    editable->SetSelectionBoundsAt(0, anchor, newOffset);
    251  } else {
    252    editable->SetCaretOffset(newOffset);
    253  }
    254 }
    255 
    256 bool SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity,
    257                                        int32_t aStartOffset,
    258                                        int32_t aEndOffset, bool aForward,
    259                                        bool aSelect) {
    260  MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
    261  MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    262  RefPtr<SessionAccessibility> self(this);
    263  if (Accessible* acc = GetAccessibleByID(aID)) {
    264    if (acc->IsLocal()) {
    265      nsAppShell::PostEvent([this, self, aID, aGranularity, aStartOffset,
    266                             aEndOffset, aForward, aSelect] {
    267        MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    268        if (Accessible* _acc = GetAccessibleByID(aID)) {
    269          auto result = AccessibleWrap::NavigateText(
    270              _acc, aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
    271 
    272          if (result) {
    273            SendTextTraversedEvent(_acc, result->first, result->second);
    274            AdjustCaretToTextNavigation(_acc, result->first, result->second,
    275                                        aForward, aSelect);
    276          }
    277        }
    278      });
    279      return true;
    280    } else {
    281      auto result = AccessibleWrap::NavigateText(
    282          acc, aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
    283      if (result) {
    284        nsAppShell::PostEvent([this, self, aID, result, aForward, aSelect] {
    285          MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
    286          if (Accessible* _acc = GetAccessibleByID(aID)) {
    287            SendTextTraversedEvent(_acc, result->first, result->second);
    288            AdjustCaretToTextNavigation(_acc, result->first, result->second,
    289                                        aForward, aSelect);
    290          }
    291        });
    292      }
    293 
    294      return !!result;
    295    }
    296  }
    297 
    298  return false;
    299 }
    300 
    301 void SessionAccessibility::SetSelection(int32_t aID, int32_t aStart,
    302                                        int32_t aEnd) {
    303  if (Accessible* acc = GetAccessibleByID(aID)) {
    304    if (auto* textAcc = acc->AsHyperTextBase()) {
    305      if (aStart == aEnd) {
    306        textAcc->SetCaretOffset(aStart);
    307      } else {
    308        textAcc->SetSelectionBoundsAt(0, aStart, aEnd);
    309      }
    310    }
    311  }
    312 }
    313 
    314 void SessionAccessibility::Cut(int32_t aID) {
    315  if (Accessible* acc = GetAccessibleByID(aID)) {
    316    if (auto* textAcc = acc->AsHyperTextBase()) {
    317      int32_t startSel, endSel;
    318      if (textAcc->SelectionBoundsAt(0, &startSel, &endSel)) {
    319        textAcc->CutText(startSel, endSel);
    320      }
    321    }
    322  }
    323 }
    324 
    325 void SessionAccessibility::Copy(int32_t aID) {
    326  if (Accessible* acc = GetAccessibleByID(aID)) {
    327    if (auto* textAcc = acc->AsHyperTextBase()) {
    328      int32_t startSel, endSel;
    329      GetSelectionOrCaret(textAcc, &startSel, &endSel);
    330      textAcc->CopyText(startSel, endSel);
    331    }
    332  }
    333 }
    334 
    335 void SessionAccessibility::Paste(int32_t aID) {
    336  if (Accessible* acc = GetAccessibleByID(aID)) {
    337    if (auto* textAcc = acc->AsHyperTextBase()) {
    338      textAcc->PasteText(nsIAccessibleText::TEXT_OFFSET_CARET);
    339    }
    340  }
    341 }
    342 
    343 RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
    344    Accessible* aAccessible) {
    345  MOZ_ASSERT(NS_IsMainThread());
    346  if (LocalAccessible* localAcc = aAccessible->AsLocal()) {
    347    DocAccessible* docAcc = localAcc->Document();
    348    // If the accessible is being shutdown from the doc's shutdown
    349    // the doc accessible won't have a ref to a presshell anymore,
    350    // but we should have a ref to the DOM document node, and the DOM doc
    351    // has a ref to the presshell.
    352    dom::Document* doc = docAcc ? docAcc->DocumentNode() : nullptr;
    353    if (doc && doc->IsContentDocument()) {
    354      // Only content accessibles should have an associated SessionAccessible.
    355      return GetInstanceFor(doc->GetPresShell());
    356    }
    357  } else {
    358    dom::CanonicalBrowsingContext* cbc =
    359        static_cast<dom::BrowserParent*>(
    360            aAccessible->AsRemote()->Document()->Manager())
    361            ->GetBrowsingContext()
    362            ->Top();
    363    dom::BrowserParent* bp = cbc->GetBrowserParent();
    364    if (!bp) {
    365      bp = static_cast<dom::BrowserParent*>(
    366          aAccessible->AsRemote()->Document()->Manager());
    367    }
    368    if (auto element = bp->GetOwnerElement()) {
    369      if (auto doc = element->OwnerDoc()) {
    370        if (nsPresContext* presContext = doc->GetPresContext()) {
    371          return GetInstanceFor(presContext->PresShell());
    372        }
    373      } else {
    374        MOZ_ASSERT_UNREACHABLE(
    375            "Browser parent's element does not have owner doc.");
    376      }
    377    }
    378  }
    379 
    380  return nullptr;
    381 }
    382 
    383 RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
    384    PresShell* aPresShell) {
    385  MOZ_ASSERT(NS_IsMainThread());
    386  if (!aPresShell) {
    387    return nullptr;
    388  }
    389 
    390  nsCOMPtr<nsIWidget> rootWidget = aPresShell->GetRootWidget();
    391  // `rootWidget` can be one of several types. Here we make sure it is an
    392  // android nsWindow.
    393  if (RefPtr<nsWindow> window = nsWindow::From(rootWidget)) {
    394    return window->GetSessionAccessibility();
    395  }
    396 
    397  return nullptr;
    398 }
    399 
    400 void SessionAccessibility::SendAccessibilityFocusedEvent(
    401    Accessible* aAccessible, bool aScrollIntoView) {
    402  MOZ_ASSERT(NS_IsMainThread());
    403  mSessionAccessibility->SendEvent(
    404      java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
    405      AccessibleWrap::GetVirtualViewID(aAccessible),
    406      AccessibleWrap::AndroidClass(aAccessible), nullptr);
    407  if (aScrollIntoView) {
    408    aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
    409  }
    410 }
    411 
    412 void SessionAccessibility::SendHoverEnterEvent(Accessible* aAccessible) {
    413  MOZ_ASSERT(NS_IsMainThread());
    414  mSessionAccessibility->SendEvent(
    415      java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
    416      AccessibleWrap::GetVirtualViewID(aAccessible),
    417      AccessibleWrap::AndroidClass(aAccessible), nullptr);
    418 }
    419 
    420 void SessionAccessibility::SendFocusEvent(Accessible* aAccessible) {
    421  MOZ_ASSERT(NS_IsMainThread());
    422  // Suppress focus events from about:blank pages.
    423  // This is important for tests.
    424  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
    425    return;
    426  }
    427 
    428  mSessionAccessibility->SendEvent(
    429      java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
    430      AccessibleWrap::GetVirtualViewID(aAccessible),
    431      AccessibleWrap::AndroidClass(aAccessible), nullptr);
    432 }
    433 
    434 void SessionAccessibility::SendScrollingEvent(Accessible* aAccessible,
    435                                              int32_t aScrollX,
    436                                              int32_t aScrollY,
    437                                              int32_t aMaxScrollX,
    438                                              int32_t aMaxScrollY) {
    439  MOZ_ASSERT(NS_IsMainThread());
    440  int32_t virtualViewId = AccessibleWrap::GetVirtualViewID(aAccessible);
    441 
    442  if (virtualViewId != kNoID) {
    443    // XXX: Support scrolling in subframes
    444    return;
    445  }
    446 
    447  GECKOBUNDLE_START(eventInfo);
    448  GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
    449  GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
    450  GECKOBUNDLE_PUT(eventInfo, "maxScrollX",
    451                  java::sdk::Integer::ValueOf(aMaxScrollX));
    452  GECKOBUNDLE_PUT(eventInfo, "maxScrollY",
    453                  java::sdk::Integer::ValueOf(aMaxScrollY));
    454  GECKOBUNDLE_FINISH(eventInfo);
    455 
    456  mSessionAccessibility->SendEvent(
    457      java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
    458      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    459  SendWindowContentChangedEvent();
    460 }
    461 
    462 void SessionAccessibility::SendWindowContentChangedEvent() {
    463  mSessionAccessibility->SendEvent(
    464      java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED, kNoID,
    465      java::SessionAccessibility::CLASSNAME_WEBVIEW, nullptr);
    466 }
    467 
    468 void SessionAccessibility::SendWindowStateChangedEvent(
    469    Accessible* aAccessible) {
    470  MOZ_ASSERT(NS_IsMainThread());
    471  // Suppress window state changed events from about:blank pages.
    472  // This is important for tests.
    473  if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
    474    return;
    475  }
    476 
    477  mSessionAccessibility->SendEvent(
    478      java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
    479      AccessibleWrap::GetVirtualViewID(aAccessible),
    480      AccessibleWrap::AndroidClass(aAccessible), nullptr);
    481 
    482  SendWindowContentChangedEvent();
    483 }
    484 
    485 void SessionAccessibility::SendTextSelectionChangedEvent(
    486    Accessible* aAccessible, int32_t aCaretOffset) {
    487  MOZ_ASSERT(NS_IsMainThread());
    488  int32_t fromIndex = aCaretOffset;
    489  int32_t startSel = -1;
    490  int32_t endSel = -1;
    491  bool hasSelection =
    492      aAccessible->AsHyperTextBase()->SelectionBoundsAt(0, &startSel, &endSel);
    493 
    494  if (hasSelection) {
    495    fromIndex = startSel == aCaretOffset ? endSel : startSel;
    496  }
    497 
    498  nsAutoString text;
    499  if (aAccessible->IsHyperText()) {
    500    aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
    501  } else if (aAccessible->IsText()) {
    502    aAccessible->AppendTextTo(text, 0, -1);
    503  }
    504 
    505  GECKOBUNDLE_START(eventInfo);
    506  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
    507  GECKOBUNDLE_PUT(eventInfo, "fromIndex",
    508                  java::sdk::Integer::ValueOf(fromIndex));
    509  GECKOBUNDLE_PUT(eventInfo, "toIndex",
    510                  java::sdk::Integer::ValueOf(aCaretOffset));
    511  GECKOBUNDLE_FINISH(eventInfo);
    512 
    513  mSessionAccessibility->SendEvent(
    514      java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
    515      AccessibleWrap::GetVirtualViewID(aAccessible),
    516      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    517 }
    518 
    519 void SessionAccessibility::SendTextChangedEvent(Accessible* aAccessible,
    520                                                const nsAString& aStr,
    521                                                int32_t aStart, uint32_t aLen,
    522                                                bool aIsInsert,
    523                                                bool aFromUser) {
    524  MOZ_ASSERT(NS_IsMainThread());
    525  if (!aFromUser) {
    526    // Only dispatch text change events from users, for now.
    527    return;
    528  }
    529 
    530  nsAutoString text;
    531  if (aAccessible->IsHyperText()) {
    532    aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
    533  } else if (aAccessible->IsText()) {
    534    aAccessible->AppendTextTo(text, 0, -1);
    535  }
    536  nsAutoString beforeText(text);
    537  if (aIsInsert) {
    538    beforeText.Cut(aStart, aLen);
    539  } else {
    540    beforeText.Insert(aStr, aStart);
    541  }
    542 
    543  GECKOBUNDLE_START(eventInfo);
    544  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
    545  GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
    546  GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStart));
    547  GECKOBUNDLE_PUT(eventInfo, "addedCount",
    548                  java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
    549  GECKOBUNDLE_PUT(eventInfo, "removedCount",
    550                  java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
    551  GECKOBUNDLE_FINISH(eventInfo);
    552 
    553  mSessionAccessibility->SendEvent(
    554      java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
    555      AccessibleWrap::GetVirtualViewID(aAccessible),
    556      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    557 }
    558 
    559 void SessionAccessibility::SendTextTraversedEvent(Accessible* aAccessible,
    560                                                  int32_t aStartOffset,
    561                                                  int32_t aEndOffset) {
    562  MOZ_ASSERT(NS_IsMainThread());
    563  nsAutoString text;
    564  if (aAccessible->IsHyperText()) {
    565    aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
    566  } else if (aAccessible->IsText()) {
    567    aAccessible->AppendTextTo(text, 0, -1);
    568  }
    569 
    570  GECKOBUNDLE_START(eventInfo);
    571  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
    572  GECKOBUNDLE_PUT(eventInfo, "fromIndex",
    573                  java::sdk::Integer::ValueOf(aStartOffset));
    574  GECKOBUNDLE_PUT(eventInfo, "toIndex",
    575                  java::sdk::Integer::ValueOf(aEndOffset));
    576  GECKOBUNDLE_FINISH(eventInfo);
    577 
    578  mSessionAccessibility->SendEvent(
    579      java::sdk::AccessibilityEvent::
    580          TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
    581      AccessibleWrap::GetVirtualViewID(aAccessible),
    582      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    583 }
    584 
    585 void SessionAccessibility::SendClickedEvent(Accessible* aAccessible,
    586                                            uint32_t aFlags) {
    587  GECKOBUNDLE_START(eventInfo);
    588  GECKOBUNDLE_PUT(eventInfo, "flags", java::sdk::Integer::ValueOf(aFlags));
    589  GECKOBUNDLE_FINISH(eventInfo);
    590 
    591  mSessionAccessibility->SendEvent(
    592      java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
    593      AccessibleWrap::GetVirtualViewID(aAccessible),
    594      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    595 }
    596 
    597 void SessionAccessibility::SendSelectedEvent(Accessible* aAccessible,
    598                                             bool aSelected) {
    599  MOZ_ASSERT(NS_IsMainThread());
    600  GECKOBUNDLE_START(eventInfo);
    601  // Boolean::FALSE/TRUE gets clobbered by a macro, so ugh.
    602  GECKOBUNDLE_PUT(eventInfo, "selected",
    603                  java::sdk::Integer::ValueOf(aSelected ? 1 : 0));
    604  GECKOBUNDLE_FINISH(eventInfo);
    605 
    606  mSessionAccessibility->SendEvent(
    607      java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
    608      AccessibleWrap::GetVirtualViewID(aAccessible),
    609      AccessibleWrap::AndroidClass(aAccessible), eventInfo);
    610 }
    611 
    612 void SessionAccessibility::SendAnnouncementEvent(Accessible* aAccessible,
    613                                                 const nsAString& aAnnouncement,
    614                                                 uint16_t aPriority) {
    615  MOZ_ASSERT(NS_IsMainThread());
    616  GECKOBUNDLE_START(eventInfo);
    617  GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(aAnnouncement));
    618  GECKOBUNDLE_FINISH(eventInfo);
    619 
    620  // Announcements should have the root as their source, so we ignore the
    621  // accessible of the event.
    622  mSessionAccessibility->SendEvent(
    623      java::sdk::AccessibilityEvent::TYPE_ANNOUNCEMENT, kNoID,
    624      java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo);
    625 }
    626 
    627 void SessionAccessibility::PopulateNodeInfo(
    628    Accessible* aAccessible, mozilla::jni::Object::Param aNodeInfo) {
    629  nsAutoString name;
    630  ENameValueFlag nameFlag = aAccessible->Name(name);
    631  nsAutoString textValue;
    632  aAccessible->Value(textValue);
    633  nsAutoString nodeID;
    634  aAccessible->DOMNodeID(nodeID);
    635  nsAutoString accDesc;
    636  aAccessible->Description(accDesc);
    637  uint64_t state = aAccessible->State();
    638  LayoutDeviceIntRect bounds = aAccessible->Bounds();
    639  int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible);
    640  Accessible* parent = virtualViewID != kNoID ? aAccessible->Parent() : nullptr;
    641  int32_t parentID = parent ? AccessibleWrap::GetVirtualViewID(parent) : 0;
    642  role role = aAccessible->Role();
    643  if (role == roles::LINK && !(state & states::LINKED)) {
    644    // A link without the linked state (<a> with no href) shouldn't be presented
    645    // as a link.
    646    role = roles::TEXT;
    647  }
    648 
    649  uint32_t flags = AccessibleWrap::GetFlags(aAccessible);
    650 
    651  int32_t className = AccessibleWrap::AndroidClass(aAccessible);
    652 
    653  nsAutoString hint;
    654  nsAutoString text;
    655  nsAutoString description;
    656  if (state & states::EDITABLE) {
    657    // An editable field's name is populated in the hint.
    658    hint.Assign(name);
    659    text.Assign(textValue);
    660  } else {
    661    if (role == roles::LINK || role == roles::HEADING) {
    662      description.Assign(name);
    663    } else if (role != roles::CELL || nameFlag != eNameFromSubtree) {
    664      // In most cases, use the name as the text. We discard the name completely
    665      // for a table cell where the name is computed from the subtree because
    666      // we don't want UI Automator to find the cell instead of a link inside
    667      // it. TraversalRule ignores table cells anyway, so this is only relevant
    668      // to UI Automator.
    669      text.Assign(name);
    670    }
    671  }
    672 
    673  if (!accDesc.IsEmpty()) {
    674    if (!hint.IsEmpty()) {
    675      // If this is an editable, the description is concatenated with a
    676      // whitespace directly after the name.
    677      hint.AppendLiteral(" ");
    678    }
    679    hint.Append(accDesc);
    680  }
    681 
    682  if ((state & states::REQUIRED) != 0) {
    683    nsAutoString requiredString;
    684    if (LocalizeString(u"stateRequired"_ns, requiredString)) {
    685      if (!hint.IsEmpty()) {
    686        // If the hint is non-empty, concatenate with a comma for a brief pause.
    687        hint.AppendLiteral(", ");
    688      }
    689      hint.Append(requiredString);
    690    }
    691  }
    692 
    693  RefPtr<AccAttributes> attributes = aAccessible->Attributes();
    694  nsAccUtils::SetAccGroupAttrs(attributes, aAccessible);
    695 
    696  nsAutoString geckoRole;
    697  nsAutoString roleDescription;
    698  if (virtualViewID != kNoID) {
    699    AccessibleWrap::GetRoleDescription(role, attributes, geckoRole,
    700                                       roleDescription);
    701  }
    702 
    703  int32_t inputType = 0;
    704  if (attributes) {
    705    nsString inputTypeAttr;
    706    attributes->GetAttribute(nsGkAtoms::textInputType, inputTypeAttr);
    707    inputType = AccessibleWrap::GetInputType(inputTypeAttr);
    708  }
    709 
    710  auto childCount = aAccessible->ChildCount();
    711  nsTArray<int32_t> children(childCount);
    712  if (!nsAccUtils::MustPrune(aAccessible)) {
    713    for (uint32_t i = 0; i < childCount; i++) {
    714      auto child = aAccessible->ChildAt(i);
    715      children.AppendElement(AccessibleWrap::GetVirtualViewID(child));
    716    }
    717  }
    718 
    719  const int32_t boundsArray[4] = {bounds.x, bounds.y, bounds.x + bounds.width,
    720                                  bounds.y + bounds.height};
    721 
    722  mSessionAccessibility->PopulateNodeInfo(
    723      aNodeInfo, virtualViewID, parentID, jni::IntArray::From(children), flags,
    724      className, jni::IntArray::New(boundsArray, 4), jni::StringParam(text),
    725      jni::StringParam(description), jni::StringParam(hint),
    726      jni::StringParam(geckoRole), jni::StringParam(roleDescription),
    727      jni::StringParam(nodeID), inputType);
    728 
    729  if (aAccessible->HasNumericValue()) {
    730    double curValue = aAccessible->CurValue();
    731    double minValue = aAccessible->MinValue();
    732    double maxValue = aAccessible->MaxValue();
    733    double step = aAccessible->Step();
    734 
    735    int32_t rangeType = 0;  // integer
    736    if (maxValue == 1 && minValue == 0) {
    737      rangeType = 2;  // percent
    738    } else if (std::round(step) != step) {
    739      rangeType = 1;  // float;
    740    }
    741 
    742    mSessionAccessibility->PopulateNodeRangeInfo(
    743        aNodeInfo, rangeType, static_cast<float>(minValue),
    744        static_cast<float>(maxValue), static_cast<float>(curValue));
    745  }
    746 
    747  if (attributes) {
    748    Maybe<int32_t> rowIndex =
    749        attributes->GetAttribute<int32_t>(nsGkAtoms::posinset);
    750    if (rowIndex) {
    751      mSessionAccessibility->PopulateNodeCollectionItemInfo(
    752          aNodeInfo, *rowIndex - 1, 1, 0, 1);
    753    }
    754 
    755    Maybe<int32_t> rowCount =
    756        attributes->GetAttribute<int32_t>(nsGkAtoms::child_item_count);
    757    if (rowCount) {
    758      int32_t selectionMode = 0;
    759      if (aAccessible->IsSelect()) {
    760        selectionMode = (state & states::MULTISELECTABLE) ? 2 : 1;
    761      }
    762      mSessionAccessibility->PopulateNodeCollectionInfo(
    763          aNodeInfo, *rowCount, 1, selectionMode,
    764          attributes->HasAttribute(nsGkAtoms::tree));
    765    }
    766  }
    767 }
    768 
    769 Accessible* SessionAccessibility::GetAccessibleByID(int32_t aID) const {
    770  return mIDToAccessibleMap.Get(aID);
    771 }
    772 
    773 #ifdef DEBUG
    774 static bool IsDetachedDoc(Accessible* aAccessible) {
    775  if (!aAccessible->IsRemote() || !aAccessible->AsRemote()->IsDoc()) {
    776    return false;
    777  }
    778 
    779  return !aAccessible->Parent() ||
    780         aAccessible->Parent()->FirstChild() != aAccessible;
    781 }
    782 #endif
    783 
    784 SessionAccessibility::IDMappingEntry::IDMappingEntry(Accessible* aAccessible)
    785    : mInternalID(0) {
    786  *this = aAccessible;
    787 }
    788 
    789 SessionAccessibility::IDMappingEntry&
    790 SessionAccessibility::IDMappingEntry::operator=(Accessible* aAccessible) {
    791  mInternalID = aAccessible->ID();
    792  MOZ_ASSERT(!(mInternalID & IS_REMOTE), "First bit is used in accessible ID!");
    793  if (aAccessible->IsRemote()) {
    794    mInternalID |= IS_REMOTE;
    795  }
    796 
    797  Accessible* docAcc = nsAccUtils::DocumentFor(aAccessible);
    798  MOZ_ASSERT(docAcc);
    799  if (docAcc) {
    800    MOZ_ASSERT(docAcc->IsRemote() == aAccessible->IsRemote());
    801    if (docAcc->IsRemote()) {
    802      mDoc = docAcc->AsRemote()->AsDoc();
    803    } else {
    804      mDoc = docAcc->AsLocal();
    805    }
    806  }
    807 
    808  return *this;
    809 }
    810 
    811 SessionAccessibility::IDMappingEntry::operator Accessible*() const {
    812  if (mInternalID == 0) {
    813    return static_cast<LocalAccessible*>(mDoc.get());
    814  }
    815 
    816  if (mInternalID == IS_REMOTE) {
    817    return static_cast<DocAccessibleParent*>(mDoc.get());
    818  }
    819 
    820  if (mInternalID & IS_REMOTE) {
    821    return static_cast<DocAccessibleParent*>(mDoc.get())
    822        ->GetAccessible(mInternalID & ~IS_REMOTE);
    823  }
    824 
    825  Accessible* accessible =
    826      static_cast<LocalAccessible*>(mDoc.get())
    827          ->AsDoc()
    828          ->GetAccessibleByUniqueID(reinterpret_cast<void*>(mInternalID));
    829  // If the accessible is retrievable from the DocAccessible, it can't be
    830  // defunct.
    831  MOZ_ASSERT(!accessible->AsLocal()->IsDefunct());
    832 
    833  return accessible;
    834 }
    835 
    836 void SessionAccessibility::RegisterAccessible(Accessible* aAccessible) {
    837  if (IPCAccessibilityActive()) {
    838    // Don't register accessible in content process.
    839    return;
    840  }
    841 
    842  nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
    843  RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aAccessible);
    844  if (!sessionAcc) {
    845    return;
    846  }
    847 
    848  bool isTopLevel = false;
    849  if (aAccessible->IsLocal() && aAccessible->IsDoc()) {
    850    DocAccessibleWrap* doc =
    851        static_cast<DocAccessibleWrap*>(aAccessible->AsLocal()->AsDoc());
    852    isTopLevel = doc->IsTopLevelContentDoc();
    853  } else if (aAccessible->IsRemote() && aAccessible->IsDoc()) {
    854    isTopLevel = aAccessible->AsRemote()->AsDoc()->IsTopLevel();
    855  }
    856 
    857  int32_t virtualViewID = kNoID;
    858  if (!isTopLevel) {
    859    if (sessionAcc->mIDToAccessibleMap.IsEmpty()) {
    860      // We expect there to already be at least one accessible
    861      // registered (the top-level one). If it isn't we are
    862      // probably in a shutdown process where it was already
    863      // unregistered. So we don't register this accessible.
    864      return;
    865    }
    866    // Don't use the special "unset" value (0).
    867    while ((virtualViewID = sIDSet.GetID()) == kUnsetID) {
    868    }
    869  }
    870  AccessibleWrap::SetVirtualViewID(aAccessible, virtualViewID);
    871 
    872  Accessible* oldAcc = sessionAcc->mIDToAccessibleMap.Get(virtualViewID);
    873  if (oldAcc) {
    874    // About to overwrite mapping of registered accessible. This should
    875    // only happen when the registered accessible is a detached document.
    876    MOZ_ASSERT(IsDetachedDoc(oldAcc),
    877               "ID already registered to non-detached document");
    878    AccessibleWrap::SetVirtualViewID(oldAcc, kUnsetID);
    879  }
    880 
    881  sessionAcc->mIDToAccessibleMap.InsertOrUpdate(virtualViewID, aAccessible);
    882 }
    883 
    884 void SessionAccessibility::UnregisterAccessible(Accessible* aAccessible) {
    885  if (IPCAccessibilityActive()) {
    886    // Don't unregister accessible in content process.
    887    return;
    888  }
    889 
    890  nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
    891  int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible);
    892  if (virtualViewID == kUnsetID) {
    893    return;
    894  }
    895 
    896  RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aAccessible);
    897  if (sessionAcc) {
    898    Accessible* registeredAcc =
    899        sessionAcc->mIDToAccessibleMap.Get(virtualViewID);
    900    if (registeredAcc != aAccessible) {
    901      // Attempting to unregister an accessible that is not mapped to
    902      // its virtual view ID. This probably means it is a detached document
    903      // and a more recent document overwrote its '-1' mapping.
    904      // We set its own virtual view ID to `kUnsetID` and return early.
    905      MOZ_ASSERT(!registeredAcc || IsDetachedDoc(aAccessible),
    906                 "Accessible is detached document");
    907      AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID);
    908      return;
    909    }
    910 
    911    MOZ_ASSERT(registeredAcc, "Unregistering unregistered accessible");
    912    MOZ_ASSERT(registeredAcc == aAccessible, "Unregistering wrong accessible");
    913    sessionAcc->mIDToAccessibleMap.Remove(virtualViewID);
    914  }
    915 
    916  if (virtualViewID > kNoID) {
    917    sIDSet.ReleaseID(virtualViewID);
    918  }
    919 
    920  AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID);
    921 }
    922 
    923 void SessionAccessibility::UnregisterAll(PresShell* aPresShell) {
    924  if (IPCAccessibilityActive()) {
    925    // Don't unregister accessible in content process.
    926    return;
    927  }
    928 
    929  nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
    930  RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aPresShell);
    931  if (sessionAcc) {
    932    sessionAcc->mIDToAccessibleMap.Clear();
    933  }
    934 }