blob: 3085f996949c14e7b45917a9f8ad7b57b1006f7f [file] [log] [blame]
/*
* Copyright (C) 2003-2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "AXGeometryManager.h"
#include "AXIsolatedTree.h"
#include "AXTextMarker.h"
#include "AXTextStateChangeIntent.h"
#include "AXTreeStore.h"
#include "AccessibilityObject.h"
#include "SimpleRange.h"
#include "StyleChange.h"
#include "Timer.h"
#include "VisibleUnits.h"
#include <limits.h>
#include <wtf/Compiler.h>
#include <wtf/Deque.h>
#include <wtf/HashMap.h>
#include <wtf/HashSet.h>
#include <wtf/ListHashSet.h>
#include <wtf/WeakHashMap.h>
#include <wtf/WeakHashSet.h>
#include <wtf/text/MakeString.h>
OBJC_CLASS NSMutableArray;
namespace WTF {
class TextStream;
}
namespace WebCore {
class AXRemoteFrame;
class AccessibilityNodeObject;
class AccessibilityRenderObject;
class AccessibilityTable;
class AccessibilityTableCell;
class Document;
class HTMLAreaElement;
class HTMLDetailsElement;
class HTMLTableElement;
class HTMLTextFormControlElement;
class Node;
class Page;
class RenderBlock;
class RenderObject;
class RenderStyle;
class RenderText;
class RenderWidget;
class Scrollbar;
class ScrollView;
class VisiblePosition;
class Widget;
enum class AXStreamOptions : uint8_t;
struct CharacterOffset {
RefPtr<Node> node;
int startIndex;
int offset;
int remainingOffset;
CharacterOffset(Node* n = nullptr, int startIndex = 0, int offset = 0, int remaining = 0)
: node(n)
, startIndex(startIndex)
, offset(offset)
, remainingOffset(remaining)
{ }
int remaining() const { return remainingOffset; }
bool isNull() const { return !node; }
bool isEqual(const CharacterOffset& other) const
{
if (isNull() || other.isNull())
return false;
return node == other.node && startIndex == other.startIndex && offset == other.offset;
}
String debugDescription()
{
return makeString("CharacterOffset {node: "_s, node ? node->debugDescription() : "null"_s, ", startIndex: "_s, startIndex, ", offset: "_s, offset, ", remainingOffset: "_s, remainingOffset, '}');
}
};
DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AXComputedObjectAttributeCache);
class AXComputedObjectAttributeCache {
WTF_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(AXComputedObjectAttributeCache);
public:
AccessibilityObjectInclusion getIgnored(AXID) const;
void setIgnored(AXID, AccessibilityObjectInclusion);
private:
struct CachedAXObjectAttributes {
CachedAXObjectAttributes()
: ignored(AccessibilityObjectInclusion::DefaultBehavior)
{ }
AccessibilityObjectInclusion ignored;
};
HashMap<AXID, CachedAXObjectAttributes> m_idMapping;
};
struct VisiblePositionIndex {
int value = -1;
RefPtr<ContainerNode> scope;
};
struct VisiblePositionIndexRange {
VisiblePositionIndex startIndex;
VisiblePositionIndex endIndex;
bool isNull() const { return startIndex.value == -1 || endIndex.value == -1; }
};
struct AXTreeData {
String liveTree;
String isolatedTree;
};
#if PLATFORM(COCOA)
struct AXTextChangeContext {
AXTextStateChangeIntent intent;
String deletedText;
String insertedText;
VisibleSelection selection;
};
#endif // PLATFORM(COCOA)
class AccessibilityReplacedText {
public:
AccessibilityReplacedText() = default;
AccessibilityReplacedText(const VisibleSelection&);
void postTextStateChangeNotification(AXObjectCache*, AXTextEditType, const String&, const VisibleSelection&);
const VisiblePositionIndexRange& replacedRange() { return m_replacedRange; }
protected:
String m_replacedText;
VisiblePositionIndexRange m_replacedRange;
};
#define WEBCORE_AXNOTIFICATION_KEYS_DEFAULT(macro) \
macro(AccessKeyChanged) \
macro(ActiveDescendantChanged) \
macro(AnnouncementRequested) \
macro(AutocorrectionOccured) \
macro(AutofillTypeChanged) \
macro(ARIAColumnIndexChanged) \
macro(ARIARoleDescriptionChanged) \
macro(ARIARowIndexChanged) \
macro(BrailleLabelChanged) \
macro(BrailleRoleDescriptionChanged) \
macro(CellSlotsChanged) \
macro(CheckedStateChanged) \
macro(ChildrenChanged) \
macro(ColumnCountChanged) \
macro(ColumnIndexChanged) \
macro(ColumnSpanChanged) \
macro(CommandChanged) \
macro(CommandForChanged) \
macro(ContentEditableAttributeChanged) \
macro(ControlledObjectsChanged) \
macro(CurrentStateChanged) \
macro(DatetimeChanged) \
macro(DescribedByChanged) \
macro(DisabledStateChanged) \
macro(DraggableStateChanged) \
macro(DropEffectChanged) \
macro(ExtendedDescriptionChanged) \
macro(FlowToChanged) \
macro(FocusableStateChanged) \
macro(FocusedUIElementChanged) \
macro(FontChanged) \
macro(FrameLoadComplete) \
macro(GrabbedStateChanged) \
macro(HasPopupChanged) \
macro(IdAttributeChanged) \
macro(ImageOverlayChanged) \
macro(InertOrVisibilityChanged) \
macro(InputTypeChanged) \
macro(IsAtomicChanged) \
macro(IsEditableWebAreaChanged) \
macro(KeyShortcutsChanged) \
macro(LabelChanged) \
macro(LanguageChanged) \
macro(LayoutComplete) \
macro(LevelChanged) \
macro(LoadComplete) \
macro(NameChanged) \
macro(NewDocumentLoadComplete) \
macro(PageScrolled) \
macro(PlaceholderChanged) \
macro(PopoverTargetChanged) \
macro(PositionInSetChanged) \
macro(RoleChanged) \
macro(RowIndexChanged) \
macro(RowSpanChanged) \
macro(CellScopeChanged) \
macro(SelectedChildrenChanged) \
macro(SelectedCellsChanged) \
macro(SelectedStateChanged) \
macro(SelectedTextChanged) \
macro(SetSizeChanged) \
macro(TextColorChanged) \
macro(TextCompositionBegan) \
macro(TextCompositionEnded) \
macro(URLChanged) \
macro(ValueChanged) \
macro(VisibilityChanged) \
macro(VisitedStateChanged) \
macro(ScrolledToAnchor) \
macro(LiveRegionCreated) \
macro(LiveRegionChanged) \
macro(LiveRegionRelevantChanged) \
macro(LiveRegionStatusChanged) \
macro(MaximumValueChanged) \
macro(MenuListItemSelected) \
macro(MenuListValueChanged) \
macro(MenuClosed) \
macro(MenuOpened) \
macro(MinimumValueChanged) \
macro(MultiSelectableStateChanged) \
macro(OrientationChanged) \
macro(RowCountChanged) \
macro(RowCollapsed) \
macro(RowExpanded) \
macro(ExpandedChanged) \
macro(InvalidStatusChanged) \
macro(PressDidSucceed) \
macro(PressDidFail) \
macro(PressedStateChanged) \
macro(ReadOnlyStatusChanged) \
macro(RequiredStatusChanged) \
macro(SortDirectionChanged) \
macro(SpeakAsChanged) \
macro(TextChanged) \
macro(TextCompositionChanged) \
macro(TextUnderElementChanged) \
macro(TextSecurityChanged) \
macro(ElementBusyChanged) \
macro(DraggingStarted) \
macro(DraggingEnded) \
macro(DraggingEnteredDropZone) \
macro(DraggingDropped) \
macro(DraggingExitedDropZone) \
#if ENABLE(AX_THREAD_TEXT_APIS)
#define WEBCORE_AXNOTIFICATION_KEYS(macro) \
WEBCORE_AXNOTIFICATION_KEYS_DEFAULT(macro) \
macro(TextRunsChanged)
#else
#define WEBCORE_AXNOTIFICATION_KEYS(macro) \
WEBCORE_AXNOTIFICATION_KEYS_DEFAULT(macro)
#endif
enum class AXNotification {
#define WEBCORE_DEFINE_AXNOTIFICATION_ENUM(name) name,
WEBCORE_AXNOTIFICATION_KEYS(WEBCORE_DEFINE_AXNOTIFICATION_ENUM)
#undef WEBCORE_DEFINE_AXNOTIFICATION_ENUM
};
enum class AXLoadingEvent : uint8_t {
Started,
Reloaded,
Failed,
Finished
};
#if !PLATFORM(COCOA)
enum class AXTextChange : uint8_t { Inserted, Deleted, Replaced, AttributesChanged };
#endif
enum class PostTarget { Element, ObservableParent };
DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(AXObjectCache);
class AXObjectCache final : public CanMakeWeakPtr<AXObjectCache>, public CanMakeCheckedPtr<AXObjectCache>
, public AXTreeStore<AXObjectCache> {
WTF_MAKE_NONCOPYABLE(AXObjectCache);
WTF_MAKE_TZONE_ALLOCATED(AXObjectCache);
WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(AXObjectCache);
friend class AXIsolatedTree;
friend class AXTextMarker;
friend WTF::TextStream& operator<<(WTF::TextStream&, AXObjectCache&);
public:
explicit AXObjectCache(Page&, Document*);
~AXObjectCache();
// Returns the root object for a specific frame.
WEBCORE_EXPORT AXCoreObject* rootObjectForFrame(LocalFrame&);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
WEBCORE_EXPORT void buildIsolatedTreeIfNeeded();
#endif
// Creation/retrieval of AX objects associated with a DOM or RenderTree object.
inline AccessibilityObject* getOrCreate(RenderObject* renderer)
{
return renderer ? getOrCreate(*renderer) : nullptr;
}
AccessibilityObject* getOrCreate(RenderObject&);
inline AccessibilityObject* getOrCreate(Widget* widget)
{
return widget ? getOrCreate(*widget) : nullptr;
}
AccessibilityObject* getOrCreate(Widget&);
enum class IsPartOfRelation : bool { No, Yes };
inline AccessibilityObject* getOrCreate(Node* node, IsPartOfRelation isPartOfRelation = IsPartOfRelation::No)
{
return node ? getOrCreate(*node, isPartOfRelation) : nullptr;
}
WEBCORE_EXPORT AccessibilityObject* getOrCreate(Node&, IsPartOfRelation = IsPartOfRelation::No);
// used for objects without backing elements
AccessibilityObject* create(AccessibilityRole);
// Will only return the AccessibilityObject if it already exists.
inline AccessibilityObject* get(RenderObject* renderer)
{
return renderer ? get(*renderer) : nullptr;
}
inline AccessibilityObject* get(RenderObject& renderer) const
{
auto axID = m_renderObjectMapping.getOptional(renderer);
return axID ? m_objects.get(*axID) : nullptr;
}
inline AccessibilityObject* get(Widget* widget) const
{
return widget ? get(*widget) : nullptr;
}
inline AccessibilityObject* get(Widget& widget) const
{
auto axID = m_widgetObjectMapping.getOptional(widget);
return axID ? m_objects.get(*axID) : nullptr;
}
inline AccessibilityObject* get(Node* node) const
{
return node ? get(*node) : nullptr;
}
AccessibilityObject* get(Node&) const;
void remove(RenderObject&);
void remove(Node&);
void remove(Widget&);
void remove(std::optional<AXID>);
#if !PLATFORM(COCOA) && !USE(ATSPI)
void detachWrapper(AXCoreObject*, AccessibilityDetachmentType);
#endif
private:
using DOMObjectVariant = Variant<std::nullptr_t, RenderObject*, Node*, Widget*>;
void cacheAndInitializeWrapper(AccessibilityObject&, DOMObjectVariant = nullptr);
void attachWrapper(AccessibilityObject&);
public:
void onPageActivityStateChange(OptionSet<ActivityState>);
void setPageActivityState(OptionSet<ActivityState> state) { m_pageActivityState = state; }
OptionSet<ActivityState> pageActivityState() const { return m_pageActivityState; }
inline void childrenChanged(Node& node)
{
if (!node.renderer()) {
// We only need to handle DOM changes for things that don't have renderers.
// If something does have a renderer, we would already get children-changed notifications
// from the render tree.
childrenChanged(get(node));
}
}
void childrenChanged(RenderObject&, RenderObject* newChild = nullptr);
void childrenChanged(AccessibilityObject*);
void onDragElementChanged(Element* oldElement, Element* newElement);
void onEventListenerAdded(Node&, const AtomString& eventType);
void onEventListenerRemoved(Node&, const AtomString& eventType);
void onFocusChange(Element* oldElement, Element* newElement);
void onInertOrVisibilityChange(RenderElement&);
void onPopoverToggle(const HTMLElement&);
void onScrollbarFrameRectChange(const Scrollbar&);
void onSelectedChanged(Element&);
void onSelectedTextChanged(const VisiblePositionRange&, AccessibilityObject* = nullptr);
void onSlottedContentChange(const HTMLSlotElement&);
void onStyleChange(Element&, OptionSet<Style::Change>, const RenderStyle* oldStyle, const RenderStyle* newStyle);
void onStyleChange(RenderText&, StyleDifference, const RenderStyle* oldStyle, const RenderStyle& newStyle);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
// Returns true if the font changes, requiring all descendants to update the Font property.
bool onFontChange(Element&, const RenderStyle*, const RenderStyle*);
// Returns true if the text color changes, requiring all descendants to update the TextColor property.
bool onTextColorChange(Element&, const RenderStyle*, const RenderStyle*);
#endif
void onTextSecurityChanged(HTMLInputElement&);
void onTitleChange(Document&);
void onValidityChange(Element&);
void onTextCompositionChange(Node&, CompositionState, bool, const String&, size_t, bool);
void onWidgetVisibilityChanged(RenderWidget&);
void valueChanged(Element&);
void checkedStateChanged(Element&);
void autofillTypeChanged(HTMLInputElement&);
void handleRoleChanged(AccessibilityObject&, AccessibilityRole previousRole);
void handleReferenceTargetChanged();
void handlePageEditibilityChanged(Document&);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void columnIndexChanged(AccessibilityObject&);
void rowIndexChanged(AccessibilityObject&);
#endif
// Called when a RenderObject is created for an Element. Depending on the
// presence of a RenderObject, we may have instatiated an AXRenderObject or
// an AXNodeObject. This occurs when an Element with no renderer is
// re-parented into a subtree that does have a renderer.
void onRendererCreated(Element&);
// Similar to the above, but for when a RenderText is created for a Text node.
// We may have already created an AccessibilityNodeObject for the Text, so this
// method allows us to make any appropriate changes now that the Text has a renderer.
void onRendererCreated(Text&);
#if PLATFORM(MAC)
void onDocumentRenderTreeCreation(const Document&);
#endif
#if ENABLE(AX_THREAD_TEXT_APIS)
void onTextRunsChanged(const RenderObject&);
#endif
void updateLoadingProgress(double);
void loadingFinished() { updateLoadingProgress(1); }
double loadingProgress() const { return m_loadingProgress; }
struct AttributeChange {
WeakPtr<Element, WeakPtrImplWithEventTargetData> element { nullptr };
QualifiedName attrName;
AtomString oldValue;
AtomString newValue;
};
using DeferredCollection = Variant<HashMap<Element*, String>
, HashSet<AXID>
, ListHashSet<Node*>
, ListHashSet<Ref<AccessibilityObject>>
, Vector<AttributeChange>
, Vector<std::pair<Node*, Node*>>
, WeakHashSet<Element, WeakPtrImplWithEventTargetData>
, WeakHashSet<HTMLTableElement, WeakPtrImplWithEventTargetData>
, WeakHashSet<AccessibilityObject>
, WeakHashSet<AccessibilityTable>
, WeakHashSet<AccessibilityTableCell>
, WeakListHashSet<Node, WeakPtrImplWithEventTargetData>
, WeakListHashSet<Element, WeakPtrImplWithEventTargetData>
, WeakHashMap<Element, String, WeakPtrImplWithEventTargetData>>;
void deferFocusedUIElementChangeIfNeeded(Node* oldFocusedNode, Node* newFocusedNode);
void deferModalChange(Element&);
void deferMenuListValueChange(Element*);
void deferElementAddedOrRemoved(Element*);
void handleScrolledToAnchor(const Node& anchorNode);
void onScrollbarUpdate(ScrollView&);
void onRemoteFrameInitialized(AXRemoteFrame&);
bool isRetrievingCurrentModalNode() { return m_isRetrievingCurrentModalNode; }
Node* modalNode();
void deferAttributeChangeIfNeeded(Element&, const QualifiedName&, const AtomString&, const AtomString&);
void recomputeIsIgnored(RenderObject&);
void recomputeIsIgnored(Node*);
static void enableAccessibility();
static void disableAccessibility();
static bool forceDeferredSpellChecking();
static void setForceDeferredSpellChecking(bool);
#if PLATFORM(MAC)
static bool shouldSpellCheck();
#else
static bool shouldSpellCheck() { return true; }
#endif
WEBCORE_EXPORT AccessibilityObject* focusedObjectForPage(const Page*);
// Enhanced user interface accessibility can be toggled by the assistive technology.
WEBCORE_EXPORT static void setEnhancedUserInterfaceAccessibility(bool flag);
static bool accessibilityEnabled();
WEBCORE_EXPORT static bool accessibilityEnhancedUserInterfaceEnabled();
#if ENABLE(AX_THREAD_TEXT_APIS)
static bool useAXThreadTextApis() { return gAccessibilityThreadTextApisEnabled && !isMainThread(); }
static bool shouldCreateAXThreadCompatibleMarkers() { return gAccessibilityThreadTextApisEnabled && isIsolatedTreeEnabled(); }
#endif
static bool forceInitialFrameCaching() { return gForceInitialFrameCaching; }
WEBCORE_EXPORT static void setForceInitialFrameCaching(bool);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
static bool shouldServeInitialCachedFrame();
#endif
const Element* rootAXEditableElement(const Node*);
bool elementIsTextControl(const Element&);
AccessibilityObject* objectForID(const AXID id) const { return m_objects.get(id); }
template<typename U> Vector<Ref<AXCoreObject>> objectsForIDs(const U&) const;
Node* nodeForID(std::optional<AXID>) const;
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void onPaint(const RenderObject&, IntRect&&) const;
void onPaint(const Widget&, IntRect&&) const;
#else
NO_RETURN_DUE_TO_ASSERT void onPaint(const RenderObject&, IntRect&&) const { ASSERT_NOT_REACHED(); }
NO_RETURN_DUE_TO_ASSERT void onPaint(const Widget&, IntRect&&) const { ASSERT_NOT_REACHED(); }
#endif
// Text marker utilities.
std::optional<TextMarkerData> textMarkerDataForVisiblePosition(const VisiblePosition&, TextMarkerOrigin = TextMarkerOrigin::Unknown);
TextMarkerData textMarkerDataForCharacterOffset(const CharacterOffset&, TextMarkerOrigin = TextMarkerOrigin::Unknown);
TextMarkerData textMarkerDataForNextCharacterOffset(const CharacterOffset&);
AXTextMarker nextTextMarker(const AXTextMarker&);
TextMarkerData textMarkerDataForPreviousCharacterOffset(const CharacterOffset&);
AXTextMarker previousTextMarker(const AXTextMarker&);
VisiblePosition visiblePositionForTextMarkerData(const TextMarkerData&);
CharacterOffset characterOffsetForTextMarkerData(const TextMarkerData&);
// Use ignoreNextNodeStart/ignorePreviousNodeEnd to determine the behavior when we are at node boundary.
CharacterOffset nextCharacterOffset(const CharacterOffset&, bool ignoreNextNodeStart = true);
CharacterOffset previousCharacterOffset(const CharacterOffset&, bool ignorePreviousNodeEnd = true);
TextMarkerData startOrEndTextMarkerDataForRange(const SimpleRange&, bool);
CharacterOffset startOrEndCharacterOffsetForRange(const SimpleRange&, bool, bool enterTextControls = false);
AccessibilityObject* objectForTextMarkerData(const TextMarkerData&);
std::optional<SimpleRange> rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&);
static SimpleRange rangeForNodeContents(Node&);
static unsigned lengthForRange(const SimpleRange&);
// Word boundary
CharacterOffset nextWordEndCharacterOffset(const CharacterOffset&);
CharacterOffset previousWordStartCharacterOffset(const CharacterOffset&);
std::optional<SimpleRange> leftWordRange(const CharacterOffset&);
std::optional<SimpleRange> rightWordRange(const CharacterOffset&);
// Paragraph
std::optional<SimpleRange> paragraphForCharacterOffset(const CharacterOffset&);
CharacterOffset nextParagraphEndCharacterOffset(const CharacterOffset&);
CharacterOffset previousParagraphStartCharacterOffset(const CharacterOffset&);
// Sentence
std::optional<SimpleRange> sentenceForCharacterOffset(const CharacterOffset&);
CharacterOffset nextSentenceEndCharacterOffset(const CharacterOffset&);
CharacterOffset previousSentenceStartCharacterOffset(const CharacterOffset&);
// Bounds
CharacterOffset characterOffsetForPoint(const IntPoint&, AXCoreObject*);
IntRect absoluteCaretBoundsForCharacterOffset(const CharacterOffset&);
CharacterOffset characterOffsetForBounds(const IntRect&, bool);
// Lines
CharacterOffset endCharacterOffsetOfLine(const CharacterOffset&);
CharacterOffset startCharacterOffsetOfLine(const CharacterOffset&);
// Index
CharacterOffset characterOffsetForIndex(int, const AXCoreObject*);
void postNotification(RenderObject*, AXNotification, PostTarget = PostTarget::Element);
void postNotification(Node*, AXNotification, PostTarget = PostTarget::Element);
void postNotification(AccessibilityObject*, Document*, AXNotification, PostTarget = PostTarget::Element);
void postNotification(AccessibilityObject* object, AXNotification notification)
{
if (object)
postNotification(*object, notification);
}
void postNotification(AccessibilityObject&, AXNotification);
// Requests clients to announce to the user the given message in the way they deem appropriate.
WEBCORE_EXPORT void announce(const String&);
#ifndef NDEBUG
void showIntent(const AXTextStateChangeIntent&);
#endif
void setTextSelectionIntent(const AXTextStateChangeIntent&);
void setIsSynchronizingSelection(bool);
void postTextStateChangeNotification(Node*, AXTextEditType, const String&, const VisiblePosition&);
void postTextReplacementNotification(Node*, AXTextEditType deletionType, const String& deletedText, AXTextEditType insertionType, const String& insertedText, const VisiblePosition&);
void postTextReplacementNotificationForTextControl(HTMLTextFormControlElement&, const String& deletedText, const String& insertedText);
void postTextStateChangeNotification(Node*, const AXTextStateChangeIntent&, const VisibleSelection&);
void postTextStateChangeNotification(const Position&, const AXTextStateChangeIntent&, const VisibleSelection&);
void postLiveRegionChangeNotification(AccessibilityObject&);
void frameLoadingEventNotification(LocalFrame*, AXLoadingEvent);
void prepareForDocumentDestruction(const Document&);
void startCachingComputedObjectAttributesUntilTreeMutates();
void stopCachingComputedObjectAttributes();
AXComputedObjectAttributeCache* computedObjectAttributeCache() { return m_computedObjectAttributeCache.get(); }
Document* document() const { return m_document.get(); }
RefPtr<Document> protectedDocument() const;
constexpr const std::optional<PageIdentifier>& pageID() const { return m_pageID; }
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void objectBecameIgnored(const AccessibilityObject& object)
{
if (RefPtr tree = AXIsolatedTree::treeForPageID(m_pageID))
tree->objectBecameIgnored(object);
}
void objectBecameUnignored(const AccessibilityObject& object)
{
#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)
if (RefPtr tree = AXIsolatedTree::treeForPageID(m_pageID))
tree->objectBecameUnignored(object);
#else
UNUSED_PARAM(object);
#endif // ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)
}
#endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE)
#if PLATFORM(MAC)
static void setShouldRepostNotificationsForTests(bool);
#endif
void deferRecomputeIsIgnoredIfNeeded(Element*);
void deferRecomputeIsIgnored(Element*);
void deferRecomputeTableIsExposed(Element*);
void deferRecomputeTableCellSlots(AccessibilityTable&);
void deferTextChangedIfNeeded(Node*);
void deferSelectedChildrenChangedIfNeeded(Element&);
WEBCORE_EXPORT void performDeferredCacheUpdate(ForceLayout);
void deferTextReplacementNotificationForTextControl(HTMLTextFormControlElement&, const String& previousValue);
std::optional<SimpleRange> rangeMatchesTextNearRange(const SimpleRange&, const String&);
static ASCIILiteral notificationPlatformName(AXNotification);
AXTreeData treeData(std::optional<OptionSet<AXStreamOptions>> = std::nullopt);
enum class UpdateRelations : bool { No, Yes };
// Returns the IDs of the objects that relate to the given object with the specified relationship.
std::optional<ListHashSet<AXID>> relatedObjectIDsFor(const AXCoreObject&, AXRelation, UpdateRelations = UpdateRelations::Yes);
void updateRelations(Element&, const QualifiedName&);
#if PLATFORM(IOS_FAMILY)
void relayNotification(String&&, RetainPtr<NSData>&&);
#endif
#if PLATFORM(MAC)
AXCoreObject::AccessibilityChildrenVector sortedLiveRegions();
AXCoreObject::AccessibilityChildrenVector sortedNonRootWebAreas();
void addSortedObject(AccessibilityObject&, PreSortedObjectType);
void removeLiveRegion(AccessibilityObject&);
void initializeSortedIDLists();
static bool clientIsInTestMode();
#endif
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void scheduleObjectRegionsUpdate(bool scheduleImmediately = false) { m_geometryManager->scheduleObjectRegionsUpdate(scheduleImmediately); }
void willUpdateObjectRegions() { m_geometryManager->willUpdateObjectRegions(); }
WEBCORE_EXPORT static bool isIsolatedTreeEnabled();
WEBCORE_EXPORT static void initializeAXThreadIfNeeded();
private:
static bool clientSupportsIsolatedTree();
// Propagates the root of the isolated tree back into the Core and WebKit.
void setIsolatedTree(Ref<AXIsolatedTree>);
void setIsolatedTreeFocusedObject(AccessibilityObject*);
RefPtr<AXIsolatedTree> getOrCreateIsolatedTree();
void buildIsolatedTree();
void updateIsolatedTree(AccessibilityObject&, AXNotification);
void updateIsolatedTree(AccessibilityObject*, AXNotification);
void updateIsolatedTree(const Vector<std::pair<Ref<AccessibilityObject>, AXNotification>>&);
void updateIsolatedTree(AccessibilityObject*, AXProperty) const;
void updateIsolatedTree(AccessibilityObject&, AXProperty) const;
void startUpdateTreeSnapshotTimer();
#endif
protected:
void postPlatformNotification(AccessibilityObject&, AXNotification);
void platformHandleFocusedUIElementChanged(Element* oldFocus, Element* newFocus);
void platformPerformDeferredCacheUpdate();
#if PLATFORM(COCOA) || USE(ATSPI)
void postTextSelectionChangePlatformNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&);
void postTextStateChangePlatformNotification(AccessibilityObject*, AXTextEditType, const String&, const VisiblePosition&);
void postTextReplacementPlatformNotification(AccessibilityObject*, AXTextEditType, const String&, AXTextEditType, const String&, const VisiblePosition&);
void postTextReplacementPlatformNotificationForTextControl(AccessibilityObject*, const String& deletedText, const String& insertedText);
#else // PLATFORM(COCOA) || USE(ATSPI)
static AXTextChange textChangeForEditType(AXTextEditType);
void nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&);
#endif
#if PLATFORM(MAC)
void postUserInfoForChanges(AccessibilityObject&, AccessibilityObject&, RetainPtr<NSMutableArray>);
#endif
#if PLATFORM(COCOA)
WEBCORE_EXPORT void postPlatformAnnouncementNotification(const String&);
#else
void postPlatformAnnouncementNotification(const String&) { }
#endif
void frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent);
void handleLabelChanged(AccessibilityObject*);
// CharacterOffset functions.
enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3, TraverseOptionDoNotEnterTextControls = 1 << 4 };
Node* nextNode(Node*) const;
Node* previousNode(Node*) const;
CharacterOffset traverseToOffsetInRange(const SimpleRange&, int, TraverseOption = TraverseOptionDefault, bool stayWithinRange = false);
public:
VisiblePosition visiblePositionFromCharacterOffset(const CharacterOffset&);
protected:
CharacterOffset characterOffsetFromVisiblePosition(const VisiblePosition&);
char32_t characterAfter(const CharacterOffset&);
char32_t characterBefore(const CharacterOffset&);
CharacterOffset characterOffsetForNodeAndOffset(Node&, int, TraverseOption = TraverseOptionDefault);
enum class NeedsContextAtParagraphStart : bool { No, Yes };
CharacterOffset previousBoundary(const CharacterOffset&, BoundarySearchFunction, NeedsContextAtParagraphStart = NeedsContextAtParagraphStart::No);
CharacterOffset nextBoundary(const CharacterOffset&, BoundarySearchFunction);
CharacterOffset startCharacterOffsetOfWord(const CharacterOffset&, WordSide = WordSide::RightWordIfOnBoundary);
CharacterOffset endCharacterOffsetOfWord(const CharacterOffset&, WordSide = WordSide::RightWordIfOnBoundary);
CharacterOffset startCharacterOffsetOfParagraph(const CharacterOffset&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary);
CharacterOffset endCharacterOffsetOfParagraph(const CharacterOffset&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary);
CharacterOffset startCharacterOffsetOfSentence(const CharacterOffset&);
CharacterOffset endCharacterOffsetOfSentence(const CharacterOffset&);
CharacterOffset characterOffsetForPoint(const IntPoint&);
LayoutRect localCaretRectForCharacterOffset(RenderObject*&, const CharacterOffset&);
bool shouldSkipBoundary(const CharacterOffset&, const CharacterOffset&);
private:
AccessibilityObject* rootWebArea();
// Returns the object or nearest render-tree ancestor object that is already created (i.e.
// retrievable by |get|, not |getOrCreate|).
AccessibilityObject* getIncludingAncestors(RenderObject&) const;
// The AX focus is more finegrained than the notion of focused Node. This method handles those cases where the focused AX object is a descendant or a sub-part of the focused Node.
AccessibilityObject* focusedObjectForNode(Node*);
static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement&);
void notificationPostTimerFired();
void liveRegionChangedNotificationPostTimerFired();
void performCacheUpdateTimerFired() { performDeferredCacheUpdate(ForceLayout::No); }
void postTextStateChangeNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&);
#if PLATFORM(COCOA)
bool enqueuePasswordNotification(AccessibilityObject&, AXTextChangeContext&&);
void passwordNotificationTimerFired();
#endif
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void selectedTextRangeTimerFired();
Seconds platformSelectedTextRangeDebounceInterval() const;
void updateTreeSnapshotTimerFired();
void processQueuedIsolatedNodeUpdates();
void deferAddUnconnectedNode(AccessibilityObject&);
#if PLATFORM(MAC)
void createIsolatedObjectIfNeeded(AccessibilityObject&);
#endif
#endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void deferRowspanChange(AccessibilityObject*);
void handleChildrenChanged(AccessibilityObject&);
void handleAllDeferredChildrenChanged();
void handleInputTypeChanged(Element&);
void handleRoleChanged(Element&, const AtomString&, const AtomString&);
void handleARIARoleDescriptionChanged(Element&);
void handleMenuOpened(Element&);
void handleLiveRegionCreated(Element&);
void handleMenuItemSelected(Element*);
void handleTabPanelSelected(Element*, Element*);
void handleRowCountChanged(AccessibilityObject*, Document*);
void handleAttributeChange(Element*, const QualifiedName&, const AtomString&, const AtomString&);
bool shouldProcessAttributeChange(Element*, const QualifiedName&);
void selectedChildrenChanged(Node*);
void selectedChildrenChanged(RenderObject*);
void handleScrollbarUpdate(ScrollView&);
void handleActiveDescendantChange(Element&, const AtomString&, const AtomString&);
void handleAriaExpandedChange(Element&);
enum class UpdateModal : bool { No, Yes };
void handleFocusedUIElementChanged(Element* oldFocus, Element* newFocus, UpdateModal = UpdateModal::Yes);
void handleMenuListValueChanged(Element&);
void handleTextChanged(AccessibilityObject*);
void handleRecomputeCellSlots(AccessibilityTable&);
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
void handleRowspanChanged(AccessibilityTableCell&);
#endif
// aria-modal or modal <dialog> related
bool isModalElement(Element&) const;
void findModalNodes();
void updateCurrentModalNode();
bool isNodeVisible(const Node*) const;
bool modalElementHasAccessibleContent(Element&);
// Relationships between objects.
static Vector<QualifiedName>& relationAttributes();
static AXRelation attributeToRelationType(const QualifiedName&);
enum class AddSymmetricRelation : bool { No, Yes };
static AXRelation symmetricRelation(AXRelation);
bool addRelation(Element&, Element&, AXRelation);
bool addRelation(AccessibilityObject*, AccessibilityObject*, AXRelation, AddSymmetricRelation = AddSymmetricRelation::Yes);
bool addRelation(Element&, const QualifiedName&);
void addLabelForRelation(Element&);
bool removeRelation(Element&, AXRelation);
void removeAllRelations(AXID);
void removeRelationByID(AXID originID, AXID targetID, AXRelation);
void updateLabelFor(HTMLLabelElement&);
void updateLabeledBy(Element*);
void updateRelationsIfNeeded();
void updateRelationsForTree(ContainerNode&);
void relationsNeedUpdate(bool);
void dirtyIsolatedTreeRelations();
HashMap<AXID, AXRelations> relations();
const HashSet<AXID>& relationTargetIDs();
bool isDescendantOfRelatedNode(Node&);
#if PLATFORM(MAC)
AXTextStateChangeIntent inferDirectionFromIntent(AccessibilityObject&, const AXTextStateChangeIntent&, const VisibleSelection&);
#endif
// Object creation.
Ref<AccessibilityRenderObject> createObjectFromRenderer(RenderObject&);
Ref<AccessibilityNodeObject> createFromNode(Node&);
const WeakPtr<Document, WeakPtrImplWithEventTargetData> m_document;
const std::optional<PageIdentifier> m_pageID; // constant for object's lifetime.
OptionSet<ActivityState> m_pageActivityState;
HashMap<AXID, Ref<AccessibilityObject>> m_objects;
WeakHashMap<RenderObject, AXID, SingleThreadWeakPtrImpl> m_renderObjectMapping;
WeakHashMap<Widget, AXID, SingleThreadWeakPtrImpl> m_widgetObjectMapping;
// FIXME: The type for m_nodeObjectMapping really should be:
// HashMap<WeakRef<Node, WeakPtrImplWithEventTargetData>, AXID>
// As this guarantees that we've called AXObjectCache::remove(Node&) for every node we store.
// However, in rare circumstances, we can add a node to this map, then later the document associated
// with the node loses its m_frame via detachFromFrame(). Then the node gets destroyed, but we can't
// clean it up from this map, since existingAXObjectCache fails due to the nullptr m_frame.
// This scenario seems extremely rare, and may only happen when the webpage is about to be destroyed anyways,
// so, go with WeakHashMap now until we find a completely safe solution based on document / frame lifecycles.
WeakHashMap<Node, Markable<AXID>, WeakPtrImplWithEventTargetData> m_nodeObjectMapping;
std::unique_ptr<AXComputedObjectAttributeCache> m_computedObjectAttributeCache;
WEBCORE_EXPORT static std::atomic<bool> gAccessibilityEnabled;
static bool gAccessibilityEnhancedUserInterfaceEnabled;
WEBCORE_EXPORT static std::atomic<bool> gForceDeferredSpellChecking;
// FIXME: since the following only affects the behavior of isolated objects, we should move it into AXIsolatedTree in order to keep this class main thread only.
static std::atomic<bool> gForceInitialFrameCaching;
#if ENABLE(AX_THREAD_TEXT_APIS)
// Accessed on and off the main thread.
static std::atomic<bool> gAccessibilityThreadTextApisEnabled;
#endif
Timer m_notificationPostTimer;
Vector<std::pair<Ref<AccessibilityObject>, AXNotification>> m_notificationsToPost;
#if PLATFORM(COCOA)
Timer m_passwordNotificationTimer;
Deque<std::pair<Ref<AccessibilityObject>, AXTextChangeContext>> m_passwordNotifications;
#endif
Timer m_liveRegionChangedPostTimer;
ListHashSet<Ref<AccessibilityObject>> m_changedLiveRegions;
#if PLATFORM(MAC)
// This block is PLATFORM(MAC) because the remote search API is currently only used on macOS.
// AX tree-order-sorted list of a few types of objects. This is useful because some assistive
// technologies send us frequent remote search requests for all the live regions or non-root webareas
// on the page.
bool m_sortedIDListsInitialized { false };
Vector<AXID> m_sortedLiveRegionIDs;
Vector<AXID> m_sortedNonRootWebAreaIDs;
#endif // PLATFORM(MAC)
WeakPtr<Element, WeakPtrImplWithEventTargetData> m_currentModalElement;
// Multiple aria-modals behavior is undefined by spec. We keep them sorted based on DOM order here.
Vector<WeakPtr<Element, WeakPtrImplWithEventTargetData>> m_modalElements;
bool m_modalNodesInitialized { false };
bool m_isRetrievingCurrentModalNode { false };
Timer m_performCacheUpdateTimer;
AXTextStateChangeIntent m_textSelectionIntent;
// An object can be "replaced" when we create an AX object from the backing element before it has
// attached a renderer, but then want to replace it with a new AX object after the renderer has been attached.
HashSet<AXID> m_deferredReplacedObjects;
WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredRecomputeIsIgnoredList;
WeakHashSet<HTMLTableElement, WeakPtrImplWithEventTargetData> m_deferredRecomputeTableIsExposedList;
WeakHashSet<AccessibilityTable> m_deferredRecomputeTableCellSlotsList;
WeakHashSet<AccessibilityTableCell> m_deferredRowspanChanges;
WeakListHashSet<Node, WeakPtrImplWithEventTargetData> m_deferredTextChangedList;
WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredSelectedChildredChangedList;
ListHashSet<Ref<AccessibilityObject>> m_deferredChildrenChangedList;
WeakListHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredElementAddedOrRemovedList;
WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredModalChangedList;
WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredMenuListChange;
SingleThreadWeakHashSet<ScrollView> m_deferredScrollbarUpdateChangeList;
WeakHashMap<Element, String, WeakPtrImplWithEventTargetData> m_deferredTextFormControlValue;
Vector<AttributeChange> m_deferredAttributeChange;
std::optional<std::pair<WeakPtr<Element, WeakPtrImplWithEventTargetData>, WeakPtr<Element, WeakPtrImplWithEventTargetData>>> m_deferredFocusedNodeChange;
WeakHashSet<AccessibilityObject> m_deferredUnconnectedObjects;
#if PLATFORM(MAC)
WeakHashSet<Document, WeakPtrImplWithEventTargetData> m_deferredDocumentAddedList;
#endif
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
Timer m_buildIsolatedTreeTimer;
bool m_deferredRegenerateIsolatedTree { false };
const Ref<AXGeometryManager> m_geometryManager;
DeferrableOneShotTimer m_selectedTextRangeTimer;
Markable<AXID> m_lastDebouncedTextRangeObject;
Timer m_updateTreeSnapshotTimer;
#endif
bool m_isSynchronizingSelection { false };
bool m_performingDeferredCacheUpdate { false };
double m_loadingProgress { 0 };
unsigned m_cacheUpdateDeferredCount { 0 };
// Relationships between objects.
HashMap<AXID, AXRelations> m_relations;
bool m_relationsNeedUpdate { true };
HashSet<AXID> m_relationTargets;
HashMap<AXID, AXRelations> m_recentlyRemovedRelations;
#if USE(ATSPI)
ListHashSet<RefPtr<AccessibilityObject>> m_deferredParentChangedList;
#endif
#if PLATFORM(MAC)
Markable<AXID> m_lastTextFieldAXID;
VisibleSelection m_lastSelection;
#endif
};
template<typename U>
inline Vector<Ref<AXCoreObject>> AXObjectCache::objectsForIDs(const U& axIDs) const
{
ASSERT(isMainThread());
return WTF::compactMap(axIDs, [&](auto& axID) -> std::optional<Ref<AXCoreObject>> {
if (auto* object = objectForID(axID))
return Ref { *object };
return std::nullopt;
});
}
inline Node* AXObjectCache::nodeForID(std::optional<AXID> axID) const
{
if (!axID)
return nullptr;
auto* object = m_objects.get(*axID);
return object ? object->node() : nullptr;
}
inline bool AXObjectCache::accessibilityEnabled()
{
return gAccessibilityEnabled;
}
inline void AXObjectCache::enableAccessibility()
{
gAccessibilityEnabled = true;
}
inline void AXObjectCache::disableAccessibility()
{
gAccessibilityEnabled = false;
}
inline bool AXObjectCache::forceDeferredSpellChecking()
{
return gForceDeferredSpellChecking;
}
inline void AXObjectCache::setForceDeferredSpellChecking(bool shouldForce)
{
gForceDeferredSpellChecking = shouldForce;
}
class AXAttributeCacheEnabler final
{
public:
explicit AXAttributeCacheEnabler(AXObjectCache *cache);
~AXAttributeCacheEnabler();
private:
AXObjectCache* m_cache;
bool m_wasAlreadyCaching { false };
};
bool hasRole(Element&, StringView role);
bool hasAnyRole(Element&, Vector<StringView>&& roles);
bool hasAnyRole(Element*, Vector<StringView>&& roles);
bool hasCellARIARole(Element&);
bool hasPresentationRole(Element&);
bool hasTableRole(Element&);
bool isRowGroup(Element&);
bool isRowGroup(Node*);
ContainerNode* composedParentIgnoringDocumentFragments(Node&);
ContainerNode* composedParentIgnoringDocumentFragments(Node*);
ElementName elementName(Node*);
ElementName elementName(Node&);
// Returns true if the element has an attribute that will result in an accname being computed.
// https://www.w3.org/TR/accname-1.2/
bool hasAccNameAttribute(Element&);
bool isNodeFocused(Node&);
bool isRenderHidden(const RenderStyle*);
// Checks both CSS display properties, and CSS visibility properties.
bool isRenderHidden(const RenderStyle&);
// Only checks CSS visibility properties.
bool isVisibilityHidden(const RenderStyle&);
WTF::TextStream& operator<<(WTF::TextStream&, AXNotification);
} // namespace WebCore