diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /accessible/mac/AccessibleWrap.mm | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/mac/AccessibleWrap.mm')
-rw-r--r-- | accessible/mac/AccessibleWrap.mm | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm new file mode 100644 index 0000000000..944e8bd059 --- /dev/null +++ b/accessible/mac/AccessibleWrap.mm @@ -0,0 +1,413 @@ +/* clang-format off */ +/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* clang-format on */ +/* 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 "DocAccessibleWrap.h" +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" +#include "nsUnicharUtils.h" + +#include "LocalAccessible-inl.h" +#include "nsAccUtils.h" +#include "Role.h" +#include "TextRange.h" +#include "gfxPlatform.h" + +#import "MOXLandmarkAccessibles.h" +#import "MOXMathAccessibles.h" +#import "MOXTextMarkerDelegate.h" +#import "MOXWebAreaAccessible.h" +#import "mozAccessible.h" +#import "mozActionElements.h" +#import "mozHTMLAccessible.h" +#import "mozSelectableElements.h" +#import "mozTableAccessible.h" +#import "mozTextAccessible.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) + : LocalAccessible(aContent, aDoc), + mNativeObject(nil), + mNativeInited(false) { + if (aContent && aContent->IsElement() && aDoc) { + // Check if this accessible is a live region and queue it + // it for dispatching an event after it has been inserted. + DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc); + static const dom::Element::AttrValuesArray sLiveRegionValues[] = { + nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr}; + int32_t attrValue = nsAccUtils::FindARIAAttrValueIn( + aContent->AsElement(), nsGkAtoms::aria_live, sLiveRegionValues, + eIgnoreCase); + if (attrValue == 0) { + // aria-live is "off", do nothing. + } else if (attrValue > 0) { + // aria-live attribute is polite or assertive. It's live! + doc->QueueNewLiveRegion(this); + } else if (const nsRoleMapEntry* roleMap = + aria::GetRoleMap(aContent->AsElement())) { + // aria role defines it as a live region. It's live! + if (roleMap->liveAttRule == ePoliteLiveAttr || + roleMap->liveAttRule == eAssertiveLiveAttr) { + doc->QueueNewLiveRegion(this); + } + } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute( + aContent, nsGkAtoms::aria_live)) { + // HTML element defines it as a live region. It's live! + if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) { + doc->QueueNewLiveRegion(this); + } + } + } +} + +AccessibleWrap::~AccessibleWrap() {} + +mozAccessible* AccessibleWrap::GetNativeObject() { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (!mNativeInited && !mNativeObject) { + // We don't creat OSX accessibles for xul tooltips, defunct accessibles, + // <br> (whitespace) elements, or pruned children. + // + // To maintain a scripting environment where the XPCOM accessible hierarchy + // look the same on all platforms, we still let the C++ objects be created + // though. + if (!IsXULTooltip() && !IsDefunct() && Role() != roles::WHITESPACE) { + mNativeObject = [[GetNativeType() alloc] initWithAccessible:this]; + } + } + + mNativeInited = true; + + return mNativeObject; + + NS_OBJC_END_TRY_BLOCK_RETURN(nil); +} + +void AccessibleWrap::GetNativeInterface(void** aOutInterface) { + *aOutInterface = static_cast<void*>(GetNativeObject()); +} + +// overridden in subclasses to create the right kind of object. by default we +// create a generic 'mozAccessible' node. +Class AccessibleWrap::GetNativeType() { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (IsXULTabpanels()) { + return [mozPaneAccessible class]; + } + + if (IsTable()) { + return [mozTableAccessible class]; + } + + if (IsTableRow()) { + return [mozTableRowAccessible class]; + } + + if (IsTableCell()) { + return [mozTableCellAccessible class]; + } + + if (IsDoc()) { + return [MOXWebAreaAccessible class]; + } + + return GetTypeFromRole(Role()); + + NS_OBJC_END_TRY_BLOCK_RETURN(nil); +} + +// this method is very important. it is fired when an accessible object "dies". +// after this point the object might still be around (because some 3rd party +// still has a ref to it), but it is in fact 'dead'. +void AccessibleWrap::Shutdown() { + // this ensure we will not try to re-create the native object. + mNativeInited = true; + + // we really intend to access the member directly. + if (mNativeObject) { + [mNativeObject expire]; + [mNativeObject release]; + mNativeObject = nil; + } + + LocalAccessible::Shutdown(); +} + +nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + nsresult rv = LocalAccessible::HandleAccEvent(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + if (IsDefunct()) { + // The accessible can become defunct after their events are handled. + return NS_OK; + } + + uint32_t eventType = aEvent->GetEventType(); + + if (eventType == nsIAccessibleEvent::EVENT_SHOW) { + DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document()); + doc->ProcessNewLiveRegions(); + } + + if (IPCAccessibilityActive()) { + return NS_OK; + } + + LocalAccessible* eventTarget = nullptr; + + switch (eventType) { + case nsIAccessibleEvent::EVENT_SELECTION: + case nsIAccessibleEvent::EVENT_SELECTION_ADD: + case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { + AccSelChangeEvent* selEvent = downcast_accEvent(aEvent); + // The "widget" is the selected widget's container. In OSX + // it is the target of the selection changed event. + eventTarget = selEvent->Widget(); + break; + } + case nsIAccessibleEvent::EVENT_TEXT_INSERTED: + case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { + LocalAccessible* acc = aEvent->GetAccessible(); + // If there is a text input ancestor, use it as the event source. + while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) { + acc = acc->LocalParent(); + } + eventTarget = acc ? acc : aEvent->GetAccessible(); + break; + } + default: + eventTarget = aEvent->GetAccessible(); + break; + } + + mozAccessible* nativeAcc = nil; + eventTarget->GetNativeInterface((void**)&nativeAcc); + if (!nativeAcc) { + return NS_ERROR_FAILURE; + } + + switch (eventType) { + case nsIAccessibleEvent::EVENT_STATE_CHANGE: { + AccStateChangeEvent* event = downcast_accEvent(aEvent); + [nativeAcc stateChanged:event->GetState() + isEnabled:event->IsStateEnabled()]; + break; + } + + case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: { + MOXTextMarkerDelegate* delegate = + [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()]; + AccTextSelChangeEvent* event = downcast_accEvent(aEvent); + AutoTArray<TextRange, 1> ranges; + event->SelectionRanges(&ranges); + + if (ranges.Length()) { + // Cache selection in delegate. + [delegate setSelectionFrom:ranges[0].StartContainer() + at:ranges[0].StartOffset() + to:ranges[0].EndContainer() + at:ranges[0].EndOffset()]; + } + + [nativeAcc handleAccessibleEvent:eventType]; + break; + } + + case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { + AccCaretMoveEvent* event = downcast_accEvent(aEvent); + int32_t caretOffset = event->GetCaretOffset(); + MOXTextMarkerDelegate* delegate = + [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()]; + [delegate setCaretOffset:eventTarget + at:caretOffset + moveGranularity:event->GetGranularity()]; + if (event->IsSelectionCollapsed()) { + // If the selection is collapsed, invalidate our text selection cache. + [delegate setSelectionFrom:eventTarget + at:caretOffset + to:eventTarget + at:caretOffset]; + } + + if (mozTextAccessible* textAcc = static_cast<mozTextAccessible*>( + [nativeAcc moxEditableAncestor])) { + [textAcc + handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED]; + } else { + [nativeAcc + handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED]; + } + break; + } + + case nsIAccessibleEvent::EVENT_TEXT_INSERTED: + case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { + AccTextChangeEvent* tcEvent = downcast_accEvent(aEvent); + [nativeAcc handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString( + tcEvent->ModifiedText()) + inserted:tcEvent->IsTextInserted() + inContainer:aEvent->GetAccessible() + at:tcEvent->GetStartOffset()]; + break; + } + + case nsIAccessibleEvent::EVENT_ALERT: + case nsIAccessibleEvent::EVENT_FOCUS: + case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: + case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: + case nsIAccessibleEvent::EVENT_MENUPOPUP_START: + case nsIAccessibleEvent::EVENT_MENUPOPUP_END: + case nsIAccessibleEvent::EVENT_REORDER: + case nsIAccessibleEvent::EVENT_SELECTION: + case nsIAccessibleEvent::EVENT_SELECTION_ADD: + case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: + case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED: + case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED: + case nsIAccessibleEvent::EVENT_NAME_CHANGE: + case nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED: + case nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED: + [nativeAcc handleAccessibleEvent:eventType]; + break; + + default: + break; + } + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +bool AccessibleWrap::ApplyPostFilter(const EWhichPostFilter& aSearchKey, + const nsString& aSearchText) { + // We currently only support the eContainsText post filter. + MOZ_ASSERT(aSearchKey == EWhichPostFilter::eContainsText, + "Only search text supported"); + nsAutoString name; + Name(name); + return CaseInsensitiveFindInReadable(aSearchText, name); +} + +//////////////////////////////////////////////////////////////////////////////// +// AccessibleWrap protected + +Class a11y::GetTypeFromRole(roles::Role aRole) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + switch (aRole) { + case roles::COMBOBOX: + return [mozPopupButtonAccessible class]; + + case roles::PUSHBUTTON: + return [mozButtonAccessible class]; + + case roles::PAGETAB: + return [mozTabAccessible class]; + + case roles::CHECKBUTTON: + case roles::TOGGLE_BUTTON: + case roles::SWITCH: + case roles::CHECK_MENU_ITEM: + return [mozCheckboxAccessible class]; + + case roles::RADIOBUTTON: + case roles::RADIO_MENU_ITEM: + return [mozRadioButtonAccessible class]; + + case roles::SPINBUTTON: + case roles::SLIDER: + return [mozIncrementableAccessible class]; + + case roles::HEADING: + return [mozHeadingAccessible class]; + + case roles::PAGETABLIST: + return [mozTabGroupAccessible class]; + + case roles::ENTRY: + case roles::CAPTION: + case roles::ACCEL_LABEL: + case roles::EDITCOMBOBOX: + case roles::PASSWORD_TEXT: + // normal textfield (static or editable) + return [mozTextAccessible class]; + + case roles::TEXT_LEAF: + case roles::STATICTEXT: + return [mozTextLeafAccessible class]; + + case roles::LANDMARK: + return [MOXLandmarkAccessible class]; + + case roles::LINK: + return [mozLinkAccessible class]; + + case roles::LISTBOX: + return [mozListboxAccessible class]; + + case roles::LISTITEM: + return [MOXListItemAccessible class]; + + case roles::OPTION: { + return [mozOptionAccessible class]; + } + + case roles::RICH_OPTION: { + return [mozSelectableChildAccessible class]; + } + + case roles::COMBOBOX_LIST: + case roles::MENUBAR: + case roles::MENUPOPUP: { + return [mozMenuAccessible class]; + } + + case roles::COMBOBOX_OPTION: + case roles::PARENT_MENUITEM: + case roles::MENUITEM: { + return [mozMenuItemAccessible class]; + } + + case roles::MATHML_ROOT: + return [MOXMathRootAccessible class]; + + case roles::MATHML_SQUARE_ROOT: + return [MOXMathSquareRootAccessible class]; + + case roles::MATHML_FRACTION: + return [MOXMathFractionAccessible class]; + + case roles::MATHML_SUB: + case roles::MATHML_SUP: + case roles::MATHML_SUB_SUP: + return [MOXMathSubSupAccessible class]; + + case roles::MATHML_UNDER: + case roles::MATHML_OVER: + case roles::MATHML_UNDER_OVER: + return [MOXMathUnderOverAccessible class]; + + case roles::OUTLINE: + case roles::TREE_TABLE: + return [mozOutlineAccessible class]; + + case roles::OUTLINEITEM: + return [mozOutlineRowAccessible class]; + + default: + return [mozAccessible class]; + } + + return nil; + + NS_OBJC_END_TRY_BLOCK_RETURN(nil); +} |