diff options
Diffstat (limited to '')
23 files changed, 3310 insertions, 0 deletions
diff --git a/accessible/android/ARIAGridAccessibleWrap.h b/accessible/android/ARIAGridAccessibleWrap.h new file mode 100644 index 0000000000..f6380f2b2e --- /dev/null +++ b/accessible/android/ARIAGridAccessibleWrap.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H +#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H + +#include "ARIAGridAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class ARIAGridAccessible ARIAGridAccessibleWrap; +typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/AccessibleWrap.cpp b/accessible/android/AccessibleWrap.cpp new file mode 100644 index 0000000000..dc82e85e56 --- /dev/null +++ b/accessible/android/AccessibleWrap.cpp @@ -0,0 +1,942 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "AccessibleWrap.h" + +#include "JavaBuiltins.h" +#include "Accessible-inl.h" +#include "HyperTextAccessible-inl.h" +#include "AccEvent.h" +#include "AndroidInputType.h" +#include "DocAccessibleWrap.h" +#include "IDSet.h" +#include "SessionAccessibility.h" +#include "TextLeafAccessible.h" +#include "TraversalRule.h" +#include "Pivot.h" +#include "Platform.h" +#include "nsAccessibilityService.h" +#include "nsEventShell.h" +#include "nsPersistentProperties.h" +#include "nsIAccessibleAnnouncementEvent.h" +#include "nsAccUtils.h" +#include "nsTextEquivUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "RootAccessible.h" + +#include "mozilla/a11y/PDocAccessibleChild.h" +#include "mozilla/jni/GeckoBundleUtils.h" + +// icu TRUE conflicting with java::sdk::Boolean::TRUE() +// https://searchfox.org/mozilla-central/rev/ce02064d8afc8673cef83c92896ee873bd35e7ae/intl/icu/source/common/unicode/umachine.h#265 +// https://searchfox.org/mozilla-central/source/__GENERATED__/widget/android/bindings/JavaBuiltins.h#78 +#ifdef TRUE +# undef TRUE +#endif + +using namespace mozilla::a11y; + +// IDs should be a positive 32bit integer. +IDSet sIDSet(31UL); + +//----------------------------------------------------- +// construction +//----------------------------------------------------- +AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : Accessible(aContent, aDoc) { + if (aDoc) { + mID = AcquireID(); + DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc); + doc->AddID(mID, this); + } +} + +//----------------------------------------------------- +// destruction +//----------------------------------------------------- +AccessibleWrap::~AccessibleWrap() {} + +nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { + auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible()); + NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE); + DocAccessibleWrap* doc = + static_cast<DocAccessibleWrap*>(accessible->Document()); + if (doc) { + switch (aEvent->GetEventType()) { + case nsIAccessibleEvent::EVENT_FOCUS: { + if (DocAccessibleWrap* topContentDoc = + doc->GetTopLevelContentDoc(accessible)) { + topContentDoc->CacheFocusPath(accessible); + } + break; + } + case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: { + AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent); + auto newPosition = + static_cast<AccessibleWrap*>(vcEvent->NewAccessible()); + if (newPosition) { + if (DocAccessibleWrap* topContentDoc = + doc->GetTopLevelContentDoc(accessible)) { + topContentDoc->CacheFocusPath(newPosition); + } + } + break; + } + case nsIAccessibleEvent::EVENT_REORDER: { + if (DocAccessibleWrap* topContentDoc = + doc->GetTopLevelContentDoc(accessible)) { + topContentDoc->CacheViewport(true); + } + break; + } + case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { + if (accessible != aEvent->Document() && !aEvent->IsFromUserInput()) { + AccCaretMoveEvent* caretEvent = downcast_accEvent(aEvent); + HyperTextAccessible* ht = AsHyperText(); + // Pivot to the caret's position if it has an expanded selection. + // This is used mostly for find in page. + if ((ht && ht->SelectionCount())) { + DOMPoint point = + AsHyperText()->OffsetToDOMPoint(caretEvent->GetCaretOffset()); + if (Accessible* newPos = + doc->GetAccessibleOrContainer(point.node)) { + static_cast<AccessibleWrap*>(newPos)->PivotTo( + java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, true, + true); + } + } + } + break; + } + case nsIAccessibleEvent::EVENT_SCROLLING_START: { + accessible->PivotTo( + java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, true, true); + break; + } + default: + break; + } + } + + nsresult rv = Accessible::HandleAccEvent(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + accessible->HandleLiveRegionEvent(aEvent); + + if (IPCAccessibilityActive()) { + return NS_OK; + } + + // The accessible can become defunct if we have an xpcom event listener + // which decides it would be fun to change the DOM and flush layout. + if (accessible->IsDefunct() || !accessible->IsBoundToParent()) { + return NS_OK; + } + + if (doc) { + if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) { + return NS_OK; + } + } + + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(accessible); + if (!sessionAcc) { + return NS_OK; + } + + switch (aEvent->GetEventType()) { + case nsIAccessibleEvent::EVENT_FOCUS: + sessionAcc->SendFocusEvent(accessible); + break; + case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: { + AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent); + if (!vcEvent->IsFromUserInput()) { + break; + } + + RefPtr<AccessibleWrap> newPosition = + static_cast<AccessibleWrap*>(vcEvent->NewAccessible()); + if (sessionAcc && newPosition) { + if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) { + sessionAcc->SendHoverEnterEvent(newPosition); + } else if (vcEvent->BoundaryType() == nsIAccessiblePivot::NO_BOUNDARY) { + sessionAcc->SendAccessibilityFocusedEvent(newPosition); + } + + if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) { + sessionAcc->SendTextTraversedEvent( + newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset()); + } + } + break; + } + case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { + AccCaretMoveEvent* event = downcast_accEvent(aEvent); + sessionAcc->SendTextSelectionChangedEvent(accessible, + event->GetCaretOffset()); + break; + } + case nsIAccessibleEvent::EVENT_TEXT_INSERTED: + case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { + AccTextChangeEvent* event = downcast_accEvent(aEvent); + sessionAcc->SendTextChangedEvent( + accessible, event->ModifiedText(), event->GetStartOffset(), + event->GetLength(), event->IsTextInserted(), + event->IsFromUserInput()); + break; + } + case nsIAccessibleEvent::EVENT_STATE_CHANGE: { + AccStateChangeEvent* event = downcast_accEvent(aEvent); + auto state = event->GetState(); + if (state & states::CHECKED) { + sessionAcc->SendClickedEvent( + accessible, java::SessionAccessibility::FLAG_CHECKABLE | + (event->IsStateEnabled() + ? java::SessionAccessibility::FLAG_CHECKED + : 0)); + } + + if (state & states::EXPANDED) { + sessionAcc->SendClickedEvent( + accessible, java::SessionAccessibility::FLAG_EXPANDABLE | + (event->IsStateEnabled() + ? java::SessionAccessibility::FLAG_EXPANDED + : 0)); + } + + if (state & states::SELECTED) { + sessionAcc->SendSelectedEvent(accessible, event->IsStateEnabled()); + } + + if (state & states::BUSY) { + sessionAcc->SendWindowStateChangedEvent(accessible); + } + break; + } + case nsIAccessibleEvent::EVENT_SCROLLING: { + AccScrollingEvent* event = downcast_accEvent(aEvent); + sessionAcc->SendScrollingEvent(accessible, event->ScrollX(), + event->ScrollY(), event->MaxScrollX(), + event->MaxScrollY()); + break; + } + case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: { + AccAnnouncementEvent* event = downcast_accEvent(aEvent); + sessionAcc->SendAnnouncementEvent(accessible, event->Announcement(), + event->Priority()); + break; + } + default: + break; + } + + return NS_OK; +} + +void AccessibleWrap::Shutdown() { + if (mDoc) { + if (mID > 0) { + if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) { + doc->RemoveID(mID); + } + ReleaseID(mID); + mID = 0; + } + } + + Accessible::Shutdown(); +} + +bool AccessibleWrap::DoAction(uint8_t aIndex) const { + if (ActionCount()) { + return Accessible::DoAction(aIndex); + } + + if (mContent) { + // We still simulate a click on an accessible even if there is no + // known actions. For the sake of bad markup. + DoCommand(); + return true; + } + + return false; +} + +int32_t AccessibleWrap::AcquireID() { return sIDSet.GetID(); } + +void AccessibleWrap::ReleaseID(int32_t aID) { sIDSet.ReleaseID(aID); } + +void AccessibleWrap::SetTextContents(const nsAString& aText) { + if (IsHyperText()) { + AsHyperText()->ReplaceText(aText); + } +} + +void AccessibleWrap::GetTextContents(nsAString& aText) { + // For now it is a simple wrapper for getting entire range of TextSubstring. + // In the future this may be smarter and retrieve a flattened string. + if (IsHyperText()) { + AsHyperText()->TextSubstring(0, -1, aText); + } else if (IsTextLeaf()) { + aText = AsTextLeaf()->Text(); + } +} + +bool AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, + int32_t* aEndOffset) { + if (IsHyperText()) { + return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset); + } + + return false; +} + +void AccessibleWrap::PivotTo(int32_t aGranularity, bool aForward, + bool aInclusive) { + AccessibleOrProxy accOrProxyRoot = AccessibleOrProxy(RootAccessible()); + a11y::Pivot pivot(accOrProxyRoot); + TraversalRule rule(aGranularity); + AccessibleOrProxy accOrProxy = AccessibleOrProxy(this); + Accessible* result = + aForward ? pivot.Next(accOrProxy, rule, aInclusive).AsAccessible() + : pivot.Prev(accOrProxy, rule, aInclusive).AsAccessible(); + if (result && (result != this || aInclusive)) { + PivotMoveReason reason = aForward ? nsIAccessiblePivot::REASON_NEXT + : nsIAccessiblePivot::REASON_PREV; + RefPtr<AccEvent> event = new AccVCChangeEvent( + result->Document(), this, -1, -1, result, -1, -1, reason, + nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput); + nsEventShell::FireEvent(event); + } +} + +void AccessibleWrap::ExploreByTouch(float aX, float aY) { + a11y::Pivot pivot(RootAccessible()); + TraversalRule rule; + + Accessible* result = pivot.AtPoint(aX, aY, rule).AsAccessible(); + + if (result && result != this) { + RefPtr<AccEvent> event = + new AccVCChangeEvent(result->Document(), this, -1, -1, result, -1, -1, + nsIAccessiblePivot::REASON_POINT, + nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput); + nsEventShell::FireEvent(event); + } +} + +void AccessibleWrap::NavigateText(int32_t aGranularity, int32_t aStartOffset, + int32_t aEndOffset, bool aForward, + bool aSelect) { + a11y::Pivot pivot(RootAccessible()); + + HyperTextAccessible* editable = + (State() & states::EDITABLE) != 0 ? AsHyperText() : nullptr; + + int32_t start = aStartOffset, end = aEndOffset; + // If the accessible is an editable, set the virtual cursor position + // to its caret offset. Otherwise use the document's virtual cursor + // position as a starting offset. + if (editable) { + start = end = editable->CaretOffset(); + } + + uint16_t pivotGranularity = nsIAccessiblePivot::LINE_BOUNDARY; + switch (aGranularity) { + case 1: // MOVEMENT_GRANULARITY_CHARACTER + pivotGranularity = nsIAccessiblePivot::CHAR_BOUNDARY; + break; + case 2: // MOVEMENT_GRANULARITY_WORD + pivotGranularity = nsIAccessiblePivot::WORD_BOUNDARY; + break; + default: + break; + } + + int32_t newOffset; + Accessible* newAnchor = nullptr; + if (aForward) { + newAnchor = pivot.NextText(this, &start, &end, pivotGranularity); + newOffset = end; + } else { + newAnchor = pivot.PrevText(this, &start, &end, pivotGranularity); + newOffset = start; + } + + if (newAnchor && (start != aStartOffset || end != aEndOffset)) { + if (IsTextLeaf() && newAnchor == Parent()) { + // For paragraphs, divs, spans, etc., we put a11y focus on the text leaf + // node instead of the HyperTextAccessible. However, Pivot will always + // return a HyperTextAccessible. Android doesn't support text navigation + // landing on an accessible which is different to the originating + // accessible. Therefore, if we're still within the same text leaf, + // translate the offsets to the text leaf. + int32_t thisChild = IndexInParent(); + HyperTextAccessible* newHyper = newAnchor->AsHyperText(); + MOZ_ASSERT(newHyper); + int32_t startChild = newHyper->GetChildIndexAtOffset(start); + // We use end - 1 because the end offset is exclusive, so end itself + // might be associated with the next child. + int32_t endChild = newHyper->GetChildIndexAtOffset(end - 1); + if (startChild == thisChild && endChild == thisChild) { + // We've landed within the same text leaf. + newAnchor = this; + int32_t thisOffset = newHyper->GetChildOffset(thisChild); + start -= thisOffset; + end -= thisOffset; + } + } + RefPtr<AccEvent> event = new AccVCChangeEvent( + newAnchor->Document(), this, aStartOffset, aEndOffset, newAnchor, start, + end, nsIAccessiblePivot::REASON_NONE, pivotGranularity, eFromUserInput); + nsEventShell::FireEvent(event); + } + + // If we are in an editable, move the caret to the new virtual cursor + // offset. + if (editable) { + if (aSelect) { + int32_t anchor = editable->CaretOffset(); + if (editable->SelectionCount()) { + int32_t startSel, endSel; + GetSelectionOrCaret(&startSel, &endSel); + anchor = startSel == anchor ? endSel : startSel; + } + editable->SetSelectionBoundsAt(0, anchor, newOffset); + } else { + editable->SetCaretOffset(newOffset); + } + } +} + +void AccessibleWrap::SetSelection(int32_t aStart, int32_t aEnd) { + if (HyperTextAccessible* textAcc = AsHyperText()) { + if (aStart == aEnd) { + textAcc->SetCaretOffset(aStart); + } else { + textAcc->SetSelectionBoundsAt(0, aStart, aEnd); + } + } +} + +void AccessibleWrap::Cut() { + if ((State() & states::EDITABLE) == 0) { + return; + } + + if (HyperTextAccessible* textAcc = AsHyperText()) { + int32_t startSel, endSel; + GetSelectionOrCaret(&startSel, &endSel); + textAcc->CutText(startSel, endSel); + } +} + +void AccessibleWrap::Copy() { + if (HyperTextAccessible* textAcc = AsHyperText()) { + int32_t startSel, endSel; + GetSelectionOrCaret(&startSel, &endSel); + textAcc->CopyText(startSel, endSel); + } +} + +void AccessibleWrap::Paste() { + if ((State() & states::EDITABLE) == 0) { + return; + } + + if (IsHyperText()) { + RefPtr<HyperTextAccessible> textAcc = AsHyperText(); + int32_t startSel, endSel; + GetSelectionOrCaret(&startSel, &endSel); + if (startSel != endSel) { + textAcc->DeleteText(startSel, endSel); + } + textAcc->PasteText(startSel); + } +} + +void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset, + int32_t* aEndOffset) { + *aStartOffset = *aEndOffset = -1; + if (HyperTextAccessible* textAcc = AsHyperText()) { + if (!textAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) { + *aStartOffset = *aEndOffset = textAcc->CaretOffset(); + } + } +} + +uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState, + uint8_t aActionCount) { + uint32_t flags = 0; + if (aState & states::CHECKABLE) { + flags |= java::SessionAccessibility::FLAG_CHECKABLE; + } + + if (aState & states::CHECKED) { + flags |= java::SessionAccessibility::FLAG_CHECKED; + } + + if (aState & states::INVALID) { + flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID; + } + + if (aState & states::EDITABLE) { + flags |= java::SessionAccessibility::FLAG_EDITABLE; + } + + if (aActionCount && aRole != roles::TEXT_LEAF) { + flags |= java::SessionAccessibility::FLAG_CLICKABLE; + } + + if (aState & states::ENABLED) { + flags |= java::SessionAccessibility::FLAG_ENABLED; + } + + if (aState & states::FOCUSABLE) { + flags |= java::SessionAccessibility::FLAG_FOCUSABLE; + } + + if (aState & states::FOCUSED) { + flags |= java::SessionAccessibility::FLAG_FOCUSED; + } + + if (aState & states::MULTI_LINE) { + flags |= java::SessionAccessibility::FLAG_MULTI_LINE; + } + + if (aState & states::SELECTABLE) { + flags |= java::SessionAccessibility::FLAG_SELECTABLE; + } + + if (aState & states::SELECTED) { + flags |= java::SessionAccessibility::FLAG_SELECTED; + } + + if (aState & states::EXPANDABLE) { + flags |= java::SessionAccessibility::FLAG_EXPANDABLE; + } + + if (aState & states::EXPANDED) { + flags |= java::SessionAccessibility::FLAG_EXPANDED; + } + + if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) { + flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER; + } + + if (aRole == roles::PASSWORD_TEXT) { + flags |= java::SessionAccessibility::FLAG_PASSWORD; + } + + return flags; +} + +void AccessibleWrap::GetRoleDescription(role aRole, + nsIPersistentProperties* aAttributes, + nsAString& aGeckoRole, + nsAString& aRoleDescription) { + if (aRole == roles::HEADING && aAttributes) { + // The heading level is an attribute, so we need that. + AutoTArray<nsString, 1> formatString; + nsresult rv = aAttributes->GetStringProperty("level"_ns, + *formatString.AppendElement()); + if (NS_SUCCEEDED(rv) && + LocalizeString("headingLevel", aRoleDescription, formatString)) { + return; + } + } + + if ((aRole == roles::LANDMARK || aRole == roles::REGION) && aAttributes) { + nsAutoString xmlRoles; + if (NS_SUCCEEDED( + aAttributes->GetStringProperty("xml-roles"_ns, xmlRoles))) { + nsWhitespaceTokenizer tokenizer(xmlRoles); + while (tokenizer.hasMoreTokens()) { + if (LocalizeString(NS_ConvertUTF16toUTF8(tokenizer.nextToken()).get(), + aRoleDescription)) { + return; + } + } + } + } + + GetAccService()->GetStringRole(aRole, aGeckoRole); + LocalizeString(NS_ConvertUTF16toUTF8(aGeckoRole).get(), aRoleDescription); +} + +already_AddRefed<nsIPersistentProperties> +AccessibleWrap::AttributeArrayToProperties( + const nsTArray<Attribute>& aAttributes) { + RefPtr<nsPersistentProperties> props = new nsPersistentProperties(); + nsAutoString unused; + + for (size_t i = 0; i < aAttributes.Length(); i++) { + props->SetStringProperty(aAttributes.ElementAt(i).Name(), + aAttributes.ElementAt(i).Value(), unused); + } + + return props.forget(); +} + +int32_t AccessibleWrap::GetAndroidClass(role aRole) { +#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \ + ia2Role, androidClass, nameRule) \ + case roles::geckoRole: \ + return androidClass; + + switch (aRole) { +#include "RoleMap.h" + default: + return java::SessionAccessibility::CLASSNAME_VIEW; + } + +#undef ROLE +} + +int32_t AccessibleWrap::GetInputType(const nsString& aInputTypeAttr) { + if (aInputTypeAttr.EqualsIgnoreCase("email")) { + return java::sdk::InputType::TYPE_CLASS_TEXT | + java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + + if (aInputTypeAttr.EqualsIgnoreCase("number")) { + return java::sdk::InputType::TYPE_CLASS_NUMBER; + } + + if (aInputTypeAttr.EqualsIgnoreCase("password")) { + return java::sdk::InputType::TYPE_CLASS_TEXT | + java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD; + } + + if (aInputTypeAttr.EqualsIgnoreCase("tel")) { + return java::sdk::InputType::TYPE_CLASS_PHONE; + } + + if (aInputTypeAttr.EqualsIgnoreCase("text")) { + return java::sdk::InputType::TYPE_CLASS_TEXT | + java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; + } + + if (aInputTypeAttr.EqualsIgnoreCase("url")) { + return java::sdk::InputType::TYPE_CLASS_TEXT | + java::sdk::InputType::TYPE_TEXT_VARIATION_URI; + } + + return 0; +} + +void AccessibleWrap::WrapperDOMNodeID(nsString& aDOMNodeID) { + if (mContent) { + nsAtom* id = mContent->GetID(); + if (id) { + id->ToString(aDOMNodeID); + } + } +} + +bool AccessibleWrap::WrapperRangeInfo(double* aCurVal, double* aMinVal, + double* aMaxVal, double* aStep) { + if (HasNumericValue()) { + *aCurVal = CurValue(); + *aMinVal = MinValue(); + *aMaxVal = MaxValue(); + *aStep = Step(); + return true; + } + + return false; +} + +mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(bool aSmall) { + nsAutoString name; + Name(name); + nsAutoString textValue; + Value(textValue); + nsAutoString nodeID; + WrapperDOMNodeID(nodeID); + nsAutoString description; + Description(description); + + if (aSmall) { + return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID, + description); + } + + double curValue = UnspecifiedNaN<double>(); + double minValue = UnspecifiedNaN<double>(); + double maxValue = UnspecifiedNaN<double>(); + double step = UnspecifiedNaN<double>(); + WrapperRangeInfo(&curValue, &minValue, &maxValue, &step); + + nsCOMPtr<nsIPersistentProperties> attributes = Attributes(); + + return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID, + description, curValue, minValue, maxValue, step, attributes); +} + +mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle( + const uint64_t aState, const nsIntRect& aBounds, const uint8_t aActionCount, + const nsString& aName, const nsString& aTextValue, + const nsString& aDOMNodeID, const nsString& aDescription, + const double& aCurVal, const double& aMinVal, const double& aMaxVal, + const double& aStep, nsIPersistentProperties* aAttributes) { + if (!IsProxy() && IsDefunct()) { + return nullptr; + } + + GECKOBUNDLE_START(nodeInfo); + GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID())); + + AccessibleWrap* parent = WrapperParent(); + GECKOBUNDLE_PUT( + nodeInfo, "parentId", + java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0)); + + role role = WrapperRole(); + if (role == roles::LINK && !(aState & states::LINKED)) { + // A link without the linked state (<a> with no href) shouldn't be presented + // as a link. + role = roles::TEXT; + } + + uint32_t flags = GetFlags(role, aState, aActionCount); + GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags)); + GECKOBUNDLE_PUT(nodeInfo, "className", + java::sdk::Integer::ValueOf(AndroidClass())); + + nsAutoString hint; + if (aState & states::EDITABLE) { + // An editable field's name is populated in the hint. + hint.Assign(aName); + GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue)); + } else { + if (role == roles::LINK || role == roles::HEADING) { + GECKOBUNDLE_PUT(nodeInfo, "description", jni::StringParam(aName)); + } else { + GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName)); + } + } + + if (!aDescription.IsEmpty()) { + if (!hint.IsEmpty()) { + // If this is an editable, the description is concatenated with a + // whitespace directly after the name. + hint.AppendLiteral(" "); + } + hint.Append(aDescription); + } + + if ((aState & states::REQUIRED) != 0) { + nsAutoString requiredString; + if (LocalizeString("stateRequired", requiredString)) { + if (!hint.IsEmpty()) { + // If the hint is non-empty, concatenate with a comma for a brief pause. + hint.AppendLiteral(", "); + } + hint.Append(requiredString); + } + } + + if (!hint.IsEmpty()) { + GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(hint)); + } + + nsAutoString geckoRole; + nsAutoString roleDescription; + if (VirtualViewID() != kNoID) { + GetRoleDescription(role, aAttributes, geckoRole, roleDescription); + } + + GECKOBUNDLE_PUT(nodeInfo, "roleDescription", + jni::StringParam(roleDescription)); + GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole)); + + if (!aDOMNodeID.IsEmpty()) { + GECKOBUNDLE_PUT(nodeInfo, "viewIdResourceName", + jni::StringParam(aDOMNodeID)); + } + + const int32_t data[4] = {aBounds.x, aBounds.y, aBounds.x + aBounds.width, + aBounds.y + aBounds.height}; + GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4)); + + if (HasNumericValue()) { + GECKOBUNDLE_START(rangeInfo); + if (aMaxVal == 1 && aMinVal == 0) { + GECKOBUNDLE_PUT(rangeInfo, "type", + java::sdk::Integer::ValueOf(2)); // percent + } else if (std::round(aStep) != aStep) { + GECKOBUNDLE_PUT(rangeInfo, "type", + java::sdk::Integer::ValueOf(1)); // float + } else { + GECKOBUNDLE_PUT(rangeInfo, "type", + java::sdk::Integer::ValueOf(0)); // integer + } + + if (!IsNaN(aCurVal)) { + GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal)); + } + if (!IsNaN(aMinVal)) { + GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal)); + } + if (!IsNaN(aMaxVal)) { + GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal)); + } + + GECKOBUNDLE_FINISH(rangeInfo); + GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo); + } + + if (aAttributes) { + nsString inputTypeAttr; + nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, + inputTypeAttr); + int32_t inputType = GetInputType(inputTypeAttr); + if (inputType) { + GECKOBUNDLE_PUT(nodeInfo, "inputType", + java::sdk::Integer::ValueOf(inputType)); + } + + nsString posinset; + nsresult rv = aAttributes->GetStringProperty("posinset"_ns, posinset); + if (NS_SUCCEEDED(rv)) { + int32_t rowIndex; + if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) { + GECKOBUNDLE_START(collectionItemInfo); + GECKOBUNDLE_PUT(collectionItemInfo, "rowIndex", + java::sdk::Integer::ValueOf(rowIndex)); + GECKOBUNDLE_PUT(collectionItemInfo, "columnIndex", + java::sdk::Integer::ValueOf(0)); + GECKOBUNDLE_PUT(collectionItemInfo, "rowSpan", + java::sdk::Integer::ValueOf(1)); + GECKOBUNDLE_PUT(collectionItemInfo, "columnSpan", + java::sdk::Integer::ValueOf(1)); + GECKOBUNDLE_FINISH(collectionItemInfo); + + GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo); + } + } + + nsString colSize; + rv = aAttributes->GetStringProperty("child-item-count"_ns, colSize); + if (NS_SUCCEEDED(rv)) { + int32_t rowCount; + if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) { + GECKOBUNDLE_START(collectionInfo); + GECKOBUNDLE_PUT(collectionInfo, "rowCount", + java::sdk::Integer::ValueOf(rowCount)); + GECKOBUNDLE_PUT(collectionInfo, "columnCount", + java::sdk::Integer::ValueOf(1)); + + nsString unused; + rv = aAttributes->GetStringProperty("hierarchical"_ns, unused); + if (NS_SUCCEEDED(rv)) { + GECKOBUNDLE_PUT(collectionInfo, "isHierarchical", + java::sdk::Boolean::TRUE()); + } + + if (IsSelect()) { + int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1; + GECKOBUNDLE_PUT(collectionInfo, "selectionMode", + java::sdk::Integer::ValueOf(selectionMode)); + } + + GECKOBUNDLE_FINISH(collectionInfo); + GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo); + } + } + } + + bool mustPrune = + IsProxy() ? nsAccUtils::MustPrune(Proxy()) : nsAccUtils::MustPrune(this); + if (!mustPrune) { + auto childCount = ChildCount(); + nsTArray<int32_t> children(childCount); + for (uint32_t i = 0; i < childCount; i++) { + auto child = static_cast<AccessibleWrap*>(GetChildAt(i)); + children.AppendElement(child->VirtualViewID()); + } + + GECKOBUNDLE_PUT(nodeInfo, "children", + jni::IntArray::New(children.Elements(), children.Length())); + } + + GECKOBUNDLE_FINISH(nodeInfo); + + return nodeInfo; +} + +void AccessibleWrap::GetTextEquiv(nsString& aText) { + if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule)) { + // This is an accessible that normally doesn't get its name from its + // subtree, so we collect the text equivalent explicitly. + nsTextEquivUtils::GetTextEquivFromSubtree(this, aText); + } else { + Name(aText); + } +} + +bool AccessibleWrap::HandleLiveRegionEvent(AccEvent* aEvent) { + auto eventType = aEvent->GetEventType(); + if (eventType != nsIAccessibleEvent::EVENT_TEXT_INSERTED && + eventType != nsIAccessibleEvent::EVENT_NAME_CHANGE) { + // XXX: Right now only announce text inserted events. aria-relevant=removals + // is potentially on the chopping block[1]. We also don't support editable + // text because we currently can't descern the source of the change[2]. + // 1. https://github.com/w3c/aria/issues/712 + // 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189 + return false; + } + + if (aEvent->IsFromUserInput()) { + return false; + } + + nsCOMPtr<nsIPersistentProperties> attributes = Attributes(); + nsString live; + nsresult rv = attributes->GetStringProperty("container-live"_ns, live); + if (!NS_SUCCEEDED(rv)) { + return false; + } + + uint16_t priority = live.EqualsIgnoreCase("assertive") + ? nsIAccessibleAnnouncementEvent::ASSERTIVE + : nsIAccessibleAnnouncementEvent::POLITE; + + nsString atomic; + rv = attributes->GetStringProperty("container-atomic"_ns, atomic); + + Accessible* announcementTarget = this; + nsAutoString announcement; + if (atomic.EqualsIgnoreCase("true")) { + Accessible* atomicAncestor = nullptr; + for (Accessible* parent = announcementTarget; parent; + parent = parent->Parent()) { + dom::Element* element = parent->Elm(); + if (element && + element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic, + nsGkAtoms::_true, eCaseMatters)) { + atomicAncestor = parent; + break; + } + } + + if (atomicAncestor) { + announcementTarget = atomicAncestor; + static_cast<AccessibleWrap*>(atomicAncestor)->GetTextEquiv(announcement); + } + } else { + GetTextEquiv(announcement); + } + + announcement.CompressWhitespace(); + if (announcement.IsEmpty()) { + return false; + } + + announcementTarget->Announce(announcement, priority); + return true; +} diff --git a/accessible/android/AccessibleWrap.h b/accessible/android/AccessibleWrap.h new file mode 100644 index 0000000000..7e621177e4 --- /dev/null +++ b/accessible/android/AccessibleWrap.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_AccessibleWrap_h_ +#define mozilla_a11y_AccessibleWrap_h_ + +#include "Accessible.h" +#include "mozilla/a11y/ProxyAccessible.h" +#include "mozilla/java/GeckoBundleWrappers.h" +#include "mozilla/java/SessionAccessibilityWrappers.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace a11y { + +class AccessibleWrap : public Accessible { + public: + AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc); + virtual ~AccessibleWrap(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY // TODO: Mark this as MOZ_CAN_RUN_SCRIPT + virtual nsresult + HandleAccEvent(AccEvent* aEvent) override; + + virtual void Shutdown() override; + + virtual bool DoAction(uint8_t aIndex) const override; + + int32_t VirtualViewID() const { return mID; } + + virtual void SetTextContents(const nsAString& aText); + + virtual void GetTextContents(nsAString& aText); + + virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset); + + virtual void PivotTo(int32_t aGranularity, bool aForward, bool aInclusive); + + virtual void ExploreByTouch(float aX, float aY); + + virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset, + int32_t aEndOffset, bool aForward, bool aSelect); + + virtual void SetSelection(int32_t aStart, int32_t aEnd); + + virtual void Cut(); + + virtual void Copy(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void Paste(); + + mozilla::java::GeckoBundle::LocalRef ToBundle(bool aSmall = false); + + mozilla::java::GeckoBundle::LocalRef ToBundle( + const uint64_t aState, const nsIntRect& aBounds, + const uint8_t aActionCount, const nsString& aName, + const nsString& aTextValue, const nsString& aDOMNodeID, + const nsString& aDescription, + const double& aCurVal = UnspecifiedNaN<double>(), + const double& aMinVal = UnspecifiedNaN<double>(), + const double& aMaxVal = UnspecifiedNaN<double>(), + const double& aStep = UnspecifiedNaN<double>(), + nsIPersistentProperties* aAttributes = nullptr); + + virtual void WrapperDOMNodeID(nsString& aDOMNodeID); + + int32_t AndroidClass() { + return mID == kNoID ? java::SessionAccessibility::CLASSNAME_WEBVIEW + : GetAndroidClass(WrapperRole()); + } + + static already_AddRefed<nsIPersistentProperties> AttributeArrayToProperties( + const nsTArray<Attribute>& aAttributes); + + static const int32_t kNoID = -1; + + protected: + // IDs should be a positive 32bit integer. + static int32_t AcquireID(); + static void ReleaseID(int32_t aID); + + static int32_t GetAndroidClass(role aRole); + + static int32_t GetInputType(const nsString& aInputTypeAttr); + + int32_t mID; + + private: + virtual AccessibleWrap* WrapperParent() { + return static_cast<AccessibleWrap*>(Parent()); + } + + virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal, + double* aMaxVal, double* aStep); + + virtual role WrapperRole() { return Role(); } + + void GetTextEquiv(nsString& aText); + + bool HandleLiveRegionEvent(AccEvent* aEvent); + + void GetSelectionOrCaret(int32_t* aStartOffset, int32_t* aEndOffset); + + static void GetRoleDescription(role aRole, + nsIPersistentProperties* aAttributes, + nsAString& aGeckoRole, + nsAString& aRoleDescription); + + static uint32_t GetFlags(role aRole, uint64_t aState, uint8_t aActionCount); +}; + +static inline AccessibleWrap* WrapperFor(const ProxyAccessible* aProxy) { + return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper()); +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/ApplicationAccessibleWrap.h b/accessible/android/ApplicationAccessibleWrap.h new file mode 100644 index 0000000000..89b07916c9 --- /dev/null +++ b/accessible/android/ApplicationAccessibleWrap.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=4:tabstop=4: + */ +/* 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/. */ + +#ifndef mozilla_a11y_ApplicationAccessibleWrap_h__ +#define mozilla_a11y_ApplicationAccessibleWrap_h__ + +#include "ApplicationAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef ApplicationAccessible ApplicationAccessibleWrap; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/DocAccessibleWrap.cpp b/accessible/android/DocAccessibleWrap.cpp new file mode 100644 index 0000000000..a9652c1e1b --- /dev/null +++ b/accessible/android/DocAccessibleWrap.cpp @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "Accessible-inl.h" +#include "AccessibleOrProxy.h" +#include "DocAccessibleChild.h" +#include "DocAccessibleWrap.h" +#include "nsIDocShell.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "nsIPersistentProperties2.h" +#include "Pivot.h" +#include "SessionAccessibility.h" +#include "TraversalRule.h" +#include "mozilla/PresShell.h" +#include "mozilla/a11y/DocAccessiblePlatformExtChild.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +const uint32_t kCacheRefreshInterval = 500; + +#define UNIQUE_ID(acc) \ + !acc || (acc->IsDoc() && acc->AsDoc()->IPCDoc()) \ + ? 0 \ + : reinterpret_cast<uint64_t>(acc->UniqueID()) + +//////////////////////////////////////////////////////////////////////////////// +// DocAccessibleWrap +//////////////////////////////////////////////////////////////////////////////// + +DocAccessibleWrap::DocAccessibleWrap(Document* aDocument, PresShell* aPresShell) + : DocAccessible(aDocument, aPresShell) { + if (aDocument->GetBrowsingContext()->IsTopContent()) { + // The top-level content document gets this special ID. + mID = kNoID; + } else { + mID = AcquireID(); + } +} + +DocAccessibleWrap::~DocAccessibleWrap() {} + +AccessibleWrap* DocAccessibleWrap::GetAccessibleByID(int32_t aID) const { + if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) { + return acc; + } + + // If the ID is not in the hash table, check the IDs of the child docs. + for (uint32_t i = 0; i < ChildDocumentCount(); i++) { + auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i)); + if (childDoc->VirtualViewID() == aID) { + return childDoc; + } + } + + return nullptr; +} + +void DocAccessibleWrap::DoInitialUpdate() { + DocAccessible::DoInitialUpdate(); + CacheViewport(true); +} + +nsresult DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent) { + switch (aEvent->GetEventType()) { + case nsIAccessibleEvent::EVENT_SCROLLING_END: + CacheViewport(false); + break; + case nsIAccessibleEvent::EVENT_SCROLLING: + UpdateFocusPathBounds(); + break; + default: + break; + } + + return DocAccessible::HandleAccEvent(aEvent); +} + +void DocAccessibleWrap::CacheViewportCallback(nsITimer* aTimer, + void* aDocAccParam) { + RefPtr<DocAccessibleWrap> docAcc( + dont_AddRef(reinterpret_cast<DocAccessibleWrap*>(aDocAccParam))); + if (!docAcc || docAcc->HasShutdown()) { + return; + } + + PresShell* presShell = docAcc->PresShellPtr(); + nsIFrame* rootFrame = presShell->GetRootFrame(); + if (!rootFrame) { + return; + } + + nsTArray<nsIFrame*> frames; + nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); + nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect(); + + nsLayoutUtils::GetFramesForArea( + RelativeTo{presShell->GetRootFrame()}, scrollPort, frames, + {nsLayoutUtils::FrameForPointOption::OnlyVisible}); + AccessibleHashtable inViewAccs; + for (size_t i = 0; i < frames.Length(); i++) { + nsIContent* content = frames.ElementAt(i)->GetContent(); + if (!content) { + continue; + } + + Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content); + if (!visibleAcc) { + continue; + } + + for (Accessible* acc = visibleAcc; acc && acc != docAcc->Parent(); + acc = acc->Parent()) { + if (inViewAccs.Contains(acc->UniqueID())) { + break; + } + inViewAccs.Put(acc->UniqueID(), RefPtr{acc}); + } + } + + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = docAcc->IPCDoc(); + nsTArray<BatchData> cacheData(inViewAccs.Count()); + for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) { + Accessible* accessible = iter.Data(); + nsAutoString name; + accessible->Name(name); + nsAutoString textValue; + accessible->Value(textValue); + nsAutoString nodeID; + static_cast<AccessibleWrap*>(accessible)->WrapperDOMNodeID(nodeID); + nsAutoString description; + accessible->Description(description); + + cacheData.AppendElement(BatchData( + accessible->Document()->IPCDoc(), UNIQUE_ID(accessible), + accessible->State(), accessible->Bounds(), accessible->ActionCount(), + name, textValue, nodeID, description, UnspecifiedNaN<double>(), + UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), + UnspecifiedNaN<double>(), nsTArray<Attribute>())); + } + + ipcDoc->SendBatch(eBatch_Viewport, cacheData); + } else if (RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(docAcc)) { + nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count()); + for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) { + accessibles.AppendElement( + static_cast<AccessibleWrap*>(iter.Data().get())); + } + + sessionAcc->ReplaceViewportCache(accessibles); + } + + if (docAcc->mCachePivotBoundaries) { + AccessibleOrProxy accOrProxy = AccessibleOrProxy(docAcc); + a11y::Pivot pivot(accOrProxy); + TraversalRule rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT); + Accessible* first = pivot.First(rule).AsAccessible(); + Accessible* last = pivot.Last(rule).AsAccessible(); + + // If first/last are null, pass the root document as pivot boundary. + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = docAcc->IPCDoc(); + DocAccessibleChild* firstDoc = + first ? first->Document()->IPCDoc() : ipcDoc; + DocAccessibleChild* lastDoc = last ? last->Document()->IPCDoc() : ipcDoc; + if (ipcDoc && firstDoc && lastDoc) { + // One or more of the documents may not have recieved an IPC doc yet. + // In that case, just throw away this update. We will get a new one soon + // enough. + ipcDoc->GetPlatformExtension()->SendSetPivotBoundaries( + firstDoc, UNIQUE_ID(first), lastDoc, UNIQUE_ID(last)); + } + } else if (RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(docAcc)) { + sessionAcc->UpdateAccessibleFocusBoundaries( + first ? static_cast<AccessibleWrap*>(first) : docAcc, + last ? static_cast<AccessibleWrap*>(last) : docAcc); + } + + docAcc->mCachePivotBoundaries = false; + } + + if (docAcc->mCacheRefreshTimer) { + docAcc->mCacheRefreshTimer = nullptr; + } +} + +void DocAccessibleWrap::CacheViewport(bool aCachePivotBoundaries) { + mCachePivotBoundaries |= aCachePivotBoundaries; + if (VirtualViewID() == kNoID && !mCacheRefreshTimer) { + NS_NewTimerWithFuncCallback(getter_AddRefs(mCacheRefreshTimer), + CacheViewportCallback, this, + kCacheRefreshInterval, nsITimer::TYPE_ONE_SHOT, + "a11y::DocAccessibleWrap::CacheViewport"); + if (mCacheRefreshTimer) { + NS_ADDREF_THIS(); // Kung fu death grip + } + } +} + +DocAccessibleWrap* DocAccessibleWrap::GetTopLevelContentDoc( + AccessibleWrap* aAccessible) { + DocAccessibleWrap* doc = + static_cast<DocAccessibleWrap*>(aAccessible->Document()); + while (doc && doc->VirtualViewID() != kNoID) { + doc = static_cast<DocAccessibleWrap*>(doc->ParentDocument()); + } + + return doc; +} + +void DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) { + mFocusPath.Clear(); + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = IPCDoc(); + nsTArray<BatchData> cacheData; + for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent(); + acc = static_cast<AccessibleWrap*>(acc->Parent())) { + nsAutoString name; + acc->Name(name); + nsAutoString textValue; + acc->Value(textValue); + nsAutoString nodeID; + acc->WrapperDOMNodeID(nodeID); + nsAutoString description; + acc->Description(description); + nsCOMPtr<nsIPersistentProperties> props = acc->Attributes(); + nsTArray<Attribute> attributes; + nsAccUtils::PersistentPropertiesToArray(props, &attributes); + cacheData.AppendElement( + BatchData(acc->Document()->IPCDoc(), UNIQUE_ID(acc), acc->State(), + acc->Bounds(), acc->ActionCount(), name, textValue, nodeID, + description, acc->CurValue(), acc->MinValue(), + acc->MaxValue(), acc->Step(), attributes)); + mFocusPath.Put(acc->UniqueID(), RefPtr{acc}); + } + + ipcDoc->SendBatch(eBatch_FocusPath, cacheData); + } else if (RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(this)) { + nsTArray<AccessibleWrap*> accessibles; + for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent(); + acc = static_cast<AccessibleWrap*>(acc->Parent())) { + accessibles.AppendElement(acc); + mFocusPath.Put(acc->UniqueID(), RefPtr{acc}); + } + + sessionAcc->ReplaceFocusPathCache(accessibles); + } +} + +void DocAccessibleWrap::UpdateFocusPathBounds() { + if (!mFocusPath.Count()) { + return; + } + + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = IPCDoc(); + nsTArray<BatchData> boundsData(mFocusPath.Count()); + for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { + Accessible* accessible = iter.Data(); + if (!accessible || accessible->IsDefunct()) { + MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone."); + continue; + } + + boundsData.AppendElement( + BatchData(accessible->Document()->IPCDoc(), UNIQUE_ID(accessible), 0, + accessible->Bounds(), 0, nsString(), nsString(), nsString(), + nsString(), UnspecifiedNaN<double>(), + UnspecifiedNaN<double>(), UnspecifiedNaN<double>(), + UnspecifiedNaN<double>(), nsTArray<Attribute>())); + } + + ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData); + } else if (RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(this)) { + nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count()); + for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) { + Accessible* accessible = iter.Data(); + if (!accessible || accessible->IsDefunct()) { + MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone."); + continue; + } + + accessibles.AppendElement(static_cast<AccessibleWrap*>(accessible)); + } + + sessionAcc->UpdateCachedBounds(accessibles); + } +} + +#undef UNIQUE_ID diff --git a/accessible/android/DocAccessibleWrap.h b/accessible/android/DocAccessibleWrap.h new file mode 100644 index 0000000000..5380079fe8 --- /dev/null +++ b/accessible/android/DocAccessibleWrap.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_DocAccessibleWrap_h__ +#define mozilla_a11y_DocAccessibleWrap_h__ + +#include "DocAccessible.h" +#include "nsITimer.h" + +namespace mozilla { + +class PresShell; + +namespace a11y { + +class DocAccessibleWrap : public DocAccessible { + public: + DocAccessibleWrap(Document* aDocument, PresShell* aPresShell); + virtual ~DocAccessibleWrap(); + + virtual nsresult HandleAccEvent(AccEvent* aEvent) override; + + /** + * Manage the mapping from id to Accessible. + */ + void AddID(uint32_t aID, AccessibleWrap* aAcc) { + mIDToAccessibleMap.Put(aID, aAcc); + } + void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); } + AccessibleWrap* GetAccessibleByID(int32_t aID) const; + + DocAccessibleWrap* GetTopLevelContentDoc(AccessibleWrap* aAccessible); + + void CacheFocusPath(AccessibleWrap* aAccessible); + + void CacheViewport(bool aCachePivotBoundaries); + + enum { + eBatch_Viewport = 0, + eBatch_FocusPath = 1, + eBatch_BoundsUpdate = 2, + }; + + protected: + /* + * This provides a mapping from 32 bit id to accessible objects. + */ + nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap; + + virtual void DoInitialUpdate() override; + + private: + void UpdateFocusPathBounds(); + + static void CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam); + + nsCOMPtr<nsITimer> mCacheRefreshTimer; + + bool mCachePivotBoundaries; + + AccessibleHashtable mFocusPath; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/HTMLTableAccessibleWrap.h b/accessible/android/HTMLTableAccessibleWrap.h new file mode 100644 index 0000000000..f62e626109 --- /dev/null +++ b/accessible/android/HTMLTableAccessibleWrap.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef mozilla_a11y_HTMLTableAccessibleWrap_h__ +#define mozilla_a11y_HTMLTableAccessibleWrap_h__ + +#include "HTMLTableAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class HTMLTableAccessible HTMLTableAccessibleWrap; +typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap; +typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/HyperTextAccessibleWrap.h b/accessible/android/HyperTextAccessibleWrap.h new file mode 100644 index 0000000000..da569c8216 --- /dev/null +++ b/accessible/android/HyperTextAccessibleWrap.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_HyperTextAccessibleWrap_h__ +#define mozilla_a11y_HyperTextAccessibleWrap_h__ + +#include "HyperTextAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class HyperTextAccessible HyperTextAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/ImageAccessibleWrap.h b/accessible/android/ImageAccessibleWrap.h new file mode 100644 index 0000000000..a5621c7313 --- /dev/null +++ b/accessible/android/ImageAccessibleWrap.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:expandtab:shiftwidth=2:tabstop=2: + */ +/* 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/. */ + +#ifndef mozilla_a11y_ImageAccessibleWrap_h__ +#define mozilla_a11y_ImageAccessibleWrap_h__ + +#include "ImageAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class ImageAccessible ImageAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/Platform.cpp b/accessible/android/Platform.cpp new file mode 100644 index 0000000000..4d17d8be72 --- /dev/null +++ b/accessible/android/Platform.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "Platform.h" +#include "ProxyAccessibleWrap.h" +#include "DocAccessibleWrap.h" +#include "SessionAccessibility.h" +#include "mozilla/a11y/ProxyAccessible.h" +#include "nsIAccessibleEvent.h" +#include "nsIAccessiblePivot.h" +#include "nsIStringBundle.h" + +#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties" + +using namespace mozilla; +using namespace mozilla::a11y; + +static nsIStringBundle* sStringBundle; + +void a11y::PlatformInit() {} + +void a11y::PlatformShutdown() { NS_IF_RELEASE(sStringBundle); } + +void a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces) { + AccessibleWrap* wrapper = nullptr; + if (aProxy->IsDoc()) { + wrapper = new DocProxyAccessibleWrap(aProxy->AsDoc()); + } else { + wrapper = new ProxyAccessibleWrap(aProxy); + } + + wrapper->AddRef(); + aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper)); +} + +void a11y::ProxyDestroyed(ProxyAccessible* aProxy) { + AccessibleWrap* wrapper = + reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper()); + + // If aProxy is a document that was created, but + // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be + // null. + if (!wrapper) { + return; + } + + wrapper->Shutdown(); + aProxy->SetWrapper(0); + wrapper->Release(); +} + +void a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + if (!sessionAcc) { + return; + } + + switch (aEventType) { + case nsIAccessibleEvent::EVENT_FOCUS: + sessionAcc->SendFocusEvent(WrapperFor(aTarget)); + break; + } +} + +void a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState, + bool aEnabled) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (!sessionAcc) { + return; + } + + if (aState & states::CHECKED) { + sessionAcc->SendClickedEvent( + WrapperFor(aTarget), + java::SessionAccessibility::FLAG_CHECKABLE | + (aEnabled ? java::SessionAccessibility::FLAG_CHECKED : 0)); + } + + if (aState & states::EXPANDED) { + sessionAcc->SendClickedEvent( + WrapperFor(aTarget), + java::SessionAccessibility::FLAG_EXPANDABLE | + (aEnabled ? java::SessionAccessibility::FLAG_EXPANDED : 0)); + } + + if (aState & states::SELECTED) { + sessionAcc->SendSelectedEvent(WrapperFor(aTarget), aEnabled); + } + + if (aState & states::BUSY) { + sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget)); + } +} + +void a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset, + bool aIsSelectionCollapsed) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (sessionAcc) { + sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset); + } +} + +void a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr, + int32_t aStart, uint32_t aLen, bool aIsInsert, + bool aFromUser) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (sessionAcc) { + sessionAcc->SendTextChangedEvent(WrapperFor(aTarget), aStr, aStart, aLen, + aIsInsert, aFromUser); + } +} + +void a11y::ProxyShowHideEvent(ProxyAccessible* aTarget, + ProxyAccessible* aParent, bool aInsert, + bool aFromUser) { + // We rely on the window content changed events to be dispatched + // after the viewport cache is refreshed. +} + +void a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t) {} + +void a11y::ProxyVirtualCursorChangeEvent( + ProxyAccessible* aTarget, ProxyAccessible* aOldPosition, + int32_t aOldStartOffset, int32_t aOldEndOffset, + ProxyAccessible* aNewPosition, int32_t aNewStartOffset, + int32_t aNewEndOffset, int16_t aReason, int16_t aBoundaryType, + bool aFromUser) { + if (!aNewPosition || !aFromUser) { + return; + } + + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (!sessionAcc) { + return; + } + + if (aReason == nsIAccessiblePivot::REASON_POINT) { + sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition)); + } else if (aBoundaryType == nsIAccessiblePivot::NO_BOUNDARY) { + RefPtr<AccessibleWrap> wrapperForNewPosition = WrapperFor(aNewPosition); + sessionAcc->SendAccessibilityFocusedEvent(wrapperForNewPosition); + } + + if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) { + sessionAcc->SendTextTraversedEvent(WrapperFor(aNewPosition), + aNewStartOffset, aNewEndOffset); + } +} + +void a11y::ProxyScrollingEvent(ProxyAccessible* aTarget, uint32_t aEventType, + uint32_t aScrollX, uint32_t aScrollY, + uint32_t aMaxScrollX, uint32_t aMaxScrollY) { + if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (sessionAcc) { + sessionAcc->SendScrollingEvent(WrapperFor(aTarget), aScrollX, aScrollY, + aMaxScrollX, aMaxScrollY); + } + } +} + +void a11y::ProxyAnnouncementEvent(ProxyAccessible* aTarget, + const nsString& aAnnouncement, + uint16_t aPriority) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aTarget); + + if (sessionAcc) { + sessionAcc->SendAnnouncementEvent(WrapperFor(aTarget), aAnnouncement, + aPriority); + } +} + +void a11y::ProxyBatch(ProxyAccessible* aDocument, const uint64_t aBatchType, + const nsTArray<ProxyAccessible*>& aAccessibles, + const nsTArray<BatchData>& aData) { + RefPtr<SessionAccessibility> sessionAcc = + SessionAccessibility::GetInstanceFor(aDocument); + if (!sessionAcc) { + return; + } + + nsTArray<AccessibleWrap*> accWraps(aAccessibles.Length()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + accWraps.AppendElement(WrapperFor(aAccessibles.ElementAt(i))); + } + + switch (aBatchType) { + case DocAccessibleWrap::eBatch_Viewport: + sessionAcc->ReplaceViewportCache(accWraps, aData); + break; + case DocAccessibleWrap::eBatch_FocusPath: + sessionAcc->ReplaceFocusPathCache(accWraps, aData); + break; + case DocAccessibleWrap::eBatch_BoundsUpdate: + sessionAcc->UpdateCachedBounds(accWraps, aData); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown batch type."); + break; + } +} + +bool a11y::LocalizeString(const char* aToken, nsAString& aLocalized, + const nsTArray<nsString>& aFormatString) { + MOZ_ASSERT(XRE_IsParentProcess()); + nsresult rv = NS_OK; + if (!sStringBundle) { + nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get string bundle service"); + return false; + } + + nsCOMPtr<nsIStringBundle> sb; + rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(sb)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get string bundle"); + return false; + } + + sb.forget(&sStringBundle); + } + + MOZ_ASSERT(sStringBundle); + + if (aFormatString.Length()) { + rv = sStringBundle->FormatStringFromName(aToken, aFormatString, aLocalized); + if (NS_SUCCEEDED(rv)) { + return true; + } + } else { + rv = sStringBundle->GetStringFromName(aToken, aLocalized); + if (NS_SUCCEEDED(rv)) { + return true; + } + } + + NS_WARNING("Failed to localize string"); + aLocalized.AssignLiteral(""); + return false; +} diff --git a/accessible/android/ProxyAccessibleWrap.cpp b/accessible/android/ProxyAccessibleWrap.cpp new file mode 100644 index 0000000000..d8ab49e6d1 --- /dev/null +++ b/accessible/android/ProxyAccessibleWrap.cpp @@ -0,0 +1,172 @@ +/* -*- 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 "ProxyAccessibleWrap.h" +#include "Accessible-inl.h" + +#include "nsPersistentProperties.h" + +#include "mozilla/a11y/DocAccessiblePlatformExtParent.h" + +using namespace mozilla::a11y; + +ProxyAccessibleWrap::ProxyAccessibleWrap(ProxyAccessible* aProxy) + : AccessibleWrap(nullptr, nullptr) { + mType = eProxyType; + mBits.proxy = aProxy; + + if (aProxy->mHasValue) { + mStateFlags |= eHasNumericValue; + } + + if (aProxy->mIsSelection) { + mGenericTypes |= eSelect; + } + + if (aProxy->mIsHyperText) { + mGenericTypes |= eHyperText; + } + + auto doc = reinterpret_cast<DocProxyAccessibleWrap*>( + Proxy()->Document()->GetWrapper()); + if (doc) { + mID = AcquireID(); + doc->AddID(mID, this); + } +} + +void ProxyAccessibleWrap::Shutdown() { + auto doc = reinterpret_cast<DocProxyAccessibleWrap*>( + Proxy()->Document()->GetWrapper()); + if (mID && doc) { + doc->RemoveID(mID); + ReleaseID(mID); + mID = 0; + } + + mBits.proxy = nullptr; + mStateFlags |= eIsDefunct; +} + +// Accessible + +already_AddRefed<nsIPersistentProperties> ProxyAccessibleWrap::Attributes() { + AutoTArray<Attribute, 10> attrs; + Proxy()->Attributes(&attrs); + return AttributeArrayToProperties(attrs); +} + +uint32_t ProxyAccessibleWrap::ChildCount() const { + return Proxy()->ChildrenCount(); +} + +Accessible* ProxyAccessibleWrap::GetChildAt(uint32_t aIndex) const { + ProxyAccessible* child = Proxy()->ChildAt(aIndex); + return child ? WrapperFor(child) : nullptr; +} + +ENameValueFlag ProxyAccessibleWrap::Name(nsString& aName) const { + Proxy()->Name(aName); + return eNameOK; +} + +void ProxyAccessibleWrap::Value(nsString& aValue) const { + Proxy()->Value(aValue); +} + +uint64_t ProxyAccessibleWrap::State() { return Proxy()->State(); } + +nsIntRect ProxyAccessibleWrap::Bounds() const { return Proxy()->Bounds(); } + +void ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const { + Proxy()->ScrollTo(aHow); +} + +uint8_t ProxyAccessibleWrap::ActionCount() const { + return Proxy()->ActionCount(); +} + +bool ProxyAccessibleWrap::DoAction(uint8_t aIndex) const { + return Proxy()->DoAction(aIndex); +} + +// Other + +void ProxyAccessibleWrap::SetTextContents(const nsAString& aText) { + Proxy()->ReplaceText(PromiseFlatString(aText)); +} + +void ProxyAccessibleWrap::GetTextContents(nsAString& aText) { + nsAutoString text; + Proxy()->TextSubstring(0, -1, text); + aText.Assign(text); +} + +bool ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, + int32_t* aEndOffset) { + nsAutoString unused; + return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset); +} + +void ProxyAccessibleWrap::PivotTo(int32_t aGranularity, bool aForward, + bool aInclusive) { + Unused << Proxy()->Document()->GetPlatformExtension()->SendPivot( + Proxy()->ID(), aGranularity, aForward, aInclusive); +} + +void ProxyAccessibleWrap::ExploreByTouch(float aX, float aY) { + Unused << Proxy()->Document()->GetPlatformExtension()->SendExploreByTouch( + Proxy()->ID(), aX, aY); +} + +void ProxyAccessibleWrap::NavigateText(int32_t aGranularity, + int32_t aStartOffset, int32_t aEndOffset, + bool aForward, bool aSelect) { + Unused << Proxy()->Document()->GetPlatformExtension()->SendNavigateText( + Proxy()->ID(), aGranularity, aStartOffset, aEndOffset, aForward, aSelect); +} + +void ProxyAccessibleWrap::SetSelection(int32_t aStart, int32_t aEnd) { + Unused << Proxy()->Document()->GetPlatformExtension()->SendSetSelection( + Proxy()->ID(), aStart, aEnd); +} + +void ProxyAccessibleWrap::Cut() { + Unused << Proxy()->Document()->GetPlatformExtension()->SendCut(Proxy()->ID()); +} + +void ProxyAccessibleWrap::Copy() { + Unused << Proxy()->Document()->GetPlatformExtension()->SendCopy( + Proxy()->ID()); +} + +void ProxyAccessibleWrap::Paste() { + Unused << Proxy()->Document()->GetPlatformExtension()->SendPaste( + Proxy()->ID()); +} + +role ProxyAccessibleWrap::WrapperRole() { return Proxy()->Role(); } + +AccessibleWrap* ProxyAccessibleWrap::WrapperParent() { + return Proxy()->Parent() ? WrapperFor(Proxy()->Parent()) : nullptr; +} + +bool ProxyAccessibleWrap::WrapperRangeInfo(double* aCurVal, double* aMinVal, + double* aMaxVal, double* aStep) { + if (HasNumericValue()) { + ProxyAccessible* proxy = Proxy(); + *aCurVal = proxy->CurValue(); + *aMinVal = proxy->MinValue(); + *aMaxVal = proxy->MaxValue(); + *aStep = proxy->Step(); + return true; + } + + return false; +} + +void ProxyAccessibleWrap::WrapperDOMNodeID(nsString& aDOMNodeID) { + Proxy()->DOMNodeID(aDOMNodeID); +} diff --git a/accessible/android/ProxyAccessibleWrap.h b/accessible/android/ProxyAccessibleWrap.h new file mode 100644 index 0000000000..40286fcc45 --- /dev/null +++ b/accessible/android/ProxyAccessibleWrap.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. * + */ + +#ifndef MOZILLA_A11Y_ProxyAccessibleWrap_h +#define MOZILLA_A11Y_ProxyAccessibleWrap_h + +#include "AccessibleWrap.h" +#include "DocAccessibleParent.h" + +namespace mozilla { +namespace a11y { + +/** + * A wrapper for Accessible proxies. The public methods here should be overriden + * from AccessibleWrap or its super classes. + * This gives us an abstraction layer so SessionAccessibility doesn't have + * to distinguish between a local or remote accessibles. + * NOTE: This shouldn't be regarded as a full Accessible implementation. + */ +class ProxyAccessibleWrap : public AccessibleWrap { + public: + explicit ProxyAccessibleWrap(ProxyAccessible* aProxy); + + virtual void Shutdown() override; + + // Accessible + + virtual already_AddRefed<nsIPersistentProperties> Attributes() override; + + virtual uint32_t ChildCount() const override; + + virtual Accessible* GetChildAt(uint32_t aIndex) const override; + + virtual ENameValueFlag Name(nsString& aName) const override; + + virtual void Value(nsString& aValue) const override; + + virtual uint64_t State() override; + + virtual nsIntRect Bounds() const override; + + MOZ_CAN_RUN_SCRIPT + virtual void ScrollTo(uint32_t aHow) const override; + + virtual uint8_t ActionCount() const override; + + virtual bool DoAction(uint8_t aIndex) const override; + + // AccessibleWrap + + virtual void SetTextContents(const nsAString& aText) override; + + virtual void GetTextContents(nsAString& aText) override; + + virtual bool GetSelectionBounds(int32_t* aStartOffset, + int32_t* aEndOffset) override; + + virtual void PivotTo(int32_t aGranularity, bool aForward, + bool aInclusive) override; + + virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset, + int32_t aEndOffset, bool aForward, + bool aSelect) override; + + virtual void SetSelection(int32_t aStart, int32_t aEnd) override; + + virtual void Cut() override; + + virtual void Copy() override; + + virtual void Paste() override; + + virtual void ExploreByTouch(float aX, float aY) override; + + virtual void WrapperDOMNodeID(nsString& aDOMNodeID) override; + + private: + virtual role WrapperRole() override; + + virtual AccessibleWrap* WrapperParent() override; + + virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal, + double* aMaxVal, double* aStep) override; +}; + +class DocProxyAccessibleWrap : public ProxyAccessibleWrap { + public: + explicit DocProxyAccessibleWrap(DocAccessibleParent* aProxy) + : ProxyAccessibleWrap(aProxy) { + mGenericTypes |= eDocument; + + if (aProxy->IsTopLevel()) { + mID = kNoID; + } else { + mID = AcquireID(); + } + } + + virtual void Shutdown() override { + if (mID) { + auto doc = static_cast<DocAccessibleParent*>(Proxy()); + if (!doc->IsTopLevel()) { + MOZ_ASSERT(mID != kNoID, "A non root accessible must have an id"); + ReleaseID(mID); + } + } + mID = 0; + mBits.proxy = nullptr; + mStateFlags |= eIsDefunct; + } + + DocProxyAccessibleWrap* ParentDocument() { + DocAccessibleParent* proxy = static_cast<DocAccessibleParent*>(Proxy()); + MOZ_ASSERT(proxy); + if (DocAccessibleParent* parent = proxy->ParentDoc()) { + return reinterpret_cast<DocProxyAccessibleWrap*>(parent->GetWrapper()); + } + + return nullptr; + } + + DocProxyAccessibleWrap* GetChildDocumentAt(uint32_t aIndex) { + auto doc = Proxy()->AsDoc(); + if (doc && doc->ChildDocCount() > aIndex) { + return reinterpret_cast<DocProxyAccessibleWrap*>( + doc->ChildDocAt(aIndex)->GetWrapper()); + } + + return nullptr; + } + + void AddID(uint32_t aID, AccessibleWrap* aAcc) { + mIDToAccessibleMap.Put(aID, aAcc); + } + void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); } + AccessibleWrap* GetAccessibleByID(uint32_t aID) const { + return mIDToAccessibleMap.Get(aID); + } + + private: + /* + * This provides a mapping from 32 bit id to accessible objects. + */ + nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap; +}; +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/RootAccessibleWrap.cpp b/accessible/android/RootAccessibleWrap.cpp new file mode 100644 index 0000000000..6a9430da25 --- /dev/null +++ b/accessible/android/RootAccessibleWrap.cpp @@ -0,0 +1,98 @@ +/* -*- 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 "RootAccessibleWrap.h" + +#include "Accessible-inl.h" +#include "AccessibleOrProxy.h" +#include "DocAccessibleParent.h" +#include "DocAccessible-inl.h" +#include "ProxyAccessibleWrap.h" +#include "SessionAccessibility.h" +#include "mozilla/PresShell.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDoc, + PresShell* aPresShell) + : RootAccessible(aDoc, aPresShell) {} + +RootAccessibleWrap::~RootAccessibleWrap() {} + +AccessibleWrap* RootAccessibleWrap::GetContentAccessible() { + if (ProxyAccessible* proxy = GetPrimaryRemoteTopLevelContentDoc()) { + return WrapperFor(proxy); + } + + // Find first document that is not defunct or hidden. + // This is exclusively for Fennec which has a deck of browser elements. + // Otherwise, standard GeckoView will only have one browser element. + for (size_t i = 0; i < ChildDocumentCount(); i++) { + DocAccessible* childDoc = GetChildDocumentAt(i); + if (childDoc && !childDoc->IsDefunct() && !childDoc->IsHidden()) { + return childDoc; + } + } + + return nullptr; +} + +AccessibleWrap* RootAccessibleWrap::FindAccessibleById(int32_t aID) { + AccessibleWrap* contentAcc = GetContentAccessible(); + + if (!contentAcc) { + return nullptr; + } + + if (aID == AccessibleWrap::kNoID) { + return contentAcc; + } + + if (contentAcc->IsProxy()) { + return FindAccessibleById(static_cast<DocProxyAccessibleWrap*>(contentAcc), + aID); + } + + return FindAccessibleById( + static_cast<DocAccessibleWrap*>(contentAcc->AsDoc()), aID); +} + +AccessibleWrap* RootAccessibleWrap::FindAccessibleById( + DocProxyAccessibleWrap* aDoc, int32_t aID) { + AccessibleWrap* acc = aDoc->GetAccessibleByID(aID); + uint32_t index = 0; + while (!acc) { + auto child = + static_cast<DocProxyAccessibleWrap*>(aDoc->GetChildDocumentAt(index++)); + if (!child) { + break; + } + // A child document's id is not in its parent document's hash table. + if (child->VirtualViewID() == aID) { + acc = child; + } else { + acc = FindAccessibleById(child, aID); + } + } + + return acc; +} + +AccessibleWrap* RootAccessibleWrap::FindAccessibleById(DocAccessibleWrap* aDoc, + int32_t aID) { + AccessibleWrap* acc = aDoc->GetAccessibleByID(aID); + uint32_t index = 0; + while (!acc) { + auto child = + static_cast<DocAccessibleWrap*>(aDoc->GetChildDocumentAt(index++)); + if (!child) { + break; + } + acc = FindAccessibleById(child, aID); + } + + return acc; +} diff --git a/accessible/android/RootAccessibleWrap.h b/accessible/android/RootAccessibleWrap.h new file mode 100644 index 0000000000..6e782dbda8 --- /dev/null +++ b/accessible/android/RootAccessibleWrap.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_RootAccessibleWrap_h__ +#define mozilla_a11y_RootAccessibleWrap_h__ + +#include "RootAccessible.h" + +namespace mozilla { + +class PresShell; + +namespace a11y { + +class DocProxyAccessibleWrap; + +class RootAccessibleWrap : public RootAccessible { + public: + RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell); + virtual ~RootAccessibleWrap(); + + AccessibleWrap* GetContentAccessible(); + + AccessibleWrap* FindAccessibleById(int32_t aID); + + // Recursively searches for the accessible ID within the document tree. + AccessibleWrap* FindAccessibleById(DocAccessibleWrap* aDocument, int32_t aID); + + // Recursively searches for the accessible ID within the proxy document tree. + AccessibleWrap* FindAccessibleById(DocProxyAccessibleWrap* aDocument, + int32_t aID); +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/SessionAccessibility.cpp b/accessible/android/SessionAccessibility.cpp new file mode 100644 index 0000000000..2aa90e575e --- /dev/null +++ b/accessible/android/SessionAccessibility.cpp @@ -0,0 +1,467 @@ +/* -*- 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 "Accessible-inl.h" +#include "AndroidUiThread.h" +#include "DocAccessibleParent.h" +#include "nsThreadUtils.h" +#include "AccessibilityEvent.h" +#include "HyperTextAccessible.h" +#include "JavaBuiltins.h" +#include "RootAccessibleWrap.h" +#include "nsAccessibilityService.h" +#include "nsViewManager.h" +#include "nsIPersistentProperties2.h" + +#include "mozilla/PresShell.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/a11y/DocAccessibleParent.h" +#include "mozilla/a11y/DocManager.h" +#include "mozilla/jni/GeckoBundleUtils.h" +#include "mozilla/widget/GeckoViewSupport.h" + +#ifdef DEBUG +# include <android/log.h> +# 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, ...) \ + if (RootAccessibleWrap* rootAcc = GetRoot()) { \ + AccessibleWrap* acc = rootAcc->FindAccessibleById(aID); \ + if (!acc) { \ + return; \ + } \ + \ + acc->funcname(__VA_ARGS__); \ + } + +using namespace mozilla::a11y; + +class Settings final + : public mozilla::java::SessionAccessibility::Settings::Natives<Settings> { + public: + static void ToggleNativeAccessibility(bool aEnable) { + if (aEnable) { + GetOrCreateAccService(); + } else { + MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI); + } + } +}; + +SessionAccessibility::SessionAccessibility( + jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow, + java::SessionAccessibility::NativeProvider::Param aSessionAccessibility) + : mWindow(aWindow), mSessionAccessibility(aSessionAccessibility) { + SetAttached(true, nullptr); +} + +void SessionAccessibility::SetAttached(bool aAttached, + already_AddRefed<Runnable> aRunnable) { + if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) { + uiThread->Dispatch(NS_NewRunnableFunction( + "SessionAccessibility::Attach", + [aAttached, + sa = java::SessionAccessibility::NativeProvider::GlobalRef( + mSessionAccessibility), + runnable = RefPtr<Runnable>(aRunnable)] { + sa->SetAttached(aAttached); + if (runnable) { + runnable->Run(); + } + })); + } +} + +void SessionAccessibility::Init() { + java::SessionAccessibility::NativeProvider::Natives< + SessionAccessibility>::Init(); + Settings::Init(); +} + +mozilla::jni::Object::LocalRef SessionAccessibility::GetNodeInfo(int32_t aID) { + java::GeckoBundle::GlobalRef ret = nullptr; + RefPtr<SessionAccessibility> self(this); + nsAppShell::SyncRunEvent([this, self, aID, &ret] { + if (RootAccessibleWrap* rootAcc = GetRoot()) { + AccessibleWrap* acc = rootAcc->FindAccessibleById(aID); + if (acc) { + ret = acc->ToBundle(); + } else { + AALOG("oops, nothing for %d", aID); + } + } + }); + + return mozilla::jni::Object::Ref::From(ret); +} + +RootAccessibleWrap* SessionAccessibility::GetRoot() { + auto acc(mWindow.Access()); + if (!acc) { + return nullptr; + } + + nsWindow* gkWindow = acc->GetNsWindow(); + if (!gkWindow) { + return nullptr; + } + + return static_cast<RootAccessibleWrap*>(gkWindow->GetRootAccessible()); +} + +void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) { + FORWARD_ACTION_TO_ACCESSIBLE(SetTextContents, aText->ToString()); +} + +void SessionAccessibility::Click(int32_t aID) { + FORWARD_ACTION_TO_ACCESSIBLE(DoAction, 0); +} + +void SessionAccessibility::Pivot(int32_t aID, int32_t aGranularity, + bool aForward, bool aInclusive) { + FORWARD_ACTION_TO_ACCESSIBLE(PivotTo, aGranularity, aForward, aInclusive); +} + +void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) { + FORWARD_ACTION_TO_ACCESSIBLE(ExploreByTouch, aX, aY); +} + +void SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity, + int32_t aStartOffset, + int32_t aEndOffset, bool aForward, + bool aSelect) { + FORWARD_ACTION_TO_ACCESSIBLE(NavigateText, aGranularity, aStartOffset, + aEndOffset, aForward, aSelect); +} + +void SessionAccessibility::SetSelection(int32_t aID, int32_t aStart, + int32_t aEnd) { + FORWARD_ACTION_TO_ACCESSIBLE(SetSelection, aStart, aEnd); +} + +void SessionAccessibility::Cut(int32_t aID) { + FORWARD_ACTION_TO_ACCESSIBLE(Cut); +} + +void SessionAccessibility::Copy(int32_t aID) { + FORWARD_ACTION_TO_ACCESSIBLE(Copy); +} + +void SessionAccessibility::Paste(int32_t aID) { + FORWARD_ACTION_TO_ACCESSIBLE(Paste); +} + +RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor( + ProxyAccessible* aAccessible) { + auto tab = + static_cast<dom::BrowserParent*>(aAccessible->Document()->Manager()); + dom::Element* frame = tab->GetOwnerElement(); + MOZ_ASSERT(frame); + if (!frame) { + return nullptr; + } + + Accessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc()); + return chromeDoc ? GetInstanceFor(chromeDoc) : nullptr; +} + +RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor( + Accessible* aAccessible) { + RootAccessible* rootAcc = aAccessible->RootAccessible(); + nsViewManager* vm = rootAcc->PresShellPtr()->GetViewManager(); + if (!vm) { + return nullptr; + } + + nsCOMPtr<nsIWidget> rootWidget; + vm->GetRootWidget(getter_AddRefs(rootWidget)); + // `rootWidget` can be one of several types. Here we make sure it is an + // android nsWindow. + if (RefPtr<nsWindow> window = nsWindow::From(rootWidget)) { + return window->GetSessionAccessibility(); + } + + return nullptr; +} + +void SessionAccessibility::SendAccessibilityFocusedEvent( + AccessibleWrap* aAccessible) { + mSessionAccessibility->SendEvent( + java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr); + aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE); +} + +void SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible) { + mSessionAccessibility->SendEvent( + java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr); +} + +void SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible) { + // 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr); +} + +void SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible, + int32_t aScrollX, + int32_t aScrollY, + int32_t aMaxScrollX, + int32_t aMaxScrollY) { + int32_t virtualViewId = aAccessible->VirtualViewID(); + + if (virtualViewId != AccessibleWrap::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, + aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendWindowContentChangedEvent() { + mSessionAccessibility->SendEvent( + java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED, + AccessibleWrap::kNoID, java::SessionAccessibility::CLASSNAME_WEBVIEW, + nullptr); +} + +void SessionAccessibility::SendWindowStateChangedEvent( + AccessibleWrap* aAccessible) { + // 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr); +} + +void SessionAccessibility::SendTextSelectionChangedEvent( + AccessibleWrap* aAccessible, int32_t aCaretOffset) { + int32_t fromIndex = aCaretOffset; + int32_t startSel = -1; + int32_t endSel = -1; + if (aAccessible->GetSelectionBounds(&startSel, &endSel)) { + fromIndex = startSel == aCaretOffset ? endSel : startSel; + } + + GECKOBUNDLE_START(eventInfo); + 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible, + const nsString& aStr, + int32_t aStart, uint32_t aLen, + bool aIsInsert, + bool aFromUser) { + if (!aFromUser) { + // Only dispatch text change events from users, for now. + return; + } + + nsAutoString text; + aAccessible->GetTextContents(text); + 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, "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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible, + int32_t aStartOffset, + int32_t aEndOffset) { + nsAutoString text; + aAccessible->GetTextContents(text); + + 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendClickedEvent(AccessibleWrap* 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible, + bool aSelected) { + 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, + aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo); +} + +void SessionAccessibility::SendAnnouncementEvent(AccessibleWrap* aAccessible, + const nsString& aAnnouncement, + uint16_t aPriority) { + 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, AccessibleWrap::kNoID, + java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo); +} + +void SessionAccessibility::ReplaceViewportCache( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData) { + auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + AccessibleWrap* acc = aAccessibles.ElementAt(i); + if (!acc) { + MOZ_ASSERT_UNREACHABLE("Updated accessible is gone."); + continue; + } + + if (aData.Length() == aAccessibles.Length()) { + const BatchData& data = aData.ElementAt(i); + auto bundle = acc->ToBundle( + data.State(), data.Bounds(), data.ActionCount(), data.Name(), + data.TextValue(), data.DOMNodeID(), data.Description()); + infos->SetElement(i, bundle); + } else { + infos->SetElement(i, acc->ToBundle(true)); + } + } + + mSessionAccessibility->ReplaceViewportCache(infos); + SendWindowContentChangedEvent(); +} + +void SessionAccessibility::ReplaceFocusPathCache( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData) { + auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + AccessibleWrap* acc = aAccessibles.ElementAt(i); + if (!acc) { + MOZ_ASSERT_UNREACHABLE("Updated accessible is gone."); + continue; + } + + if (aData.Length() == aAccessibles.Length()) { + const BatchData& data = aData.ElementAt(i); + nsCOMPtr<nsIPersistentProperties> props = + AccessibleWrap::AttributeArrayToProperties(data.Attributes()); + auto bundle = + acc->ToBundle(data.State(), data.Bounds(), data.ActionCount(), + data.Name(), data.TextValue(), data.DOMNodeID(), + data.Description(), data.CurValue(), data.MinValue(), + data.MaxValue(), data.Step(), props); + infos->SetElement(i, bundle); + } else { + infos->SetElement(i, acc->ToBundle()); + } + } + + mSessionAccessibility->ReplaceFocusPathCache(infos); +} + +void SessionAccessibility::UpdateCachedBounds( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData) { + auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length()); + for (size_t i = 0; i < aAccessibles.Length(); i++) { + AccessibleWrap* acc = aAccessibles.ElementAt(i); + if (!acc) { + MOZ_ASSERT_UNREACHABLE("Updated accessible is gone."); + continue; + } + + if (aData.Length() == aAccessibles.Length()) { + const BatchData& data = aData.ElementAt(i); + auto bundle = acc->ToBundle( + data.State(), data.Bounds(), data.ActionCount(), data.Name(), + data.TextValue(), data.DOMNodeID(), data.Description()); + infos->SetElement(i, bundle); + } else { + infos->SetElement(i, acc->ToBundle(true)); + } + } + + mSessionAccessibility->UpdateCachedBounds(infos); + SendWindowContentChangedEvent(); +} + +void SessionAccessibility::UpdateAccessibleFocusBoundaries( + AccessibleWrap* aFirst, AccessibleWrap* aLast) { + mSessionAccessibility->UpdateAccessibleFocusBoundaries( + aFirst ? aFirst->VirtualViewID() : AccessibleWrap::kNoID, + aLast ? aLast->VirtualViewID() : AccessibleWrap::kNoID); +} +#undef FORWARD_ACTION_TO_ACCESSIBLE diff --git a/accessible/android/SessionAccessibility.h b/accessible/android/SessionAccessibility.h new file mode 100644 index 0000000000..79d7b725d5 --- /dev/null +++ b/accessible/android/SessionAccessibility.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_SessionAccessibility_h_ +#define mozilla_a11y_SessionAccessibility_h_ + +#include "mozilla/java/SessionAccessibilityNatives.h" +#include "mozilla/widget/GeckoViewSupport.h" +#include "nsAppShell.h" +#include "nsThreadUtils.h" +#include "nsWindow.h" + +namespace mozilla { +namespace a11y { + +class AccessibleWrap; +class ProxyAccessible; +class RootAccessibleWrap; +class BatchData; + +class SessionAccessibility final + : public java::SessionAccessibility::NativeProvider::Natives< + SessionAccessibility> { + public: + typedef java::SessionAccessibility::NativeProvider::Natives< + SessionAccessibility> + Base; + + SessionAccessibility( + jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow, + java::SessionAccessibility::NativeProvider::Param aSessionAccessibility); + + void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) { + SetAttached(false, std::move(aDisposer)); + } + + const java::SessionAccessibility::NativeProvider::Ref& + GetJavaAccessibility() { + return mSessionAccessibility; + } + + static void Init(); + static RefPtr<SessionAccessibility> GetInstanceFor( + ProxyAccessible* aAccessible); + static RefPtr<SessionAccessibility> GetInstanceFor(Accessible* aAccessible); + + // Native implementations + using Base::AttachNative; + using Base::DisposeNative; + jni::Object::LocalRef GetNodeInfo(int32_t aID); + void SetText(int32_t aID, jni::String::Param aText); + void Click(int32_t aID); + void Pivot(int32_t aID, int32_t aGranularity, bool aForward, bool aInclusive); + void ExploreByTouch(int32_t aID, float aX, float aY); + void NavigateText(int32_t aID, int32_t aGranularity, int32_t aStartOffset, + int32_t aEndOffset, bool aForward, bool aSelect); + void SetSelection(int32_t aID, int32_t aStart, int32_t aEnd); + void Cut(int32_t aID); + void Copy(int32_t aID); + void Paste(int32_t aID); + void StartNativeAccessibility(); + + // Event methods + void SendFocusEvent(AccessibleWrap* aAccessible); + void SendScrollingEvent(AccessibleWrap* aAccessible, int32_t aScrollX, + int32_t aScrollY, int32_t aMaxScrollX, + int32_t aMaxScrollY); + MOZ_CAN_RUN_SCRIPT + void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible); + void SendHoverEnterEvent(AccessibleWrap* aAccessible); + void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible, + int32_t aCaretOffset); + void SendTextTraversedEvent(AccessibleWrap* aAccessible, int32_t aStartOffset, + int32_t aEndOffset); + void SendTextChangedEvent(AccessibleWrap* aAccessible, const nsString& aStr, + int32_t aStart, uint32_t aLen, bool aIsInsert, + bool aFromUser); + void SendSelectedEvent(AccessibleWrap* aAccessible, bool aSelected); + void SendClickedEvent(AccessibleWrap* aAccessible, uint32_t aFlags); + void SendWindowContentChangedEvent(); + void SendWindowStateChangedEvent(AccessibleWrap* aAccessible); + void SendAnnouncementEvent(AccessibleWrap* aAccessible, + const nsString& aAnnouncement, uint16_t aPriority); + + // Cache methods + void ReplaceViewportCache( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData = nsTArray<BatchData>()); + + void ReplaceFocusPathCache( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData = nsTArray<BatchData>()); + + void UpdateCachedBounds( + const nsTArray<AccessibleWrap*>& aAccessibles, + const nsTArray<BatchData>& aData = nsTArray<BatchData>()); + + void UpdateAccessibleFocusBoundaries(AccessibleWrap* aFirst, + AccessibleWrap* aLast); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility) + + private: + ~SessionAccessibility() {} + + void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable); + RootAccessibleWrap* GetRoot(); + + jni::NativeWeakPtr<widget::GeckoViewSupport> mWindow; // Parent only + java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/TextLeafAccessibleWrap.h b/accessible/android/TextLeafAccessibleWrap.h new file mode 100644 index 0000000000..27de110160 --- /dev/null +++ b/accessible/android/TextLeafAccessibleWrap.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_TextLeafAccessibleWrap_h__ +#define mozilla_a11y_TextLeafAccessibleWrap_h__ + +#include "TextLeafAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class TextLeafAccessible TextLeafAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/TraversalRule.cpp b/accessible/android/TraversalRule.cpp new file mode 100644 index 0000000000..6bc0cda40c --- /dev/null +++ b/accessible/android/TraversalRule.cpp @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "TraversalRule.h" + +#include "mozilla/ArrayUtils.h" + +#include "Role.h" +#include "Accessible.h" +#include "Accessible-inl.h" +#include "HTMLListAccessible.h" +#include "SessionAccessibility.h" +#include "nsAccUtils.h" +#include "nsIAccessiblePivot.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +TraversalRule::TraversalRule() + : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT) {} + +TraversalRule::TraversalRule(int32_t aGranularity) + : mGranularity(aGranularity) {} + +uint16_t TraversalRule::Match(const AccessibleOrProxy& aAccOrProxy) { + MOZ_ASSERT(aAccOrProxy.IsAccessible(), + "Should only receive accessibles when processing on android."); + Accessible* aAccessible = aAccOrProxy.AsAccessible(); + uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE; + + if (nsAccUtils::MustPrune(aAccessible)) { + result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + uint64_t state = aAccessible->State(); + + if ((state & states::INVISIBLE) != 0) { + return result; + } + + if ((state & states::OPAQUE1) == 0) { + nsIFrame* frame = aAccessible->GetFrame(); + if (frame && frame->StyleEffects()->mOpacity == 0.0f) { + return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + } + + switch (mGranularity) { + case java::SessionAccessibility::HTML_GRANULARITY_LINK: + result |= LinkMatch(aAccessible); + break; + case java::SessionAccessibility::HTML_GRANULARITY_CONTROL: + result |= ControlMatch(aAccessible); + break; + case java::SessionAccessibility::HTML_GRANULARITY_SECTION: + result |= SectionMatch(aAccessible); + break; + case java::SessionAccessibility::HTML_GRANULARITY_HEADING: + result |= HeadingMatch(aAccessible); + break; + case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK: + result |= LandmarkMatch(aAccessible); + break; + default: + result |= DefaultMatch(aAccessible); + break; + } + + return result; +} + +bool TraversalRule::IsSingleLineage(Accessible* aAccessible) { + Accessible* child = aAccessible; + while (child) { + switch (child->ChildCount()) { + case 0: + return true; + case 1: + child = child->FirstChild(); + break; + case 2: + if (Accessible* bullet = + child->Parent()->IsHTMLListItem() + ? child->Parent()->AsHTMLListItem()->Bullet() + : nullptr) { + child = bullet->NextSibling(); + } else { + return false; + } + break; + default: + return false; + } + } + + return true; +} + +bool TraversalRule::IsListItemBullet(const Accessible* aAccessible) { + Accessible* parent = aAccessible->Parent(); + return parent && parent->IsHTMLListItem() && + parent->AsHTMLListItem()->Bullet() == aAccessible; +} + +bool TraversalRule::IsFlatSubtree(const Accessible* aAccessible) { + for (auto child = aAccessible->FirstChild(); child; + child = child->NextSibling()) { + roles::Role role = child->Role(); + if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) { + continue; + } + + if (child->ChildCount() > 0 || child->ActionCount() > 0) { + return false; + } + } + + return true; +} + +bool TraversalRule::HasName(const Accessible* aAccessible) { + nsAutoString name; + aAccessible->Name(name); + name.CompressWhitespace(); + return !name.IsEmpty(); +} + +uint16_t TraversalRule::LinkMatch(Accessible* aAccessible) { + if (aAccessible->Role() == roles::LINK && + (aAccessible->State() & states::LINKED) != 0) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::HeadingMatch(Accessible* aAccessible) { + if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::SectionMatch(Accessible* aAccessible) { + roles::Role role = aAccessible->Role(); + if (role == roles::HEADING || role == roles::LANDMARK || + aAccessible->LandmarkRole()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::LandmarkMatch(Accessible* aAccessible) { + if (aAccessible->LandmarkRole()) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::ControlMatch(Accessible* aAccessible) { + switch (aAccessible->Role()) { + case roles::PUSHBUTTON: + case roles::SPINBUTTON: + case roles::TOGGLE_BUTTON: + case roles::BUTTONDROPDOWN: + case roles::BUTTONDROPDOWNGRID: + case roles::COMBOBOX: + case roles::LISTBOX: + case roles::ENTRY: + case roles::PASSWORD_TEXT: + case roles::PAGETAB: + case roles::RADIOBUTTON: + case roles::RADIO_MENU_ITEM: + case roles::SLIDER: + case roles::CHECKBUTTON: + case roles::CHECK_MENU_ITEM: + case roles::SWITCH: + case roles::MENUITEM: + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + case roles::LINK: + return LinkMatch(aAccessible); + case roles::EDITCOMBOBOX: + if (aAccessible->NativeState() & states::EDITABLE) { + // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is + // editable. If it's a 1.1 combobox, the combobox is just a container; + // we want to stop on the textbox inside it, not the container. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + default: + break; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} + +uint16_t TraversalRule::DefaultMatch(Accessible* aAccessible) { + switch (aAccessible->Role()) { + case roles::COMBOBOX: + // We don't want to ignore the subtree because this is often + // where the list box hangs out. + return nsIAccessibleTraversalRule::FILTER_MATCH; + case roles::EDITCOMBOBOX: + if (aAccessible->NativeState() & states::EDITABLE) { + // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is + // editable. If it's a 1.1 combobox, the combobox is just a container; + // we want to stop on the textbox inside it. + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::TEXT_LEAF: + case roles::GRAPHIC: + // Nameless text leaves are boring, skip them. + if (HasName(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::STATICTEXT: + // Ignore list bullets + if (!IsListItemBullet(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH; + } + break; + case roles::HEADER: + case roles::HEADING: + case roles::COLUMNHEADER: + case roles::ROWHEADER: + case roles::STATUSBAR: + if ((aAccessible->ChildCount() > 0 || HasName(aAccessible)) && + (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible))) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::GRID_CELL: + if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::LISTITEM: + if (IsFlatSubtree(aAccessible) || IsSingleLineage(aAccessible)) { + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::LABEL: + if (IsFlatSubtree(aAccessible)) { + // Match if this is a label with text but no nested controls. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + } + break; + case roles::MENUITEM: + case roles::LINK: + case roles::PAGETAB: + case roles::PUSHBUTTON: + case roles::CHECKBUTTON: + case roles::RADIOBUTTON: + case roles::PROGRESSBAR: + case roles::BUTTONDROPDOWN: + case roles::BUTTONMENU: + case roles::CHECK_MENU_ITEM: + case roles::PASSWORD_TEXT: + case roles::RADIO_MENU_ITEM: + case roles::TOGGLE_BUTTON: + case roles::ENTRY: + case roles::KEY: + case roles::SLIDER: + case roles::SPINBUTTON: + case roles::OPTION: + case roles::SWITCH: + case roles::MATHML_MATH: + // Ignore the subtree, if there is one. So that we don't land on + // the same content that was already presented by its parent. + return nsIAccessibleTraversalRule::FILTER_MATCH | + nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE; + default: + break; + } + + return nsIAccessibleTraversalRule::FILTER_IGNORE; +} diff --git a/accessible/android/TraversalRule.h b/accessible/android/TraversalRule.h new file mode 100644 index 0000000000..46d8d264a4 --- /dev/null +++ b/accessible/android/TraversalRule.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef _TraversalRule_H_ +#define _TraversalRule_H_ + +#include "Pivot.h" + +namespace mozilla { +namespace a11y { + +class Accessible; + +/** + * Class represents a simple traversal rule. + */ +class TraversalRule final : public PivotRule { + public: + TraversalRule(); + explicit TraversalRule(int32_t aGranularity); + + ~TraversalRule() = default; + + virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override; + + private: + bool IsSingleLineage(Accessible* aAccessible); + + bool IsFlatSubtree(const Accessible* aAccessible); + + bool IsListItemBullet(const Accessible* aAccessible); + + bool HasName(const Accessible* aAccessible); + + uint16_t DefaultMatch(Accessible* aAccessible); + + uint16_t LinkMatch(Accessible* aAccessible); + + uint16_t HeadingMatch(Accessible* aAccessible); + + uint16_t ControlMatch(Accessible* aAccessible); + + uint16_t SectionMatch(Accessible* aAccessible); + + uint16_t LandmarkMatch(Accessible* aAccessible); + + int32_t mGranularity; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/XULListboxAccessibleWrap.h b/accessible/android/XULListboxAccessibleWrap.h new file mode 100644 index 0000000000..ad2c54b18d --- /dev/null +++ b/accessible/android/XULListboxAccessibleWrap.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_XULListboxAccessibleWrap_h__ +#define mozilla_a11y_XULListboxAccessibleWrap_h__ + +#include "XULListboxAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class XULListboxAccessible XULListboxAccessibleWrap; +typedef class XULListCellAccessible XULListCellAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/XULMenuAccessibleWrap.h b/accessible/android/XULMenuAccessibleWrap.h new file mode 100644 index 0000000000..81cdaf94ad --- /dev/null +++ b/accessible/android/XULMenuAccessibleWrap.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_XULMenuAccessibleWrap_h__ +#define mozilla_a11y_XULMenuAccessibleWrap_h__ + +#include "XULMenuAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/XULTreeGridAccessibleWrap.h b/accessible/android/XULTreeGridAccessibleWrap.h new file mode 100644 index 0000000000..e991d8f4f4 --- /dev/null +++ b/accessible/android/XULTreeGridAccessibleWrap.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_a11y_XULTreeGridAccessibleWrap_h__ +#define mozilla_a11y_XULTreeGridAccessibleWrap_h__ + +#include "XULTreeGridAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap; +typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/android/moz.build b/accessible/android/moz.build new file mode 100644 index 0000000000..456a9d7ec6 --- /dev/null +++ b/accessible/android/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.a11y += [ + "AccessibleWrap.h", + "HyperTextAccessibleWrap.h", + "SessionAccessibility.h", + "TraversalRule.h", +] + +SOURCES += [ + "AccessibleWrap.cpp", + "DocAccessibleWrap.cpp", + "Platform.cpp", + "ProxyAccessibleWrap.cpp", + "RootAccessibleWrap.cpp", + "SessionAccessibility.cpp", + "TraversalRule.cpp", +] + +LOCAL_INCLUDES += [ + "/accessible/base", + "/accessible/generic", + "/accessible/html", + "/accessible/ipc", + "/accessible/ipc/other", + "/accessible/xpcom", + "/accessible/xul", + "/dom/base", + "/widget", + "/widget/android", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] |