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"
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"
37 # include <android/log.h>
38 # define AALOG(args...) \
39 __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
41 # define AALOG(args...) \
46 using namespace mozilla::a11y
;
48 // IDs should be a positive 32bit integer.
52 : public mozilla::java::SessionAccessibility::Settings::Natives
<Settings
> {
54 static void ToggleNativeAccessibility(bool aEnable
) {
56 GetOrCreateAccService();
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",
76 sa
= java::SessionAccessibility::NativeProvider::GlobalRef(
77 mSessionAccessibility
),
78 runnable
= RefPtr
<Runnable
>(aRunnable
)] {
79 sa
->SetAttached(aAttached
);
87 void SessionAccessibility::Init() {
88 java::SessionAccessibility::NativeProvider::Natives
<
89 SessionAccessibility
>::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()) {
102 nsAppShell::SyncRunEvent(
103 [this, self
, aID
, aNodeInfo
= jni::Object::GlobalRef(aNodeInfo
)] {
104 if (Accessible
* acc
= GetAccessibleByID(aID
)) {
105 PopulateNodeInfo(acc
, aNodeInfo
);
107 AALOG("oops, nothing for %d", aID
);
111 PopulateNodeInfo(acc
, aNodeInfo
);
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()) {
126 nsAppShell::SyncRunEvent([this, self
, aID
, &classNameEnum
] {
127 if (Accessible
* acc
= GetAccessibleByID(aID
)) {
128 classNameEnum
= AccessibleWrap::AndroidClass(acc
);
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
)) {
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);
178 AccessibleWrap::DoPivot(acc
, aGranularity
, aForward
, aInclusive
);
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);
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
);
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
,
234 MOZ_ASSERT(NS_IsMainThread());
235 if (!(aAccessible
->State() & states::EDITABLE
)) {
239 HyperTextAccessibleBase
* editable
= aAccessible
->AsHyperTextBase();
240 MOZ_ASSERT(editable
);
245 int32_t newOffset
= aForward
? aEndOffset
: aStartOffset
;
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
);
255 editable
->SetCaretOffset(newOffset
);
259 bool SessionAccessibility::NavigateText(int32_t aID
, int32_t aGranularity
,
260 int32_t aStartOffset
,
261 int32_t aEndOffset
, bool aForward
,
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
);
276 SendTextTraversedEvent(_acc
, result
->first
, result
->second
);
277 AdjustCaretToTextNavigation(_acc
, result
->first
, result
->second
,
284 auto result
= AccessibleWrap::NavigateText(
285 acc
, aGranularity
, aStartOffset
, aEndOffset
, aForward
, aSelect
);
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
,
304 void SessionAccessibility::SetSelection(int32_t aID
, int32_t aStart
,
306 if (Accessible
* acc
= GetAccessibleByID(aID
)) {
307 if (auto* textAcc
= acc
->AsHyperTextBase()) {
308 if (aStart
== aEnd
) {
309 textAcc
->SetCaretOffset(aStart
);
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());
366 dom::CanonicalBrowsingContext
* cbc
=
367 static_cast<dom::BrowserParent
*>(
368 aAccessible
->AsRemote()->Document()->Manager())
369 ->GetBrowsingContext()
371 dom::BrowserParent
* bp
= cbc
->GetBrowserParent();
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());
382 MOZ_ASSERT_UNREACHABLE(
383 "Browser parent's element does not have owner doc.");
391 RefPtr
<SessionAccessibility
> SessionAccessibility::GetInstanceFor(
392 PresShell
* aPresShell
) {
393 MOZ_ASSERT(NS_IsMainThread());
398 nsViewManager
* vm
= aPresShell
->GetViewManager();
403 nsCOMPtr
<nsIWidget
> rootWidget
= vm
->GetRootWidget();
404 // `rootWidget` can be one of several types. Here we make sure it is an
406 if (RefPtr
<nsWindow
> window
= nsWindow::From(rootWidget
)) {
407 return window
->GetSessionAccessibility();
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) {
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
,
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
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) {
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;
505 aAccessible
->AsHyperTextBase()->SelectionBoundsAt(0, &startSel
, &endSel
);
508 fromIndex
= startSel
== aCaretOffset
? endSel
: startSel
;
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
,
537 MOZ_ASSERT(NS_IsMainThread());
539 // Only dispatch text change events from users, for now.
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
);
551 beforeText
.Cut(aStart
, aLen
);
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());
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
,
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
,
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
) {
643 aAccessible
->Name(name
);
644 nsAutoString textValue
;
645 aAccessible
->Value(textValue
);
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
663 uint32_t flags
= AccessibleWrap::GetFlags(role
, state
, actionCount
);
664 int32_t className
= AccessibleWrap::AndroidClass(aAccessible
);
668 nsAutoString description
;
669 if (state
& states::EDITABLE
) {
670 // An editable field's name is populated in the hint.
672 text
.Assign(textValue
);
674 if (role
== roles::LINK
|| role
== roles::HEADING
) {
675 description
.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
,
710 int32_t inputType
= 0;
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
));
755 Maybe
<int32_t> rowIndex
=
756 attributes
->GetAttribute
<int32_t>(nsGkAtoms::posinset
);
758 mSessionAccessibility
->PopulateNodeCollectionItemInfo(
759 aNodeInfo
, *rowIndex
- 1, 1, 0, 1);
762 Maybe
<int32_t> rowCount
=
763 attributes
->GetAttribute
<int32_t>(nsGkAtoms::child_item_count
);
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
);
781 static bool IsDetachedDoc(Accessible
* aAccessible
) {
782 if (!aAccessible
->IsRemote() || !aAccessible
->AsRemote()->IsDoc()) {
786 return !aAccessible
->Parent() ||
787 aAccessible
->Parent()->FirstChild() != aAccessible
;
791 SessionAccessibility::IDMappingEntry::IDMappingEntry(Accessible
* 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
);
807 MOZ_ASSERT(docAcc
->IsRemote() == aAccessible
->IsRemote());
808 if (docAcc
->IsRemote()) {
809 mDoc
= docAcc
->AsRemote()->AsDoc();
811 mDoc
= docAcc
->AsLocal();
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())
835 ->GetAccessibleByUniqueID(reinterpret_cast<void*>(mInternalID
));
836 // If the accessible is retrievable from the DocAccessible, it can't be
838 MOZ_ASSERT(!accessible
->AsLocal()->IsDefunct());
843 void SessionAccessibility::RegisterAccessible(Accessible
* aAccessible
) {
844 if (IPCAccessibilityActive()) {
845 // Don't register accessible in content process.
849 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
850 RefPtr
<SessionAccessibility
> sessionAcc
= GetInstanceFor(aAccessible
);
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
;
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.
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
);
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.
897 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
898 int32_t virtualViewID
= AccessibleWrap::GetVirtualViewID(aAccessible
);
899 if (virtualViewID
== kUnsetID
) {
903 RefPtr
<SessionAccessibility
> sessionAcc
= GetInstanceFor(aAccessible
);
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
);
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.
936 nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns();
937 RefPtr
<SessionAccessibility
> sessionAcc
= GetInstanceFor(aPresShell
);
939 sessionAcc
->mIDToAccessibleMap
.Clear();