Bug 1880550 - Propagate explicit heights to scrolled table cells as min-heights....
[gecko.git] / accessible / android / SessionAccessibility.cpp
blobaab9b7da69b3cfb4577236529982e1f1a42e4f7f
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/. */
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 #include "nsViewManager.h"
21 #include "mozilla/PresShell.h"
22 #include "mozilla/dom/BrowserParent.h"
23 #include "mozilla/dom/CanonicalBrowsingContext.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/DocumentInlines.h"
26 #include "mozilla/a11y/Accessible.h"
27 #include "mozilla/a11y/DocAccessibleParent.h"
28 #include "mozilla/a11y/DocManager.h"
29 #include "mozilla/a11y/HyperTextAccessibleBase.h"
30 #include "mozilla/jni/GeckoBundleUtils.h"
31 #include "mozilla/jni/NativesInlines.h"
32 #include "mozilla/widget/GeckoViewSupport.h"
33 #include "mozilla/MouseEvents.h"
34 #include "mozilla/dom/MouseEventBinding.h"
36 #ifdef DEBUG
37 # include <android/log.h>
38 # define AALOG(args...) \
39 __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
40 #else
41 # define AALOG(args...) \
42 do { \
43 } while (0)
44 #endif
46 using namespace mozilla::a11y;
48 // IDs should be a positive 32bit integer.
49 IDSet sIDSet(31UL);
51 class Settings final
52 : public mozilla::java::SessionAccessibility::Settings::Natives<Settings> {
53 public:
54 static void ToggleNativeAccessibility(bool aEnable) {
55 if (aEnable) {
56 GetOrCreateAccService();
57 } else {
58 MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
63 SessionAccessibility::SessionAccessibility(
64 jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow,
65 java::SessionAccessibility::NativeProvider::Param aSessionAccessibility)
66 : mWindow(aWindow), mSessionAccessibility(aSessionAccessibility) {
67 SetAttached(true, nullptr);
70 void SessionAccessibility::SetAttached(bool aAttached,
71 already_AddRefed<Runnable> aRunnable) {
72 if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
73 uiThread->Dispatch(NS_NewRunnableFunction(
74 "SessionAccessibility::Attach",
75 [aAttached,
76 sa = java::SessionAccessibility::NativeProvider::GlobalRef(
77 mSessionAccessibility),
78 runnable = RefPtr<Runnable>(aRunnable)] {
79 sa->SetAttached(aAttached);
80 if (runnable) {
81 runnable->Run();
83 }));
87 void SessionAccessibility::Init() {
88 java::SessionAccessibility::NativeProvider::Natives<
89 SessionAccessibility>::Init();
90 Settings::Init();
93 void SessionAccessibility::GetNodeInfo(int32_t aID,
94 mozilla::jni::Object::Param aNodeInfo) {
95 MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
96 ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
97 java::GeckoBundle::GlobalRef ret = nullptr;
98 RefPtr<SessionAccessibility> self(this);
99 if (Accessible* acc = GetAccessibleByID(aID)) {
100 if (acc->IsLocal()) {
101 mal.Unlock();
102 nsAppShell::SyncRunEvent(
103 [this, self, aID, aNodeInfo = jni::Object::GlobalRef(aNodeInfo)] {
104 if (Accessible* acc = GetAccessibleByID(aID)) {
105 PopulateNodeInfo(acc, aNodeInfo);
106 } else {
107 AALOG("oops, nothing for %d", aID);
110 } else {
111 PopulateNodeInfo(acc, aNodeInfo);
113 } else {
114 AALOG("oops, nothing for %d", aID);
118 int SessionAccessibility::GetNodeClassName(int32_t aID) {
119 MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
120 ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
121 int32_t classNameEnum = java::SessionAccessibility::CLASSNAME_VIEW;
122 RefPtr<SessionAccessibility> self(this);
123 if (Accessible* acc = GetAccessibleByID(aID)) {
124 if (acc->IsLocal()) {
125 mal.Unlock();
126 nsAppShell::SyncRunEvent([this, self, aID, &classNameEnum] {
127 if (Accessible* acc = GetAccessibleByID(aID)) {
128 classNameEnum = AccessibleWrap::AndroidClass(acc);
131 } else {
132 classNameEnum = AccessibleWrap::AndroidClass(acc);
136 return classNameEnum;
139 void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) {
140 if (Accessible* acc = GetAccessibleByID(aID)) {
141 if (acc->IsRemote()) {
142 acc->AsRemote()->ReplaceText(PromiseFlatString(aText->ToString()));
143 } else if (acc->AsLocal()->IsHyperText()) {
144 acc->AsLocal()->AsHyperText()->ReplaceText(aText->ToString());
149 void SessionAccessibility::Click(int32_t aID) {
150 MOZ_ASSERT(NS_IsMainThread());
151 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
152 if (Accessible* acc = GetAccessibleByID(aID)) {
153 acc->DoAction(0);
157 bool SessionAccessibility::Pivot(int32_t aID, int32_t aGranularity,
158 bool aForward, bool aInclusive) {
159 MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
160 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
161 RefPtr<SessionAccessibility> self(this);
162 if (Accessible* acc = GetAccessibleByID(aID)) {
163 if (acc->IsLocal()) {
164 nsAppShell::PostEvent(
165 [this, self, aID, aGranularity, aForward, aInclusive] {
166 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
167 if (Accessible* _acc = GetAccessibleByID(aID)) {
168 MOZ_ASSERT(_acc->IsLocal());
169 if (Accessible* result = AccessibleWrap::DoPivot(
170 _acc, aGranularity, aForward, aInclusive)) {
171 SendAccessibilityFocusedEvent(result, true);
175 return true;
177 Accessible* result =
178 AccessibleWrap::DoPivot(acc, aGranularity, aForward, aInclusive);
179 if (result) {
180 int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(result);
181 nsAppShell::PostEvent([this, self, virtualViewID] {
182 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
183 if (Accessible* acc = GetAccessibleByID(virtualViewID)) {
184 SendAccessibilityFocusedEvent(acc, true);
187 return true;
191 return false;
194 void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) {
195 MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
196 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
197 RefPtr<SessionAccessibility> self(this);
198 if (Accessible* origin = GetAccessibleByID(aID)) {
199 if (origin->IsLocal()) {
200 nsAppShell::PostEvent([this, self, aID, aX, aY] {
201 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
202 if (Accessible* origin = GetAccessibleByID(aID)) {
203 if (Accessible* result =
204 AccessibleWrap::ExploreByTouch(origin, aX, aY)) {
205 SendHoverEnterEvent(result);
209 } else {
210 if (Accessible* result = AccessibleWrap::ExploreByTouch(origin, aX, aY)) {
211 int32_t resultID = AccessibleWrap::GetVirtualViewID(result);
212 nsAppShell::PostEvent([this, self, resultID] {
213 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
214 if (Accessible* result = GetAccessibleByID(resultID)) {
215 SendHoverEnterEvent(result);
223 static void GetSelectionOrCaret(HyperTextAccessibleBase* aHyperTextAcc,
224 int32_t* aStartOffset, int32_t* aEndOffset) {
225 if (!aHyperTextAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) {
226 *aStartOffset = *aEndOffset = aHyperTextAcc->CaretOffset();
230 static void AdjustCaretToTextNavigation(Accessible* aAccessible,
231 int32_t aStartOffset,
232 int32_t aEndOffset, bool aForward,
233 bool aSelect) {
234 MOZ_ASSERT(NS_IsMainThread());
235 if (!(aAccessible->State() & states::EDITABLE)) {
236 return;
239 HyperTextAccessibleBase* editable = aAccessible->AsHyperTextBase();
240 MOZ_ASSERT(editable);
241 if (!editable) {
242 return;
245 int32_t newOffset = aForward ? aEndOffset : aStartOffset;
246 if (aSelect) {
247 int32_t anchor = editable->CaretOffset();
248 if (editable->SelectionCount()) {
249 int32_t startSel, endSel;
250 GetSelectionOrCaret(editable, &startSel, &endSel);
251 anchor = startSel == anchor ? endSel : startSel;
253 editable->SetSelectionBoundsAt(0, anchor, newOffset);
254 } else {
255 editable->SetCaretOffset(newOffset);
259 bool SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity,
260 int32_t aStartOffset,
261 int32_t aEndOffset, bool aForward,
262 bool aSelect) {
263 MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
264 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
265 RefPtr<SessionAccessibility> self(this);
266 if (Accessible* acc = GetAccessibleByID(aID)) {
267 if (acc->IsLocal()) {
268 nsAppShell::PostEvent([this, self, aID, aGranularity, aStartOffset,
269 aEndOffset, aForward, aSelect] {
270 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
271 if (Accessible* _acc = GetAccessibleByID(aID)) {
272 auto result = AccessibleWrap::NavigateText(
273 _acc, aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
275 if (result) {
276 SendTextTraversedEvent(_acc, result->first, result->second);
277 AdjustCaretToTextNavigation(_acc, result->first, result->second,
278 aForward, aSelect);
282 return true;
283 } else {
284 auto result = AccessibleWrap::NavigateText(
285 acc, aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
286 if (result) {
287 nsAppShell::PostEvent([this, self, aID, result, aForward, aSelect] {
288 MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
289 if (Accessible* _acc = GetAccessibleByID(aID)) {
290 SendTextTraversedEvent(_acc, result->first, result->second);
291 AdjustCaretToTextNavigation(_acc, result->first, result->second,
292 aForward, aSelect);
297 return !!result;
301 return false;
304 void SessionAccessibility::SetSelection(int32_t aID, int32_t aStart,
305 int32_t aEnd) {
306 if (Accessible* acc = GetAccessibleByID(aID)) {
307 if (auto* textAcc = acc->AsHyperTextBase()) {
308 if (aStart == aEnd) {
309 textAcc->SetCaretOffset(aStart);
310 } else {
311 textAcc->SetSelectionBoundsAt(0, aStart, aEnd);
317 void SessionAccessibility::Cut(int32_t aID) {
318 if (Accessible* acc = GetAccessibleByID(aID)) {
319 if (auto* textAcc = acc->AsHyperTextBase()) {
320 int32_t startSel, endSel;
321 if (textAcc->SelectionBoundsAt(0, &startSel, &endSel)) {
322 textAcc->CutText(startSel, endSel);
328 void SessionAccessibility::Copy(int32_t aID) {
329 if (Accessible* acc = GetAccessibleByID(aID)) {
330 if (auto* textAcc = acc->AsHyperTextBase()) {
331 int32_t startSel, endSel;
332 GetSelectionOrCaret(textAcc, &startSel, &endSel);
333 textAcc->CopyText(startSel, endSel);
338 void SessionAccessibility::Paste(int32_t aID) {
339 if (Accessible* acc = GetAccessibleByID(aID)) {
340 if (auto* textAcc = acc->AsHyperTextBase()) {
341 int32_t startSel, endSel;
342 GetSelectionOrCaret(textAcc, &startSel, &endSel);
343 if (startSel != endSel) {
344 textAcc->DeleteText(startSel, endSel);
346 textAcc->PasteText(startSel);
351 RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
352 Accessible* aAccessible) {
353 MOZ_ASSERT(NS_IsMainThread());
354 if (LocalAccessible* localAcc = aAccessible->AsLocal()) {
355 DocAccessible* docAcc = localAcc->Document();
356 // If the accessible is being shutdown from the doc's shutdown
357 // the doc accessible won't have a ref to a presshell anymore,
358 // but we should have a ref to the DOM document node, and the DOM doc
359 // has a ref to the presshell.
360 dom::Document* doc = docAcc ? docAcc->DocumentNode() : nullptr;
361 if (doc && doc->IsContentDocument()) {
362 // Only content accessibles should have an associated SessionAccessible.
363 return GetInstanceFor(doc->GetPresShell());
365 } else {
366 dom::CanonicalBrowsingContext* cbc =
367 static_cast<dom::BrowserParent*>(
368 aAccessible->AsRemote()->Document()->Manager())
369 ->GetBrowsingContext()
370 ->Top();
371 dom::BrowserParent* bp = cbc->GetBrowserParent();
372 if (!bp) {
373 bp = static_cast<dom::BrowserParent*>(
374 aAccessible->AsRemote()->Document()->Manager());
376 if (auto element = bp->GetOwnerElement()) {
377 if (auto doc = element->OwnerDoc()) {
378 if (nsPresContext* presContext = doc->GetPresContext()) {
379 return GetInstanceFor(presContext->PresShell());
381 } else {
382 MOZ_ASSERT_UNREACHABLE(
383 "Browser parent's element does not have owner doc.");
388 return nullptr;
391 RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
392 PresShell* aPresShell) {
393 MOZ_ASSERT(NS_IsMainThread());
394 if (!aPresShell) {
395 return nullptr;
398 nsViewManager* vm = aPresShell->GetViewManager();
399 if (!vm) {
400 return nullptr;
403 nsCOMPtr<nsIWidget> rootWidget = vm->GetRootWidget();
404 // `rootWidget` can be one of several types. Here we make sure it is an
405 // android nsWindow.
406 if (RefPtr<nsWindow> window = nsWindow::From(rootWidget)) {
407 return window->GetSessionAccessibility();
410 return nullptr;
413 void SessionAccessibility::SendAccessibilityFocusedEvent(
414 Accessible* aAccessible, bool aScrollIntoView) {
415 MOZ_ASSERT(NS_IsMainThread());
416 mSessionAccessibility->SendEvent(
417 java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
418 AccessibleWrap::GetVirtualViewID(aAccessible),
419 AccessibleWrap::AndroidClass(aAccessible), nullptr);
420 if (aScrollIntoView) {
421 aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
425 void SessionAccessibility::SendHoverEnterEvent(Accessible* aAccessible) {
426 MOZ_ASSERT(NS_IsMainThread());
427 mSessionAccessibility->SendEvent(
428 java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
429 AccessibleWrap::GetVirtualViewID(aAccessible),
430 AccessibleWrap::AndroidClass(aAccessible), nullptr);
433 void SessionAccessibility::SendFocusEvent(Accessible* aAccessible) {
434 MOZ_ASSERT(NS_IsMainThread());
435 // Suppress focus events from about:blank pages.
436 // This is important for tests.
437 if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
438 return;
441 mSessionAccessibility->SendEvent(
442 java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
443 AccessibleWrap::GetVirtualViewID(aAccessible),
444 AccessibleWrap::AndroidClass(aAccessible), nullptr);
447 void SessionAccessibility::SendScrollingEvent(Accessible* aAccessible,
448 int32_t aScrollX,
449 int32_t aScrollY,
450 int32_t aMaxScrollX,
451 int32_t aMaxScrollY) {
452 MOZ_ASSERT(NS_IsMainThread());
453 int32_t virtualViewId = AccessibleWrap::GetVirtualViewID(aAccessible);
455 if (virtualViewId != kNoID) {
456 // XXX: Support scrolling in subframes
457 return;
460 GECKOBUNDLE_START(eventInfo);
461 GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
462 GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
463 GECKOBUNDLE_PUT(eventInfo, "maxScrollX",
464 java::sdk::Integer::ValueOf(aMaxScrollX));
465 GECKOBUNDLE_PUT(eventInfo, "maxScrollY",
466 java::sdk::Integer::ValueOf(aMaxScrollY));
467 GECKOBUNDLE_FINISH(eventInfo);
469 mSessionAccessibility->SendEvent(
470 java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
471 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
472 SendWindowContentChangedEvent();
475 void SessionAccessibility::SendWindowContentChangedEvent() {
476 mSessionAccessibility->SendEvent(
477 java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED, kNoID,
478 java::SessionAccessibility::CLASSNAME_WEBVIEW, nullptr);
481 void SessionAccessibility::SendWindowStateChangedEvent(
482 Accessible* aAccessible) {
483 MOZ_ASSERT(NS_IsMainThread());
484 // Suppress window state changed events from about:blank pages.
485 // This is important for tests.
486 if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
487 return;
490 mSessionAccessibility->SendEvent(
491 java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
492 AccessibleWrap::GetVirtualViewID(aAccessible),
493 AccessibleWrap::AndroidClass(aAccessible), nullptr);
495 SendWindowContentChangedEvent();
498 void SessionAccessibility::SendTextSelectionChangedEvent(
499 Accessible* aAccessible, int32_t aCaretOffset) {
500 MOZ_ASSERT(NS_IsMainThread());
501 int32_t fromIndex = aCaretOffset;
502 int32_t startSel = -1;
503 int32_t endSel = -1;
504 bool hasSelection =
505 aAccessible->AsHyperTextBase()->SelectionBoundsAt(0, &startSel, &endSel);
507 if (hasSelection) {
508 fromIndex = startSel == aCaretOffset ? endSel : startSel;
511 nsAutoString text;
512 if (aAccessible->IsHyperText()) {
513 aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
514 } else if (aAccessible->IsText()) {
515 aAccessible->AppendTextTo(text, 0, -1);
518 GECKOBUNDLE_START(eventInfo);
519 GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
520 GECKOBUNDLE_PUT(eventInfo, "fromIndex",
521 java::sdk::Integer::ValueOf(fromIndex));
522 GECKOBUNDLE_PUT(eventInfo, "toIndex",
523 java::sdk::Integer::ValueOf(aCaretOffset));
524 GECKOBUNDLE_FINISH(eventInfo);
526 mSessionAccessibility->SendEvent(
527 java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
528 AccessibleWrap::GetVirtualViewID(aAccessible),
529 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
532 void SessionAccessibility::SendTextChangedEvent(Accessible* aAccessible,
533 const nsAString& aStr,
534 int32_t aStart, uint32_t aLen,
535 bool aIsInsert,
536 bool aFromUser) {
537 MOZ_ASSERT(NS_IsMainThread());
538 if (!aFromUser) {
539 // Only dispatch text change events from users, for now.
540 return;
543 nsAutoString text;
544 if (aAccessible->IsHyperText()) {
545 aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
546 } else if (aAccessible->IsText()) {
547 aAccessible->AppendTextTo(text, 0, -1);
549 nsAutoString beforeText(text);
550 if (aIsInsert) {
551 beforeText.Cut(aStart, aLen);
552 } else {
553 beforeText.Insert(aStr, aStart);
556 GECKOBUNDLE_START(eventInfo);
557 GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
558 GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
559 GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStart));
560 GECKOBUNDLE_PUT(eventInfo, "addedCount",
561 java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
562 GECKOBUNDLE_PUT(eventInfo, "removedCount",
563 java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
564 GECKOBUNDLE_FINISH(eventInfo);
566 mSessionAccessibility->SendEvent(
567 java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
568 AccessibleWrap::GetVirtualViewID(aAccessible),
569 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
572 void SessionAccessibility::SendTextTraversedEvent(Accessible* aAccessible,
573 int32_t aStartOffset,
574 int32_t aEndOffset) {
575 MOZ_ASSERT(NS_IsMainThread());
576 nsAutoString text;
577 if (aAccessible->IsHyperText()) {
578 aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text);
579 } else if (aAccessible->IsText()) {
580 aAccessible->AppendTextTo(text, 0, -1);
583 GECKOBUNDLE_START(eventInfo);
584 GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
585 GECKOBUNDLE_PUT(eventInfo, "fromIndex",
586 java::sdk::Integer::ValueOf(aStartOffset));
587 GECKOBUNDLE_PUT(eventInfo, "toIndex",
588 java::sdk::Integer::ValueOf(aEndOffset));
589 GECKOBUNDLE_FINISH(eventInfo);
591 mSessionAccessibility->SendEvent(
592 java::sdk::AccessibilityEvent::
593 TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
594 AccessibleWrap::GetVirtualViewID(aAccessible),
595 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
598 void SessionAccessibility::SendClickedEvent(Accessible* aAccessible,
599 uint32_t aFlags) {
600 GECKOBUNDLE_START(eventInfo);
601 GECKOBUNDLE_PUT(eventInfo, "flags", java::sdk::Integer::ValueOf(aFlags));
602 GECKOBUNDLE_FINISH(eventInfo);
604 mSessionAccessibility->SendEvent(
605 java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
606 AccessibleWrap::GetVirtualViewID(aAccessible),
607 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
610 void SessionAccessibility::SendSelectedEvent(Accessible* aAccessible,
611 bool aSelected) {
612 MOZ_ASSERT(NS_IsMainThread());
613 GECKOBUNDLE_START(eventInfo);
614 // Boolean::FALSE/TRUE gets clobbered by a macro, so ugh.
615 GECKOBUNDLE_PUT(eventInfo, "selected",
616 java::sdk::Integer::ValueOf(aSelected ? 1 : 0));
617 GECKOBUNDLE_FINISH(eventInfo);
619 mSessionAccessibility->SendEvent(
620 java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
621 AccessibleWrap::GetVirtualViewID(aAccessible),
622 AccessibleWrap::AndroidClass(aAccessible), eventInfo);
625 void SessionAccessibility::SendAnnouncementEvent(Accessible* aAccessible,
626 const nsAString& aAnnouncement,
627 uint16_t aPriority) {
628 MOZ_ASSERT(NS_IsMainThread());
629 GECKOBUNDLE_START(eventInfo);
630 GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(aAnnouncement));
631 GECKOBUNDLE_FINISH(eventInfo);
633 // Announcements should have the root as their source, so we ignore the
634 // accessible of the event.
635 mSessionAccessibility->SendEvent(
636 java::sdk::AccessibilityEvent::TYPE_ANNOUNCEMENT, kNoID,
637 java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo);
640 void SessionAccessibility::PopulateNodeInfo(
641 Accessible* aAccessible, mozilla::jni::Object::Param aNodeInfo) {
642 nsAutoString name;
643 aAccessible->Name(name);
644 nsAutoString textValue;
645 aAccessible->Value(textValue);
646 nsAutoString nodeID;
647 aAccessible->DOMNodeID(nodeID);
648 nsAutoString accDesc;
649 aAccessible->Description(accDesc);
650 uint64_t state = aAccessible->State();
651 LayoutDeviceIntRect bounds = aAccessible->Bounds();
652 uint8_t actionCount = aAccessible->ActionCount();
653 int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible);
654 Accessible* parent = virtualViewID != kNoID ? aAccessible->Parent() : nullptr;
655 int32_t parentID = parent ? AccessibleWrap::GetVirtualViewID(parent) : 0;
656 role role = aAccessible->Role();
657 if (role == roles::LINK && !(state & states::LINKED)) {
658 // A link without the linked state (<a> with no href) shouldn't be presented
659 // as a link.
660 role = roles::TEXT;
663 uint32_t flags = AccessibleWrap::GetFlags(role, state, actionCount);
664 int32_t className = AccessibleWrap::AndroidClass(aAccessible);
666 nsAutoString hint;
667 nsAutoString text;
668 nsAutoString description;
669 if (state & states::EDITABLE) {
670 // An editable field's name is populated in the hint.
671 hint.Assign(name);
672 text.Assign(textValue);
673 } else {
674 if (role == roles::LINK || role == roles::HEADING) {
675 description.Assign(name);
676 } else {
677 text.Assign(name);
681 if (!accDesc.IsEmpty()) {
682 if (!hint.IsEmpty()) {
683 // If this is an editable, the description is concatenated with a
684 // whitespace directly after the name.
685 hint.AppendLiteral(" ");
687 hint.Append(accDesc);
690 if ((state & states::REQUIRED) != 0) {
691 nsAutoString requiredString;
692 if (LocalizeString(u"stateRequired"_ns, requiredString)) {
693 if (!hint.IsEmpty()) {
694 // If the hint is non-empty, concatenate with a comma for a brief pause.
695 hint.AppendLiteral(", ");
697 hint.Append(requiredString);
701 RefPtr<AccAttributes> attributes = aAccessible->Attributes();
703 nsAutoString geckoRole;
704 nsAutoString roleDescription;
705 if (virtualViewID != kNoID) {
706 AccessibleWrap::GetRoleDescription(role, attributes, geckoRole,
707 roleDescription);
710 int32_t inputType = 0;
711 if (attributes) {
712 nsString inputTypeAttr;
713 attributes->GetAttribute(nsGkAtoms::textInputType, inputTypeAttr);
714 inputType = AccessibleWrap::GetInputType(inputTypeAttr);
717 auto childCount = aAccessible->ChildCount();
718 nsTArray<int32_t> children(childCount);
719 if (!nsAccUtils::MustPrune(aAccessible)) {
720 for (uint32_t i = 0; i < childCount; i++) {
721 auto child = aAccessible->ChildAt(i);
722 children.AppendElement(AccessibleWrap::GetVirtualViewID(child));
726 const int32_t boundsArray[4] = {bounds.x, bounds.y, bounds.x + bounds.width,
727 bounds.y + bounds.height};
729 mSessionAccessibility->PopulateNodeInfo(
730 aNodeInfo, virtualViewID, parentID, jni::IntArray::From(children), flags,
731 className, jni::IntArray::New(boundsArray, 4), jni::StringParam(text),
732 jni::StringParam(description), jni::StringParam(hint),
733 jni::StringParam(geckoRole), jni::StringParam(roleDescription),
734 jni::StringParam(nodeID), inputType);
736 if (aAccessible->HasNumericValue()) {
737 double curValue = aAccessible->CurValue();
738 double minValue = aAccessible->MinValue();
739 double maxValue = aAccessible->MaxValue();
740 double step = aAccessible->Step();
742 int32_t rangeType = 0; // integer
743 if (maxValue == 1 && minValue == 0) {
744 rangeType = 2; // percent
745 } else if (std::round(step) != step) {
746 rangeType = 1; // float;
749 mSessionAccessibility->PopulateNodeRangeInfo(
750 aNodeInfo, rangeType, static_cast<float>(minValue),
751 static_cast<float>(maxValue), static_cast<float>(curValue));
754 if (attributes) {
755 Maybe<int32_t> rowIndex =
756 attributes->GetAttribute<int32_t>(nsGkAtoms::posinset);
757 if (rowIndex) {
758 mSessionAccessibility->PopulateNodeCollectionItemInfo(
759 aNodeInfo, *rowIndex - 1, 1, 0, 1);
762 Maybe<int32_t> rowCount =
763 attributes->GetAttribute<int32_t>(nsGkAtoms::child_item_count);
764 if (rowCount) {
765 int32_t selectionMode = 0;
766 if (aAccessible->IsSelect()) {
767 selectionMode = (state & states::MULTISELECTABLE) ? 2 : 1;
769 mSessionAccessibility->PopulateNodeCollectionInfo(
770 aNodeInfo, *rowCount, 1, selectionMode,
771 attributes->HasAttribute(nsGkAtoms::tree));
776 Accessible* SessionAccessibility::GetAccessibleByID(int32_t aID) const {
777 return mIDToAccessibleMap.Get(aID);
780 #ifdef DEBUG
781 static bool IsDetachedDoc(Accessible* aAccessible) {
782 if (!aAccessible->IsRemote() || !aAccessible->AsRemote()->IsDoc()) {
783 return false;
786 return !aAccessible->Parent() ||
787 aAccessible->Parent()->FirstChild() != aAccessible;
789 #endif
791 SessionAccessibility::IDMappingEntry::IDMappingEntry(Accessible* aAccessible)
792 : mInternalID(0) {
793 *this = aAccessible;
796 SessionAccessibility::IDMappingEntry&
797 SessionAccessibility::IDMappingEntry::operator=(Accessible* aAccessible) {
798 mInternalID = aAccessible->ID();
799 MOZ_ASSERT(!(mInternalID & IS_REMOTE), "First bit is used in accessible ID!");
800 if (aAccessible->IsRemote()) {
801 mInternalID |= IS_REMOTE;
804 Accessible* docAcc = nsAccUtils::DocumentFor(aAccessible);
805 MOZ_ASSERT(docAcc);
806 if (docAcc) {
807 MOZ_ASSERT(docAcc->IsRemote() == aAccessible->IsRemote());
808 if (docAcc->IsRemote()) {
809 mDoc = docAcc->AsRemote()->AsDoc();
810 } else {
811 mDoc = docAcc->AsLocal();
815 return *this;
818 SessionAccessibility::IDMappingEntry::operator Accessible*() const {
819 if (mInternalID == 0) {
820 return static_cast<LocalAccessible*>(mDoc.get());
823 if (mInternalID == IS_REMOTE) {
824 return static_cast<DocAccessibleParent*>(mDoc.get());
827 if (mInternalID & IS_REMOTE) {
828 return static_cast<DocAccessibleParent*>(mDoc.get())
829 ->GetAccessible(mInternalID & ~IS_REMOTE);
832 Accessible* accessible =
833 static_cast<LocalAccessible*>(mDoc.get())
834 ->AsDoc()
835 ->GetAccessibleByUniqueID(reinterpret_cast<void*>(mInternalID));
836 // If the accessible is retrievable from the DocAccessible, it can't be
837 // defunct.
838 MOZ_ASSERT(!accessible->AsLocal()->IsDefunct());
840 return accessible;
843 void SessionAccessibility::RegisterAccessible(Accessible* aAccessible) {
844 if (IPCAccessibilityActive()) {
845 // Don't register accessible in content process.
846 return;
849 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
850 RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aAccessible);
851 if (!sessionAcc) {
852 return;
855 bool isTopLevel = false;
856 if (aAccessible->IsLocal() && aAccessible->IsDoc()) {
857 DocAccessibleWrap* doc =
858 static_cast<DocAccessibleWrap*>(aAccessible->AsLocal()->AsDoc());
859 isTopLevel = doc->IsTopLevelContentDoc();
860 } else if (aAccessible->IsRemote() && aAccessible->IsDoc()) {
861 isTopLevel = aAccessible->AsRemote()->AsDoc()->IsTopLevel();
864 int32_t virtualViewID = kNoID;
865 if (!isTopLevel) {
866 if (sessionAcc->mIDToAccessibleMap.IsEmpty()) {
867 // We expect there to already be at least one accessible
868 // registered (the top-level one). If it isn't we are
869 // probably in a shutdown process where it was already
870 // unregistered. So we don't register this accessible.
871 return;
873 // Don't use the special "unset" value (0).
874 while ((virtualViewID = sIDSet.GetID()) == kUnsetID) {
877 AccessibleWrap::SetVirtualViewID(aAccessible, virtualViewID);
879 Accessible* oldAcc = sessionAcc->mIDToAccessibleMap.Get(virtualViewID);
880 if (oldAcc) {
881 // About to overwrite mapping of registered accessible. This should
882 // only happen when the registered accessible is a detached document.
883 MOZ_ASSERT(IsDetachedDoc(oldAcc),
884 "ID already registered to non-detached document");
885 AccessibleWrap::SetVirtualViewID(oldAcc, kUnsetID);
888 sessionAcc->mIDToAccessibleMap.InsertOrUpdate(virtualViewID, aAccessible);
891 void SessionAccessibility::UnregisterAccessible(Accessible* aAccessible) {
892 if (IPCAccessibilityActive()) {
893 // Don't unregister accessible in content process.
894 return;
897 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
898 int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible);
899 if (virtualViewID == kUnsetID) {
900 return;
903 RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aAccessible);
904 if (sessionAcc) {
905 Accessible* registeredAcc =
906 sessionAcc->mIDToAccessibleMap.Get(virtualViewID);
907 if (registeredAcc != aAccessible) {
908 // Attempting to unregister an accessible that is not mapped to
909 // its virtual view ID. This probably means it is a detached document
910 // and a more recent document overwrote its '-1' mapping.
911 // We set its own virtual view ID to `kUnsetID` and return early.
912 MOZ_ASSERT(!registeredAcc || IsDetachedDoc(aAccessible),
913 "Accessible is detached document");
914 AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID);
915 return;
918 MOZ_ASSERT(registeredAcc, "Unregistering unregistered accessible");
919 MOZ_ASSERT(registeredAcc == aAccessible, "Unregistering wrong accessible");
920 sessionAcc->mIDToAccessibleMap.Remove(virtualViewID);
923 if (virtualViewID > kNoID) {
924 sIDSet.ReleaseID(virtualViewID);
927 AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID);
930 void SessionAccessibility::UnregisterAll(PresShell* aPresShell) {
931 if (IPCAccessibilityActive()) {
932 // Don't unregister accessible in content process.
933 return;
936 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
937 RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aPresShell);
938 if (sessionAcc) {
939 sessionAcc->mIDToAccessibleMap.Clear();