/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SessionAccessibility.h" #include "LocalAccessible-inl.h" #include "AndroidUiThread.h" #include "AndroidBridge.h" #include "DocAccessibleParent.h" #include "IDSet.h" #include "nsThreadUtils.h" #include "AccAttributes.h" #include "AccessibilityEvent.h" #include "HyperTextAccessible.h" #include "HyperTextAccessible-inl.h" #include "JavaBuiltins.h" #include "RootAccessibleWrap.h" #include "nsAccessibilityService.h" #include "nsAccUtils.h" #include "nsViewManager.h" #include "mozilla/PresShell.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/a11y/Accessible.h" #include "mozilla/a11y/DocAccessibleParent.h" #include "mozilla/a11y/DocAccessiblePlatformExtParent.h" #include "mozilla/a11y/DocManager.h" #include "mozilla/jni/GeckoBundleUtils.h" #include "mozilla/jni/NativesInlines.h" #include "mozilla/widget/GeckoViewSupport.h" #include "mozilla/MouseEvents.h" #include "mozilla/dom/MouseEventBinding.h" #ifdef DEBUG # include # define AALOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args) #else # define AALOG(args...) \ do { \ } while (0) #endif #define FORWARD_ACTION_TO_ACCESSIBLE(funcname, ...) \ MOZ_ASSERT(NS_IsMainThread()); \ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); \ if (Accessible* acc = GetAccessibleByID(aID)) { \ if (acc->IsRemote()) { \ acc->AsRemote()->funcname(__VA_ARGS__); \ } else { \ static_cast(acc->AsLocal())->funcname(__VA_ARGS__); \ } \ } #define FORWARD_EXT_ACTION_TO_ACCESSIBLE(funcname, ...) \ MOZ_ASSERT(NS_IsMainThread()); \ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); \ if (Accessible* acc = GetAccessibleByID(aID)) { \ if (RemoteAccessible* remote = acc->AsRemote()) { \ Unused << remote->Document()->GetPlatformExtension()->Send##funcname( \ remote->ID(), ##__VA_ARGS__); \ } else { \ static_cast(acc->AsLocal())->funcname(__VA_ARGS__); \ } \ } using namespace mozilla::a11y; // IDs should be a positive 32bit integer. IDSet sIDSet(31UL); class Settings final : public mozilla::java::SessionAccessibility::Settings::Natives { public: static void ToggleNativeAccessibility(bool aEnable) { if (aEnable) { GetOrCreateAccService(); } else { MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI); } } }; SessionAccessibility::SessionAccessibility( jni::NativeWeakPtr aWindow, java::SessionAccessibility::NativeProvider::Param aSessionAccessibility) : mWindow(aWindow), mSessionAccessibility(aSessionAccessibility) { SetAttached(true, nullptr); } void SessionAccessibility::SetAttached(bool aAttached, already_AddRefed aRunnable) { if (RefPtr uiThread = GetAndroidUiThread()) { uiThread->Dispatch(NS_NewRunnableFunction( "SessionAccessibility::Attach", [aAttached, sa = java::SessionAccessibility::NativeProvider::GlobalRef( mSessionAccessibility), runnable = RefPtr(aRunnable)] { sa->SetAttached(aAttached); if (runnable) { runnable->Run(); } })); } } void SessionAccessibility::Init() { java::SessionAccessibility::NativeProvider::Natives< SessionAccessibility>::Init(); Settings::Init(); } void SessionAccessibility::GetNodeInfo(int32_t aID, mozilla::jni::Object::Param aNodeInfo) { MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); java::GeckoBundle::GlobalRef ret = nullptr; RefPtr self(this); if (Accessible* acc = GetAccessibleByID(aID)) { if (acc->IsLocal()) { mal.Unlock(); nsAppShell::SyncRunEvent( [this, self, aID, aNodeInfo = jni::Object::GlobalRef(aNodeInfo)] { if (Accessible* acc = GetAccessibleByID(aID)) { PopulateNodeInfo(acc, aNodeInfo); } else { AALOG("oops, nothing for %d", aID); } }); } else { PopulateNodeInfo(acc, aNodeInfo); } } else { AALOG("oops, nothing for %d", aID); } } int SessionAccessibility::GetNodeClassName(int32_t aID) { MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); ReleasableMonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); int32_t classNameEnum = java::SessionAccessibility::CLASSNAME_VIEW; RefPtr self(this); if (Accessible* acc = GetAccessibleByID(aID)) { if (acc->IsLocal()) { mal.Unlock(); nsAppShell::SyncRunEvent([this, self, aID, &classNameEnum] { if (Accessible* acc = GetAccessibleByID(aID)) { classNameEnum = AccessibleWrap::AndroidClass(acc); } }); } else { classNameEnum = AccessibleWrap::AndroidClass(acc); } } return classNameEnum; } void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) { if (Accessible* acc = GetAccessibleByID(aID)) { if (acc->IsRemote()) { acc->AsRemote()->ReplaceText(PromiseFlatString(aText->ToString())); } else if (acc->AsLocal()->IsHyperText()) { acc->AsLocal()->AsHyperText()->ReplaceText(aText->ToString()); } } } void SessionAccessibility::Click(int32_t aID) { FORWARD_ACTION_TO_ACCESSIBLE(DoAction, 0); } bool SessionAccessibility::Pivot(int32_t aID, int32_t aGranularity, bool aForward, bool aInclusive) { MOZ_ASSERT(AndroidBridge::IsJavaUiThread()); MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); RefPtr self(this); if (Accessible* acc = GetAccessibleByID(aID)) { if (acc->IsLocal()) { nsAppShell::PostEvent( [this, self, aID, aGranularity, aForward, aInclusive] { MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); if (Accessible* _acc = GetAccessibleByID(aID)) { MOZ_ASSERT(_acc && _acc->IsLocal()); if (LocalAccessible* localAcc = _acc->AsLocal()) { static_cast(localAcc)->PivotTo( aGranularity, aForward, aInclusive); } } }); return true; } Accessible* result = AccessibleWrap::DoPivot(acc, aGranularity, aForward, aInclusive); if (result) { int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(result); nsAppShell::PostEvent([this, self, virtualViewID] { MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); if (Accessible* acc = GetAccessibleByID(virtualViewID)) { SendAccessibilityFocusedEvent(acc); } }); return true; } } return false; } void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) { auto gvAccessor(mWindow.Access()); if (gvAccessor) { if (nsWindow* gkWindow = gvAccessor->GetNsWindow()) { WidgetMouseEvent hittest(true, eMouseExploreByTouch, gkWindow, WidgetMouseEvent::eReal); hittest.mRefPoint = LayoutDeviceIntPoint::Floor(aX, aY); hittest.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH; hittest.mFlags.mOnlyChromeDispatch = true; gkWindow->DispatchInputEvent(&hittest); } } } void SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity, int32_t aStartOffset, int32_t aEndOffset, bool aForward, bool aSelect) { FORWARD_EXT_ACTION_TO_ACCESSIBLE(NavigateText, aGranularity, aStartOffset, aEndOffset, aForward, aSelect); } void SessionAccessibility::SetSelection(int32_t aID, int32_t aStart, int32_t aEnd) { FORWARD_EXT_ACTION_TO_ACCESSIBLE(SetSelection, aStart, aEnd); } void SessionAccessibility::Cut(int32_t aID) { FORWARD_EXT_ACTION_TO_ACCESSIBLE(Cut); } void SessionAccessibility::Copy(int32_t aID) { FORWARD_EXT_ACTION_TO_ACCESSIBLE(Copy); } void SessionAccessibility::Paste(int32_t aID) { FORWARD_EXT_ACTION_TO_ACCESSIBLE(Paste); } #undef FORWARD_ACTION_TO_ACCESSIBLE #undef FORWARD_EXT_ACTION_TO_ACCESSIBLE RefPtr SessionAccessibility::GetInstanceFor( Accessible* aAccessible) { MOZ_ASSERT(NS_IsMainThread()); if (LocalAccessible* localAcc = aAccessible->AsLocal()) { DocAccessible* docAcc = localAcc->Document(); // If the accessible is being shutdown from the doc's shutdown // the doc accessible won't have a ref to a presshell anymore, // but we should have a ref to the DOM document node, and the DOM doc // has a ref to the presshell. dom::Document* doc = docAcc ? docAcc->DocumentNode() : nullptr; if (doc && doc->IsContentDocument()) { // Only content accessibles should have an associated SessionAccessible. return GetInstanceFor(doc->GetPresShell()); } } else { DocAccessibleParent* remoteDoc = aAccessible->AsRemote()->Document(); if (remoteDoc->mSessionAccessibility) { return remoteDoc->mSessionAccessibility; } dom::CanonicalBrowsingContext* cbc = static_cast(remoteDoc->Manager()) ->GetBrowsingContext() ->Top(); dom::BrowserParent* bp = cbc->GetBrowserParent(); if (!bp) { bp = static_cast( aAccessible->AsRemote()->Document()->Manager()); } if (auto element = bp->GetOwnerElement()) { if (auto doc = element->OwnerDoc()) { if (nsPresContext* presContext = doc->GetPresContext()) { RefPtr sessionAcc = GetInstanceFor(presContext->PresShell()); remoteDoc->mSessionAccessibility = sessionAcc; return sessionAcc; } } else { MOZ_ASSERT_UNREACHABLE( "Browser parent's element does not have owner doc."); } } } return nullptr; } RefPtr SessionAccessibility::GetInstanceFor( PresShell* aPresShell) { MOZ_ASSERT(NS_IsMainThread()); if (!aPresShell) { return nullptr; } nsViewManager* vm = aPresShell->GetViewManager(); if (!vm) { return nullptr; } nsCOMPtr rootWidget = vm->GetRootWidget(); // `rootWidget` can be one of several types. Here we make sure it is an // android nsWindow. if (RefPtr window = nsWindow::From(rootWidget)) { return window->GetSessionAccessibility(); } return nullptr; } void SessionAccessibility::SendAccessibilityFocusedEvent( Accessible* aAccessible) { MOZ_ASSERT(NS_IsMainThread()); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), nullptr); aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE); } void SessionAccessibility::SendHoverEnterEvent(Accessible* aAccessible) { MOZ_ASSERT(NS_IsMainThread()); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), nullptr); } void SessionAccessibility::SendFocusEvent(Accessible* aAccessible) { MOZ_ASSERT(NS_IsMainThread()); // Suppress focus events from about:blank pages. // This is important for tests. if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) { return; } mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), nullptr); } void SessionAccessibility::SendScrollingEvent(Accessible* aAccessible, int32_t aScrollX, int32_t aScrollY, int32_t aMaxScrollX, int32_t aMaxScrollY) { MOZ_ASSERT(NS_IsMainThread()); int32_t virtualViewId = AccessibleWrap::GetVirtualViewID(aAccessible); if (virtualViewId != kNoID) { // XXX: Support scrolling in subframes return; } GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX)); GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY)); GECKOBUNDLE_PUT(eventInfo, "maxScrollX", java::sdk::Integer::ValueOf(aMaxScrollX)); GECKOBUNDLE_PUT(eventInfo, "maxScrollY", java::sdk::Integer::ValueOf(aMaxScrollY)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId, AccessibleWrap::AndroidClass(aAccessible), eventInfo); SendWindowContentChangedEvent(); } void SessionAccessibility::SendWindowContentChangedEvent() { mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED, kNoID, java::SessionAccessibility::CLASSNAME_WEBVIEW, nullptr); } void SessionAccessibility::SendWindowStateChangedEvent( Accessible* aAccessible) { MOZ_ASSERT(NS_IsMainThread()); // Suppress window state changed events from about:blank pages. // This is important for tests. if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) { return; } mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), nullptr); SendWindowContentChangedEvent(); } void SessionAccessibility::SendTextSelectionChangedEvent( Accessible* aAccessible, int32_t aCaretOffset) { MOZ_ASSERT(NS_IsMainThread()); int32_t fromIndex = aCaretOffset; int32_t startSel = -1; int32_t endSel = -1; bool hasSelection = aAccessible->AsHyperTextBase()->SelectionBoundsAt(0, &startSel, &endSel); if (hasSelection) { fromIndex = startSel == aCaretOffset ? endSel : startSel; } nsAutoString text; if (aAccessible->IsHyperText()) { aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text); } else if (aAccessible->IsText()) { aAccessible->AppendTextTo(text, 0, -1); } GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text)); GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(fromIndex)); GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aCaretOffset)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), eventInfo); } void SessionAccessibility::SendTextChangedEvent(Accessible* aAccessible, const nsAString& aStr, int32_t aStart, uint32_t aLen, bool aIsInsert, bool aFromUser) { MOZ_ASSERT(NS_IsMainThread()); if (!aFromUser) { // Only dispatch text change events from users, for now. return; } nsAutoString text; if (aAccessible->IsHyperText()) { aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text); } else if (aAccessible->IsText()) { aAccessible->AppendTextTo(text, 0, -1); } nsAutoString beforeText(text); if (aIsInsert) { beforeText.Cut(aStart, aLen); } else { beforeText.Insert(aStr, aStart); } GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text)); GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText)); GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStart)); GECKOBUNDLE_PUT(eventInfo, "addedCount", java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0)); GECKOBUNDLE_PUT(eventInfo, "removedCount", java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), eventInfo); } void SessionAccessibility::SendTextTraversedEvent(Accessible* aAccessible, int32_t aStartOffset, int32_t aEndOffset) { MOZ_ASSERT(NS_IsMainThread()); nsAutoString text; if (aAccessible->IsHyperText()) { aAccessible->AsHyperTextBase()->TextSubstring(0, -1, text); } else if (aAccessible->IsText()) { aAccessible->AppendTextTo(text, 0, -1); } GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text)); GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStartOffset)); GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aEndOffset)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent:: TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), eventInfo); } void SessionAccessibility::SendClickedEvent(Accessible* aAccessible, uint32_t aFlags) { GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "flags", java::sdk::Integer::ValueOf(aFlags)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), eventInfo); } void SessionAccessibility::SendSelectedEvent(Accessible* aAccessible, bool aSelected) { MOZ_ASSERT(NS_IsMainThread()); GECKOBUNDLE_START(eventInfo); // Boolean::FALSE/TRUE gets clobbered by a macro, so ugh. GECKOBUNDLE_PUT(eventInfo, "selected", java::sdk::Integer::ValueOf(aSelected ? 1 : 0)); GECKOBUNDLE_FINISH(eventInfo); mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED, AccessibleWrap::GetVirtualViewID(aAccessible), AccessibleWrap::AndroidClass(aAccessible), eventInfo); } void SessionAccessibility::SendAnnouncementEvent(Accessible* aAccessible, const nsAString& aAnnouncement, uint16_t aPriority) { MOZ_ASSERT(NS_IsMainThread()); GECKOBUNDLE_START(eventInfo); GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(aAnnouncement)); GECKOBUNDLE_FINISH(eventInfo); // Announcements should have the root as their source, so we ignore the // accessible of the event. mSessionAccessibility->SendEvent( java::sdk::AccessibilityEvent::TYPE_ANNOUNCEMENT, kNoID, java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo); } void SessionAccessibility::PopulateNodeInfo( Accessible* aAccessible, mozilla::jni::Object::Param aNodeInfo) { nsAutoString name; aAccessible->Name(name); nsAutoString textValue; aAccessible->Value(textValue); nsAutoString nodeID; aAccessible->DOMNodeID(nodeID); nsAutoString accDesc; aAccessible->Description(accDesc); uint64_t state = aAccessible->State(); LayoutDeviceIntRect bounds = aAccessible->Bounds(); uint8_t actionCount = aAccessible->ActionCount(); int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible); Accessible* parent = virtualViewID != kNoID ? aAccessible->Parent() : nullptr; int32_t parentID = parent ? AccessibleWrap::GetVirtualViewID(parent) : 0; role role = aAccessible->Role(); if (role == roles::LINK && !(state & states::LINKED)) { // A link without the linked state ( with no href) shouldn't be presented // as a link. role = roles::TEXT; } uint32_t flags = AccessibleWrap::GetFlags(role, state, actionCount); int32_t className = AccessibleWrap::AndroidClass(aAccessible); nsAutoString hint; nsAutoString text; nsAutoString description; if (state & states::EDITABLE) { // An editable field's name is populated in the hint. hint.Assign(name); text.Assign(textValue); } else { if (role == roles::LINK || role == roles::HEADING) { description.Assign(name); } else { text.Assign(name); } } if (!accDesc.IsEmpty()) { if (!hint.IsEmpty()) { // If this is an editable, the description is concatenated with a // whitespace directly after the name. hint.AppendLiteral(" "); } hint.Append(accDesc); } if ((state & states::REQUIRED) != 0) { nsAutoString requiredString; if (LocalizeString(u"stateRequired"_ns, requiredString)) { if (!hint.IsEmpty()) { // If the hint is non-empty, concatenate with a comma for a brief pause. hint.AppendLiteral(", "); } hint.Append(requiredString); } } RefPtr attributes = aAccessible->Attributes(); nsAutoString geckoRole; nsAutoString roleDescription; if (virtualViewID != kNoID) { AccessibleWrap::GetRoleDescription(role, attributes, geckoRole, roleDescription); } int32_t inputType = 0; if (attributes) { nsString inputTypeAttr; attributes->GetAttribute(nsGkAtoms::textInputType, inputTypeAttr); inputType = AccessibleWrap::GetInputType(inputTypeAttr); } auto childCount = aAccessible->ChildCount(); nsTArray children(childCount); if (!nsAccUtils::MustPrune(aAccessible)) { for (uint32_t i = 0; i < childCount; i++) { auto child = aAccessible->ChildAt(i); children.AppendElement(AccessibleWrap::GetVirtualViewID(child)); } } const int32_t boundsArray[4] = {bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height}; mSessionAccessibility->PopulateNodeInfo( aNodeInfo, virtualViewID, parentID, jni::IntArray::From(children), flags, className, jni::IntArray::New(boundsArray, 4), jni::StringParam(text), jni::StringParam(description), jni::StringParam(hint), jni::StringParam(geckoRole), jni::StringParam(roleDescription), jni::StringParam(nodeID), inputType); if (aAccessible->HasNumericValue()) { double curValue = aAccessible->CurValue(); double minValue = aAccessible->MinValue(); double maxValue = aAccessible->MaxValue(); double step = aAccessible->Step(); int32_t rangeType = 0; // integer if (maxValue == 1 && minValue == 0) { rangeType = 2; // percent } else if (std::round(step) != step) { rangeType = 1; // float; } mSessionAccessibility->PopulateNodeRangeInfo( aNodeInfo, rangeType, static_cast(minValue), static_cast(maxValue), static_cast(curValue)); } if (attributes) { Maybe rowIndex = attributes->GetAttribute(nsGkAtoms::posinset); if (rowIndex) { mSessionAccessibility->PopulateNodeCollectionItemInfo( aNodeInfo, *rowIndex - 1, 1, 0, 1); } Maybe rowCount = attributes->GetAttribute(nsGkAtoms::child_item_count); if (rowCount) { int32_t selectionMode = 0; if (aAccessible->IsSelect()) { selectionMode = (state & states::MULTISELECTABLE) ? 2 : 1; } mSessionAccessibility->PopulateNodeCollectionInfo( aNodeInfo, *rowCount, 1, selectionMode, attributes->HasAttribute(nsGkAtoms::tree)); } } } Accessible* SessionAccessibility::GetAccessibleByID(int32_t aID) const { Accessible* accessible = mIDToAccessibleMap.Get(aID); if (accessible && accessible->IsLocal() && accessible->AsLocal()->IsDefunct()) { MOZ_ASSERT_UNREACHABLE("Registered accessible is defunct!"); return nullptr; } return accessible; } #ifdef DEBUG static bool IsDetachedDoc(Accessible* aAccessible) { if (!aAccessible->IsRemote() || !aAccessible->AsRemote()->IsDoc()) { return false; } return !aAccessible->Parent() || aAccessible->Parent()->FirstChild() != aAccessible; } #endif void SessionAccessibility::RegisterAccessible(Accessible* aAccessible) { if (IPCAccessibilityActive()) { // Don't register accessible in content process. return; } nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns(); RefPtr sessionAcc = GetInstanceFor(aAccessible); if (!sessionAcc) { return; } bool isTopLevel = false; if (aAccessible->IsLocal() && aAccessible->IsDoc()) { DocAccessibleWrap* doc = static_cast(aAccessible->AsLocal()->AsDoc()); isTopLevel = doc->IsTopLevelContentDoc(); } else if (aAccessible->IsRemote() && aAccessible->IsDoc()) { isTopLevel = aAccessible->AsRemote()->AsDoc()->IsTopLevel(); } int32_t virtualViewID = kNoID; if (!isTopLevel) { if (sessionAcc->mIDToAccessibleMap.IsEmpty()) { // We expect there to already be at least one accessible // registered (the top-level one). If it isn't we are // probably in a shutdown process where it was already // unregistered. So we don't register this accessible. return; } // Don't use the special "unset" value (0). while ((virtualViewID = sIDSet.GetID()) == kUnsetID) { } } AccessibleWrap::SetVirtualViewID(aAccessible, virtualViewID); Accessible* oldAcc = sessionAcc->mIDToAccessibleMap.Get(virtualViewID); if (oldAcc) { // About to overwrite mapping of registered accessible. This should // only happen when the registered accessible is a detached document. MOZ_ASSERT(IsDetachedDoc(oldAcc), "ID already registered to non-detached document"); AccessibleWrap::SetVirtualViewID(oldAcc, kUnsetID); } sessionAcc->mIDToAccessibleMap.InsertOrUpdate(virtualViewID, aAccessible); } void SessionAccessibility::UnregisterAccessible(Accessible* aAccessible) { if (IPCAccessibilityActive()) { // Don't unregister accessible in content process. return; } nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns(); int32_t virtualViewID = AccessibleWrap::GetVirtualViewID(aAccessible); if (virtualViewID == kUnsetID) { return; } RefPtr sessionAcc = GetInstanceFor(aAccessible); MOZ_ASSERT(sessionAcc, "Need SessionAccessibility to unregister Accessible!"); if (sessionAcc) { Accessible* registeredAcc = sessionAcc->mIDToAccessibleMap.Get(virtualViewID); if (registeredAcc != aAccessible) { // Attempting to unregister an accessible that is not mapped to // its virtual view ID. This probably means it is a detached document // and a more recent document overwrote its '-1' mapping. // We set its own virtual view ID to `kUnsetID` and return early. MOZ_ASSERT(!registeredAcc || IsDetachedDoc(aAccessible), "Accessible is detached document"); AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID); return; } MOZ_ASSERT(registeredAcc, "Unregistering unregistered accessible"); MOZ_ASSERT(registeredAcc == aAccessible, "Unregistering wrong accessible"); sessionAcc->mIDToAccessibleMap.Remove(virtualViewID); } if (virtualViewID > kNoID) { sIDSet.ReleaseID(virtualViewID); } AccessibleWrap::SetVirtualViewID(aAccessible, kUnsetID); } void SessionAccessibility::UnregisterAll(PresShell* aPresShell) { if (IPCAccessibilityActive()) { // Don't unregister accessible in content process. return; } nsAccessibilityService::GetAndroidMonitor().AssertCurrentThreadOwns(); RefPtr sessionAcc = GetInstanceFor(aPresShell); if (sessionAcc) { sessionAcc->mIDToAccessibleMap.Clear(); } }