| /* |
| * 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 <WebCore/AXAnnouncementTypes.h> |
| #include <WebCore/AXObjectTypes.h> |
| #include <WebCore/AXTextStateChangeIntent.h> |
| #include <WebCore/AXTreeStore.h> |
| #include <WebCore/AccessibilityObject.h> |
| #include <WebCore/AccessibilityRemoteToken.h> |
| #include <WebCore/SimpleRange.h> |
| #include <WebCore/StyleChange.h> |
| #include <WebCore/Timer.h> |
| #include <WebCore/VisibleUnits.h> |
| #include <wtf/CheckedRef.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/WeakListHashSet.h> |
| |
| #if PLATFORM(COCOA) |
| #include <WebCore/AttributedString.h> |
| #include <wtf/RetainPtr.h> |
| #endif |
| |
| OBJC_CLASS NSMutableArray; |
| OBJC_CLASS NSString; |
| |
| namespace WTF { |
| class TextStream; |
| } |
| |
| namespace WebCore { |
| |
| class AXComputedObjectAttributeCache; |
| class AXGeometryManager; |
| class AXIsolatedTree; |
| class AXLiveRegionManager; |
| class AXRemoteFrame; |
| class AccessibilityNodeObject; |
| class AccessibilityObject; |
| class AccessibilityRenderObject; |
| class AccessibilitySpinButton; |
| class Document; |
| class HTMLAreaElement; |
| class HTMLCanvasElement; |
| class HTMLDetailsElement; |
| class HTMLMediaElement; |
| class HTMLSelectElement; |
| class HTMLTableElement; |
| class HTMLTextFormControlElement; |
| class Node; |
| class Page; |
| class RemoteFrame; |
| class RenderBlock; |
| class RenderBlockFlow; |
| class RenderImage; |
| |
| class AXTextMarker; |
| |
| namespace Style { |
| class ComputedStyle; |
| struct Difference; |
| } |
| |
| class RenderObject; |
| class RenderText; |
| class RenderWidget; |
| class Scrollbar; |
| class ScrollView; |
| class SpinButtonElement; |
| class VisiblePosition; |
| class Widget; |
| |
| struct AXTextStateChangeIntent; |
| struct AriaNotifyOptions; |
| struct TextMarkerData; |
| |
| enum class AXNotification : uint8_t; |
| enum class AXStreamOptions : uint16_t; |
| enum class AXProperty : uint16_t; |
| enum class LiveRegionStatus: uint8_t; |
| |
| |
| struct AXTreeData { |
| String liveTree; |
| String isolatedTree; |
| // Captures warnings about the tree state that could result in poor user-facing |
| // behavior. These are gathered while capturing the structural tree data stored |
| // in the other member variables. |
| Vector<String> warnings; |
| |
| void dumpToStderr() const |
| { |
| for (const String& warning : warnings) |
| SAFE_FPRINTF(stderr, "WARNING: %s\n", warning.utf8()); |
| SAFE_FPRINTF(stderr, "==AX Trees (PID %d)==\n%s\n%s\n", getpid(), liveTree.utf8(), isolatedTree.utf8()); |
| } |
| }; |
| |
| // When this is updated, WebCoreArgumentCoders.serialization.in must be updated as well. |
| struct AXDebugInfo { |
| AccessibilityMode accessibilityMode; |
| String liveTree; |
| String isolatedTree; |
| Vector<String> warnings; |
| uint64_t remoteTokenHash; |
| uint64_t webProcessLocalTokenHash; |
| }; |
| |
| // When this is updated, WebCoreArgumentCoders.serialization.in must be updated as well. |
| struct AriaNotifyData { |
| String message; |
| NotifyPriority priority { NotifyPriority::Normal }; |
| InterruptBehavior interrupt { InterruptBehavior::None }; |
| String language; |
| |
| String debugDescription() const |
| { |
| auto priorityString = [&] { |
| switch (priority) { |
| case NotifyPriority::Normal: |
| return "normal"_s; |
| case NotifyPriority::High: |
| return "high"_s; |
| } |
| return "unknown"_s; |
| }; |
| auto interruptString = [&] { |
| switch (interrupt) { |
| case InterruptBehavior::None: |
| return "none"_s; |
| case InterruptBehavior::All: |
| return "all"_s; |
| case InterruptBehavior::Pending: |
| return "pending"_s; |
| } |
| return "unknown"_s; |
| }; |
| return makeString("AriaNotifyData { message: \""_s, message, "\", priority: "_s, priorityString(), ", interrupt: "_s, interruptString(), ", language: \""_s, language, "\" }"_s); |
| } |
| }; |
| |
| #if PLATFORM(COCOA) |
| // When this is updated, WebCoreArgumentCoders.serialization.in must be updated as well. |
| struct LiveRegionAnnouncementData { |
| AttributedString message; |
| LiveRegionStatus status { LiveRegionStatus::Polite }; |
| |
| String debugDescription() const |
| { |
| auto statusString = [&] { |
| switch (status) { |
| case LiveRegionStatus::Off: |
| return "off"_s; |
| case LiveRegionStatus::Polite: |
| return "polite"_s; |
| case LiveRegionStatus::Assertive: |
| return "assertive"_s; |
| } |
| return "unknown"_s; |
| }; |
| return makeString("LiveRegionAnnouncementData { message: \""_s, message.string, "\", status: "_s, statusString(), " }"_s); |
| } |
| }; |
| |
| struct AXTextChangeContext { |
| AXTextStateChangeIntent intent; |
| String deletedText; |
| String insertedText; |
| VisibleSelection selection; |
| }; |
| #endif // PLATFORM(COCOA) |
| |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| // When this is updated, WebCoreArgumentCoders.serialization.in must be updated as well. |
| |
| // Describes a frame's position and scale on screen for accessibility coordinate conversion. |
| // Sent from the UIProcess to the WebProcess via IPC whenever the frame scrolls, moves, or resizes. |
| // When this is updated, WebCoreArgumentCoders.serialization.in must be updated as well. |
| struct AXFrameGeometry { |
| // The frame's content origin in screen coordinates. |
| // - Coordinate space: bottom-left on macOS, top-left on other platforms. |
| // - Points to: the top-left of the frame's document. |
| // - Units: display pixels. |
| // - Scroll: document-origin-based, so accounts for the frame's scroll position |
| // (e.g. scrolling down moves the document origin up on screen). |
| // Element rects in content space compose with this directly to produce screen coordinates. |
| IntPoint screenPosition; |
| |
| // Scale accounts for page zoom and device scale factor, among other things. |
| // Applied to the element rect before adding screenPosition. |
| AffineTransform screenTransform; |
| }; |
| #endif |
| |
| struct AXNotificationWithData { |
| using DataVariant = Variant<std::monostate, AriaNotifyData |
| #if PLATFORM(COCOA) |
| , LiveRegionAnnouncementData |
| #endif |
| >; |
| |
| AXNotification notification; |
| DataVariant data; |
| |
| AXNotificationWithData(AXNotification notification) |
| : notification(notification) |
| , data(std::monostate { }) { } |
| |
| AXNotificationWithData(AXNotification notification, const AriaNotifyData& data) |
| : notification(notification), data(data) { } |
| |
| #if PLATFORM(COCOA) |
| AXNotificationWithData(AXNotification notification, const LiveRegionAnnouncementData& data) |
| : notification(notification), data(data) { } |
| #endif |
| |
| String debugDescription() const; |
| }; |
| |
| class AccessibilityReplacedText { |
| public: |
| AccessibilityReplacedText() = default; |
| AccessibilityReplacedText(const VisibleSelection&); |
| void postTextStateChangeNotification(AXObjectCache*, AXTextEditType, const String&, const VisibleSelection&); |
| const VisiblePositionIndexRange& replacedRange() LIFETIME_BOUND { return m_replacedRange; } |
| protected: |
| String m_replacedText; |
| VisiblePositionIndexRange m_replacedRange; |
| }; |
| |
| 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 }; |
| |
| struct DeferredNotificationData { |
| // The renderer or element to post a notification for. |
| SingleThreadWeakPtr<RenderObject> renderer { nullptr }; |
| WeakPtr<Element, WeakPtrImplWithEventTargetData> element { nullptr }; |
| // The notification to post. |
| AXNotification notification; |
| |
| DeferredNotificationData() = delete; |
| |
| explicit DeferredNotificationData(RenderObject& renderer, AXNotification notification) |
| : renderer(renderer) |
| , notification(notification) |
| { } |
| }; |
| |
| 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(LocalFrame&, Document*); |
| ~AXObjectCache(); |
| |
| String debugDescription() const; |
| |
| // Returns the root object for a specific frame. |
| WEBCORE_EXPORT AXCoreObject* rootObjectForFrame(LocalFrame&); |
| AccessibilityObject* rootWebArea(); |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| WEBCORE_EXPORT void setFrameInheritedState(LocalFrame&, const InheritedFrameState&); |
| WEBCORE_EXPORT void setFrameGeometry(LocalFrame&, const AXFrameGeometry&); |
| const std::optional<AXFrameGeometry>& frameGeometry() const LIFETIME_BOUND { return m_frameGeometry; } |
| // The scroll value that was implicitly baked into the latest frameGeometry().screenPosition. |
| IntPoint frameViewOriginScrollPosition() const { return m_frameViewOriginScrollPosition; } |
| const std::optional<AXFrameGeometry>& getAndUpdateFrameGeometry() LIFETIME_BOUND; |
| #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; |
| } |
| AccessibilityObject* getOrCreate(Node&, IsPartOfRelation = IsPartOfRelation::No); |
| AccessibilityObject* getOrCreate(Element&, IsPartOfRelation = IsPartOfRelation::No); |
| // Out-of-line implementations (necessary for use outside of WebCore). Our inlined |
| // implementations (important for performance inside WebCore) cannot be exported. |
| WEBCORE_EXPORT AccessibilityObject* exportedGetOrCreate(Node&); |
| WEBCORE_EXPORT AccessibilityObject* exportedGetOrCreate(Node*); |
| |
| // used for objects without backing elements |
| AccessibilityObject* create(AccessibilityRole); |
| Ref<AccessibilitySpinButton> createSpinButton(SpinButtonElement&); |
| |
| // Will only return the AccessibilityObject if it already exists. |
| inline AccessibilityObject* get(RenderObject* renderer) const |
| { |
| return renderer ? get(*renderer) : nullptr; |
| } |
| inline AccessibilityObject* get(RenderObject& renderer) const |
| { |
| std::optional axID = getAXID(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_widgetIdMapping.getOptional(widget); |
| return axID ? m_objects.get(*axID) : nullptr; |
| } |
| |
| inline AccessibilityObject* get(Node* node) const |
| { |
| return node ? get(*node) : nullptr; |
| } |
| AccessibilityObject* get(Node&) const; |
| inline AccessibilityObject* get(Element& element) const |
| { |
| return m_nodeObjectMapping.get(element); |
| } |
| std::optional<AXID> getAXID(RenderObject&) const; |
| |
| void remove(RenderObject&); |
| void remove(Node&); |
| void remove(Widget&); |
| void remove(std::optional<AXID> axID) |
| { |
| if (axID) |
| remove(*axID); |
| } |
| void remove(AXID); |
| |
| #if !PLATFORM(COCOA) && !USE(ATSPI) |
| void detachWrapper(AXCoreObject*, AccessibilityDetachmentType); |
| #endif |
| private: |
| using DOMObjectVariant = Variant<std::nullptr_t, RenderObject*, Node*, Widget*>; |
| enum class ShouldAttachWrapper : bool { No, Yes }; |
| void cacheAndInitializeWrapper(AccessibilityObject&, DOMObjectVariant = nullptr, ShouldAttachWrapper = ShouldAttachWrapper::Yes); |
| void attachWrapper(AccessibilityObject&); |
| |
| AccessibilityObject* getOrCreateSlow(Node&, IsPartOfRelation); |
| AccessibilityObject* getOrCreateSlow(Widget&); |
| |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| RefPtr<AccessibilityScrollView> scrollViewForFrame(LocalFrame&); |
| #endif |
| |
| public: |
| void onPageActivityStateChange(OptionSet<ActivityState>); |
| void setPageActivityState(OptionSet<ActivityState> state) { m_pageActivityState = state; } |
| OptionSet<ActivityState> pageActivityState() const { return m_pageActivityState; } |
| |
| #if ENABLE(WRITING_TOOLS) |
| WEBCORE_EXPORT void setWritingToolsAvailable(bool); |
| #endif // ENABLE(WRITING_TOOLS) |
| |
| 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(RefPtr { get(node) }.get()); |
| } else if (node.renderer()->isRenderHTMLCanvas()) { |
| // Canvas fallback content (its DOM children) is not rendered, so we won't receive |
| // children-changed notifications from the render tree when those children change. |
| childrenChanged(RefPtr { get(node) }.get()); |
| } |
| } |
| void childrenChanged(RenderObject&, RenderObject* newChild = nullptr); |
| void childrenChanged(AccessibilityObject*); |
| void onDetailsSummarySlotChange(const HTMLDetailsElement&); |
| void onDragElementChanged(Element* oldElement, Element* newElement); |
| void onDraggingStarted(Element&); |
| void onDraggingEnded(Element&); |
| void onDraggingEnteredDropZone(Element&); |
| void onDraggingExitedDropZone(Element&); |
| void onDraggingDropped(Element&); |
| void onEventListenerAdded(Node&, const AtomString& eventType); |
| void onEventListenerRemoved(Node&, const AtomString& eventType); |
| void onFocusChange(Element* oldElement, Element* newElement); |
| void onFrameSelectionFocusedOrActiveStateChanged(Document&); |
| void onInertOrVisibilityChange(RenderElement&); |
| #if ENABLE(VIDEO) |
| void onMediaElementCurrentSrcChanged(HTMLMediaElement&); |
| #endif |
| void onPopoverToggle(const HTMLElement&); |
| void onRadioGroupMembershipChanged(HTMLElement&); |
| void onRemoteFrameGainedFocus(RemoteFrame&); |
| void onScrollbarFrameRectChange(const Scrollbar&); |
| void onSelectedOptionChanged(Element&); |
| void onSelectedOptionChanged(HTMLSelectElement&, int optionIndex); |
| void onSelectedTextChanged(const VisiblePositionRange&, AccessibilityObject* = nullptr); |
| void onSlottedContentChange(const HTMLSlotElement&); |
| void onStyleChange(Element&, OptionSet<Style::Change>, const Style::ComputedStyle* oldStyle, const Style::ComputedStyle* newStyle); |
| void onStyleChange(RenderText&, Style::Difference, const Style::ComputedStyle* oldStyle, const Style::ComputedStyle& newStyle); |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void onAccessibilityPaintStarted(); |
| void onAccessibilityPaintFinished(); |
| // Returns true if the font changes, requiring all descendants to update the Font property. |
| bool onFontChange(Element&, const Style::ComputedStyle*, const Style::ComputedStyle*); |
| // Returns true if the text color changes, requiring all descendants to update the TextColor property. |
| bool onTextColorChange(Element&, const Style::ComputedStyle*, const Style::ComputedStyle*); |
| #endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void onTextSecurityChanged(HTMLInputElement&); |
| void onTitleChange(Document&); |
| void onValidityChange(Element&); |
| void onTextCompositionChange(Node&, CompositionState, bool, const String&, size_t, bool); |
| void onWidgetVisibilityChanged(RenderWidget&); |
| void onPostRenderingUpdate(); |
| void valueChanged(Element&); |
| void checkedStateChanged(Element&); |
| void autofillTypeChanged(HTMLInputElement&); |
| void handleRoleChanged(AccessibilityObject&, AccessibilityRole previousRole); |
| void handleReferenceTargetChanged(); |
| |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void columnIndexChanged(AccessibilityObject&); |
| void rowIndexChanged(AccessibilityObject&); |
| #endif |
| |
| void onRendererCreated(Node&); |
| #if PLATFORM(MAC) |
| void onDocumentRenderTreeCreation(const Document&); |
| #endif |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void onTextRunsChanged(const RenderObject&); |
| #endif |
| |
| void onLaidOutInlineContent(const RenderBlockFlow&); |
| const Vector<AXStitchGroup>* stitchGroupsOwnedBy(AccessibilityObject&); |
| |
| void updateLoadingProgress(double); |
| void loadingFinished() { updateLoadingProgress(1); } |
| double loadingProgress() const { return m_loadingProgress; } |
| |
| void onTopDocumentLoaded(RenderObject&); |
| void onNonTopDocumentLoaded(RenderObject&); |
| void handlePageEditibilityChanged(Document&); |
| void onAutocorrectionOccured(Element&); |
| void onEditableTextValueChanged(Node&); |
| void onDocumentInitialFocus(Node&); |
| void onLayoutComplete(RenderObject&); |
| |
| struct AttributeChange { |
| WeakPtr<Element, WeakPtrImplWithEventTargetData> element { nullptr }; |
| QualifiedName attrName; |
| AtomString oldValue; |
| AtomString newValue; |
| }; |
| struct DeferredRemoteFrameFocus { |
| WeakPtr<RemoteFrame> remoteFrame; |
| WeakPtr<Element, WeakPtrImplWithEventTargetData> oldFocusedElement; |
| }; |
| |
| struct CanvasFocusPathBoundsChange { |
| // The canvas fallback element whose focus path bounds were set by |
| // CanvasRenderingContext2D::drawFocusIfNeeded(). |
| WeakPtr<Element, WeakPtrImplWithEventTargetData> fallbackElement; |
| WeakPtr<HTMLCanvasElement, WeakPtrImplWithEventTargetData> canvas; |
| FloatRect bounds; |
| }; |
| using DeferredCollection = Variant<HashMap<Element*, String> |
| , HashSet<AXID> |
| , ListHashSet<Node*> |
| , ListHashSet<Ref<AccessibilityObject>> |
| , Vector<AttributeChange> |
| , Vector<CanvasFocusPathBoundsChange> |
| , Vector<std::pair<Node*, Node*>> |
| , WeakHashSet<Element, WeakPtrImplWithEventTargetData> |
| , WeakHashSet<HTMLTableElement, WeakPtrImplWithEventTargetData> |
| , WeakHashSet<AccessibilityObject> |
| , WeakHashSet<AccessibilityNodeObject> |
| , 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 NODELETE 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&); |
| |
| // True for the attributes in relationAttributes() (aria-labelledby, aria-owns, etc.). |
| static bool isRelationAttribute(const QualifiedName&); |
| // Records that an element carries a relation attribute so the next relations rebuild includes it. |
| void trackRelationAttributeElement(Element&); |
| |
| void recomputeIsIgnored(RenderObject&); |
| void recomputeIsIgnored(Node*); |
| |
| // What must hold before this process will reach AXThread mode. |
| enum class AXThreadModePreconditions : uint8_t { |
| // A current accessibility client that we support isolated trees for, and this process's |
| // isolated-tree setting. |
| RequireClientAndSetting, |
| // Only the setting. For transitions driven by IPC rather than by an accessibility request, |
| // where there is no current client to ask about. |
| RequireSettingOnly, |
| None |
| }; |
| static void enableAccessibility(AXThreadModePreconditions = AXThreadModePreconditions::RequireClientAndSetting); |
| // Resets mode to Off without notifying the UI process, so the change |
| // stays local to this web process. Used by Internals::resetToConsistentState |
| // to avoid interfering with other web content processes running tests. |
| WEBCORE_EXPORT static void disableAccessibilityForTesting(); |
| WEBCORE_EXPORT static std::optional<AccessibilityMode> attemptMainThreadModeTransition(); |
| WEBCORE_EXPORT static std::atomic<AccessibilityMode> gAccessibilityMode; |
| static AccessibilityMode accessibilityMode() { return gAccessibilityMode.load(std::memory_order_relaxed); } |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| WEBCORE_EXPORT static std::optional<AccessibilityMode> transitionToAXThreadModeIfNeeded(AXThreadModePreconditions = AXThreadModePreconditions::RequireClientAndSetting); |
| WEBCORE_EXPORT static bool shouldForceAccessibilityEnabled(); |
| #endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| |
| using SyncModeToOtherProcessesCallback = Function<void(AccessibilityMode)>; |
| WEBCORE_EXPORT static void setSyncModeToOtherProcessesCallback(SyncModeToOtherProcessesCallback&&); |
| |
| #if PLATFORM(MAC) |
| WEBCORE_EXPORT static bool isAppleInternalInstall(); |
| #endif |
| 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*); |
| WEBCORE_EXPORT AccessibilityObject* focusedObjectForLocalFrame(); |
| |
| // 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 NODELETE accessibilityEnhancedUserInterfaceEnabled(); |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| static bool useAXThreadTextApis() { return !isMainThread(); } |
| static bool shouldCreateAXThreadCompatibleMarkers() { return isIsolatedTreeEnabled(); } |
| #endif |
| static bool isAXTextStitchingEnabled() { return gAccessibilityTextStitchingEnabled; } |
| WEBCORE_EXPORT static bool NODELETE isAXThreadHitTestingEnabled(); |
| |
| #if PLATFORM(COCOA) |
| static bool shouldRepostNotificationsForTests() { return gShouldRepostNotificationsForTests; } |
| |
| static void initializeUserDefaultValues(); |
| static bool accessibilityDOMIdentifiersEnabled() { return gAccessibilityDOMIdentifiersEnabled; } |
| #endif |
| |
| static bool forceInitialFrameCaching() { return gForceInitialFrameCaching; } |
| WEBCORE_EXPORT static void NODELETE 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; |
| void onPaint(const RenderText&, size_t lineIndex); |
| #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(); } |
| NO_RETURN_DUE_TO_ASSERT void onPaint(const RenderText&, size_t) { ASSERT_NOT_REACHED(); } |
| #endif |
| |
| void deferCanvasFocusPathBoundsUpdate(Element& canvasFallbackElement, HTMLCanvasElement&, FloatRect bounds); |
| std::optional<IntRect> cachedBoundsForID(AXID) const; |
| |
| // Text marker utilities. |
| std::optional<TextMarkerData> textMarkerDataForVisiblePosition(const VisiblePosition&, TextMarkerOrigin = static_cast<TextMarkerOrigin>(0)); |
| TextMarkerData textMarkerDataForCharacterOffset(const CharacterOffset&, TextMarkerOrigin = static_cast<TextMarkerOrigin>(0)); |
| 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); |
| void postDeferredNotification(RenderObject&, AXNotification); |
| void postARIANotifyNotification(Node&, const String&, const AriaNotifyOptions&); |
| #if PLATFORM(COCOA) |
| void postLiveRegionNotification(AccessibilityObject&, LiveRegionStatus, const AttributedString&); |
| #endif |
| // Requests clients to announce to the user the given message in the way they deem appropriate. |
| WEBCORE_EXPORT void announce(const String&); |
| |
| // Invokes `assemble` exactly once, either synchronously if the page is not translated and no |
| // other announcements are queued ahead, or when the translated text arrives (or times out). |
| // The `language` argument is the target language when the text was translated and empty otherwise. |
| void translateAnnouncementThenAssemble(Ref<AccessibilityObject>&&, Vector<String>&& segments, Function<void(Vector<String>&&, const String& language)>&& assemble); |
| |
| // Records a node whose descendant text is about to be swapped out for a re-rendering of the same |
| // content, e.g. when page-level translation replaces text. |
| // |
| // The resulting children-changed is not a content change the user should hear about again, so the |
| // live region announcement for it is skipped. Forgotten once that change has been processed, so |
| // it never suppresses a later, genuine mutation of the same node. |
| WEBCORE_EXPORT void deferReRenderedContent(Node&); |
| |
| WEBCORE_EXPORT static void setAnnouncementTranslationTimeoutForTesting(std::optional<Seconds>); |
| |
| void NODELETE setTextSelectionIntent(const AXTextStateChangeIntent&); |
| void NODELETE setIsSynchronizingSelection(bool); |
| |
| // While non-std::nullopt, a focus change onto the given element will not be surfaced to assistive |
| // technology. A null target suppresses a *clearing* of focus instead. |
| void beginSuppressingFocusChange(Element* target) { m_suppressedFocusChange = WeakPtr { target }; } |
| void endSuppressingFocusChange() { m_suppressedFocusChange = std::nullopt; } |
| |
| 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&); |
| |
| // Testing-only: number of times a live region snapshot has been (re)computed since the last reset. |
| WEBCORE_EXPORT unsigned liveRegionSnapshotBuildCount() const; |
| WEBCORE_EXPORT void resetLiveRegionSnapshotBuildCount(); |
| |
| void frameLoadingEventNotification(LocalFrame*, AXLoadingEvent); |
| |
| void prepareForDocumentDestruction(const Document&); |
| |
| void startCachingComputedObjectAttributesUntilTreeMutates(); |
| void stopCachingComputedObjectAttributes(); |
| |
| AXComputedObjectAttributeCache* computedObjectAttributeCache() LIFETIME_BOUND { return m_computedObjectAttributeCache.get(); } |
| |
| Document* document() const; |
| FrameIdentifier frameID() const { return m_frameID; } |
| |
| RefPtr<Page> NODELETE page() const; |
| IntPoint mapScreenPointToPagePoint(const IntPoint&) const; |
| |
| void objectBecameIgnored(const AccessibilityObject&); |
| void objectBecameUnignored(const AccessibilityObject&); |
| static bool isMockObjectOrWebAreaRole(AccessibilityRole role) |
| { |
| switch (role) { |
| case AccessibilityRole::WebArea: |
| case AccessibilityRole::ScrollArea: |
| case AccessibilityRole::ScrollBar: |
| case AccessibilityRole::LocalFrame: |
| case AccessibilityRole::FrameHost: |
| case AccessibilityRole::RemoteFrame: |
| case AccessibilityRole::SliderThumb: |
| case AccessibilityRole::SpinButton: |
| case AccessibilityRole::SpinButtonPart: |
| case AccessibilityRole::MenuListPopup: |
| case AccessibilityRole::Column: |
| case AccessibilityRole::TableHeaderContainer: |
| return true; |
| default: |
| return false; |
| } |
| } |
| void incrementUnignoredContentObjectCount(AccessibilityRole role) |
| { |
| if (!isMockObjectOrWebAreaRole(role)) |
| ++m_unignoredContentObjectCount; |
| } |
| void decrementUnignoredContentObjectCount(AccessibilityRole role) |
| { |
| if (isMockObjectOrWebAreaRole(role)) |
| return; |
| AX_ASSERT(m_unignoredContentObjectCount); |
| if (m_unignoredContentObjectCount) |
| --m_unignoredContentObjectCount; |
| } |
| |
| #if PLATFORM(COCOA) |
| static void NODELETE setShouldRepostNotificationsForTests(bool); |
| #endif |
| |
| void deferRecomputeIsIgnoredIfNeeded(Element*); |
| void deferRecomputeIsIgnored(Element*); |
| void deferRecomputeTableIsExposed(Element*); |
| void deferRecomputeTableCellSlots(AccessibilityNodeObject&); |
| 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); |
| |
| WEBCORE_EXPORT 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&); |
| bool hasAriaOwnsRelations() const { return m_relationsNeedUpdate || m_hasAriaOwnsRelations; } |
| |
| #if PLATFORM(IOS_FAMILY) |
| void relayNotification(String&&, RetainPtr<NSData>&&); |
| |
| void setWillPresentDatePopover(bool willPresent) { m_willPresentDatePopover = willPresent; } |
| bool willPresentDatePopover() const { return m_willPresentDatePopover; } |
| #endif |
| |
| #if PLATFORM(MAC) |
| AXCoreObject::AccessibilityChildrenVector sortedLiveRegions(); |
| AXCoreObject::AccessibilityChildrenVector sortedNonRootWebAreas(); |
| void deferSortForNewLiveRegion(Ref<AccessibilityObject>&&); |
| void queueUnsortedObject(Ref<AccessibilityObject>&&, PreSortedObjectType); |
| void addSortedObjects(Vector<Ref<AccessibilityObject>>&&, PreSortedObjectType); |
| void removeLiveRegion(AccessibilityObject&); |
| void initializeSortedIDLists(); |
| |
| WEBCORE_EXPORT static bool clientIsInTestMode(); |
| #endif |
| |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| inline void scheduleObjectRegionsUpdate(bool scheduleImmediately = false); |
| inline void willUpdateObjectRegions(); |
| static bool isIsolatedTreeEnabled() { return accessibilityMode() == AccessibilityMode::AXThread; } |
| WEBCORE_EXPORT static void initializeAXThreadIfNeeded(); |
| WEBCORE_EXPORT static bool NODELETE isAXThreadInitialized(); |
| WEBCORE_EXPORT RefPtr<AXIsolatedTree> getOrCreateIsolatedTree(); |
| void initializeIsolatedTreeGeometry(); |
| |
| static bool isAccessibilityList(Element&); |
| private: |
| static bool clientSupportsIsolatedTree(); |
| |
| enum class PlatformAXThreadSupport : bool { NotSupported, Supported }; |
| static PlatformAXThreadSupport platformAXThreadSupport(AXThreadModePreconditions); |
| enum class DidStartThread : bool { No, Yes }; |
| static DidStartThread platformStartSecondaryThread(); |
| |
| // Propagates the root of the isolated tree back into the Core and WebKit. |
| void setIsolatedTree(Ref<AXIsolatedTree>); |
| void setIsolatedTreeFocusedObject(AccessibilityObject*); |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| // Refreshes the isolated-tree focused object of each ancestor local frame, keeping an ancestor |
| // tree (e.g. the main frame's, which VoiceOver queries) pointed at the AXLocalFrame leading |
| // toward the focused subframe. Only the focused frame's own cache handles its focus change, so |
| // ancestor trees would otherwise never learn focus moved into a descendant local frame. |
| void updateAncestorFramesFocusedObject(); |
| #endif |
| void buildIsolatedTree(); |
| void updateIsolatedTree(AccessibilityObject&, AXNotification); |
| void updateIsolatedTree(AccessibilityObject*, AXNotification); |
| void updateIsolatedTree(const Vector<std::pair<Ref<AccessibilityObject>, AXNotificationWithData>>&); |
| void updateIsolatedTree(AccessibilityObject*, AXProperty) const; |
| void updateIsolatedTree(AccessibilityObject&, AXProperty) const; |
| void startUpdateTreeSnapshotTimer(); |
| #endif |
| void updateCachedTextOfAssociatedObjects(AccessibilityObject&); |
| |
| protected: |
| void postPlatformNotification(AccessibilityObject&, AXNotification); |
| void platformHandleFocusedUIElementChanged(AccessibilityObject* oldFocus, AccessibilityObject* 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&); |
| WEBCORE_EXPORT void postPlatformARIANotifyNotification(AccessibilityObject&, const AriaNotifyData&); |
| WEBCORE_EXPORT void postPlatformLiveRegionNotification(AccessibilityObject&, const LiveRegionAnnouncementData&); |
| #else |
| void postPlatformAnnouncementNotification(const String&) { } |
| void postPlatformARIANotifyNotification(AccessibilityObject&, const AriaNotifyData&) { } |
| #endif |
| |
| void frameLoadingEventPlatformNotification(RenderView*, AXLoadingEvent); |
| void handleLabelChanged(AccessibilityObject*); |
| |
| // CharacterOffset functions. |
| enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3, TraverseOptionDoNotEnterTextControls = 1 << 4 }; |
| Node* NODELETE nextNode(Node*) const; |
| Node* NODELETE previousNode(Node*) const; |
| CharacterOffset traverseToOffsetInRange(const SimpleRange&, int, TraverseOption = TraverseOptionDefault, bool stayWithinRange = false); |
| public: |
| VisiblePosition visiblePositionFromCharacterOffset(const CharacterOffset&, AllowUserSelectNone = AllowUserSelectNone::No); |
| 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: |
| // 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 enqueueNotificationToPost(Ref<AccessibilityObject>&&, AXNotificationWithData&&); |
| |
| void announcementTranslationDidComplete(uint64_t requestID, Vector<String>&& translatedSegments); |
| bool isReRenderedContent(const AccessibilityObject&) const; |
| void flushPendingAnnouncements(); |
| void pendingAnnouncementTimeoutTimerFired(); |
| void scheduleAnnouncementTimeoutTimerIfNeeded(); |
| static Seconds announcementTranslationTimeout(); |
| |
| void liveRegionChangedNotificationPostTimerFired(); |
| void processChangedLiveRegions(); |
| |
| 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 NODELETE platformSelectedTextRangeDebounceInterval() const; |
| void updateTreeSnapshotTimerFired() { processQueuedIsolatedNodeUpdates(); } |
| void processQueuedIsolatedNodeUpdates(); |
| |
| void deferAddUnconnectedNode(AccessibilityObject&); |
| #if PLATFORM(MAC) |
| void createIsolatedObjectIfNeeded(AccessibilityObject&); |
| #endif |
| #endif // ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| |
| void deferRowspanChange(AccessibilityObject*); |
| void handleCanvasFocusPathBoundsChange(const CanvasFocusPathBoundsChange&); |
| void handleChildrenChanged(AccessibilityObject&); |
| void handleAllDeferredChildrenChanged(); |
| void handleInputTypeChanged(Element&); |
| void handleRoleChanged(Element&, const AtomString&, const AtomString&); |
| void handleARIARoleDescriptionChanged(Element&); |
| void handleMenuOpened(Element&); |
| void handleLiveRegionCreated(Element&); |
| #if PLATFORM(COCOA) |
| void initializeLiveRegionManager(); |
| #endif |
| void handleMenuItemSelected(Element*); |
| void handleTabPanelSelected(Element*, Element*); |
| void handleRowCountChanged(AccessibilityObject*, Document*); |
| void handleAttributeChange(Element*, const QualifiedName&, const AtomString&, const AtomString&); |
| void handleClickHandlerChanged(Node&, const AtomString& eventType); |
| bool shouldProcessAttributeChange(Element*, const QualifiedName&); |
| void selectedChildrenChanged(Node*); |
| void selectedChildrenChanged(RenderObject*); |
| void handleScrollbarUpdate(ScrollView&); |
| void handleActiveDescendantChange(Element&, const AtomString&, const AtomString&); |
| void handleAriaExpandedChange(Element&); |
| void handleAriaHiddenChange(Element&); |
| enum class UpdateModal : bool { No, Yes }; |
| void handleFocusedUIElementChanged(Element* oldFocus, Element* newFocus, UpdateModal = UpdateModal::Yes); |
| void handleRemoteFrameGainedFocus(RemoteFrame&, Element* oldFocusedElement); |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| // Returns the AXLocalFrame in this cache's tree that proxies the direct child frame leading toward |
| // the focused subframe, or nullptr if focus is not in a descendant local frame. Resolves the frame |
| // owner element via TreeScope::focusedElementInScope() (as Document::activeElement() does). |
| AccessibilityObject* localFrameLeadingToFocusedFrame(); |
| #endif |
| void handleMenuListValueChanged(Element&); |
| void handleTextChanged(AccessibilityObject*); |
| void handleRecomputeCellSlots(AccessibilityNodeObject&); |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| void handleRowspanChanged(AccessibilityNodeObject&); |
| #endif |
| void handleDeferredNotification(const DeferredNotificationData&); |
| void handleDeferredPopoverToggle(AccessibilityObject&); |
| |
| // aria-modal or modal <dialog> related |
| bool isModalElement(Element&) const; |
| void findModalNodes(); |
| void updateCurrentModalNode(); |
| bool modalElementHasAccessibleContent(Element&); |
| |
| void setDirtyStitchGroups(const RenderBlock&); |
| |
| // Relationships between objects. |
| static Vector<QualifiedName>& relationAttributes(); |
| AXRelation NODELETE attributeToRelationType(const QualifiedName&); |
| enum class AddSymmetricRelation : bool { No, Yes }; |
| static AXRelation NODELETE 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); |
| bool updateLabelFor(HTMLLabelElement&); |
| void updateLabeledBy(Element*); |
| void updateRelationsIfNeeded(); |
| void updateRelationsForTree(ContainerNode&); |
| bool idChangeCanAffectRelations(Element*, const AtomString& oldID, const AtomString& newID) const; |
| void relationsNeedUpdate(bool); |
| void dirtyIsolatedTreeRelations(); |
| HashMap<AXID, AXRelations> relations(); |
| HashMap<AXID, LineRange> mostRecentlyPaintedText(); |
| 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 FrameIdentifier m_frameID; // constant for object's lifetime. |
| #if ENABLE(ACCESSIBILITY_LOCAL_FRAME) |
| std::optional<AXFrameGeometry> m_frameGeometry; |
| IntPoint m_frameViewOriginScrollPosition; |
| #endif |
| OptionSet<ActivityState> m_pageActivityState; |
| HashMap<AXID, Ref<AccessibilityObject>> m_objects; |
| |
| // Should be used only for renderer-only (i.e. no DOM node) accessibility objects. |
| WeakHashMap<RenderObject, AXID, SingleThreadWeakPtrImpl> m_renderObjectIdMapping; |
| WeakHashMap<Widget, AXID, SingleThreadWeakPtrImpl> m_widgetIdMapping; |
| // FIXME: The type for m_nodeIdMapping 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, AXID, WeakPtrImplWithEventTargetData> m_nodeIdMapping; |
| // This map exists as an optimization, reducing the number of HashMap lookups that AXObjectCache::get |
| // has to do to 1 (vs. a m_nodeIdMapping lookup, plus a m_objects lookup). Since this is one of |
| // our hottest functions, the extra memory cost is worth it. |
| WeakHashMap<Node, Ref<AccessibilityObject>, WeakPtrImplWithEventTargetData> m_nodeObjectMapping; |
| |
| WeakHashMap<RenderText, LineRange, SingleThreadWeakPtrImpl> m_mostRecentlyPaintedText; |
| |
| std::unique_ptr<AXComputedObjectAttributeCache> m_computedObjectAttributeCache; |
| #if PLATFORM(COCOA) |
| std::unique_ptr<AXLiveRegionManager> m_liveRegionManager; |
| #endif |
| |
| 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; |
| |
| // Accessed on and off the main thread. |
| static std::atomic<bool> gAccessibilityTextStitchingEnabled; |
| // Accessed on and off the main thread. |
| static std::atomic<bool> gAccessibilityThreadHitTestingEnabled; |
| |
| #if PLATFORM(COCOA) |
| static std::atomic<bool> gAccessibilityDOMIdentifiersEnabled; |
| #endif |
| |
| Timer m_notificationPostTimer; |
| Vector<std::pair<Ref<AccessibilityObject>, AXNotificationWithData>> m_notificationsToPost; |
| HashSet<AXID> m_pendingLayoutCompleteObjectIDs; |
| |
| // Core-AAM requires announcements to be translated before they reach the platform when the page |
| // is presented as a machine translation. |
| struct PendingAnnouncement { |
| Ref<AccessibilityObject> object; |
| Vector<String> segments; |
| String targetLocale; |
| // Zero once this entry is no longer awaiting a reply, whether it succeeded, failed, or timed out. |
| uint64_t translationRequestID { 0 }; |
| MonotonicTime deadline; |
| // Rebuilds the payload from the (possibly translated) segments and enqueues it to post. The |
| // language argument is empty when the segments were left untranslated. |
| Function<void(Vector<String>&&, const String& language)> assembleAndEnqueue; |
| }; |
| |
| Deque<PendingAnnouncement> m_pendingAnnouncements; |
| Timer m_pendingAnnouncementTimeoutTimer; |
| uint64_t m_nextAnnouncementTranslationRequestID { 0 }; |
| // Nodes whose pending children-changed is a re-rendering of content already announced. |
| WeakListHashSet<Node, WeakPtrImplWithEventTargetData> m_deferredReRenderedContent; |
| // The nodes above plus all their ancestors, so isReRenderedContent() is a lookup instead of a |
| // walk. Built on first query and discarded whenever m_deferredReRenderedContent changes. |
| mutable std::optional<WeakHashSet<Node, WeakPtrImplWithEventTargetData>> m_reRenderedContentAndAncestors; |
| |
| #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 }; |
| bool m_needsAriaHiddenModalOverrideCheck { false }; |
| |
| #if PLATFORM(COCOA) |
| bool m_liveRegionManagerInitialized { false }; |
| |
| static std::atomic<bool> gShouldRepostNotificationsForTests; |
| #endif |
| // "Unignored content object count" and not "unignored object count" |
| // because we exclude certain roles from this count (e.g. web-areas, mock objects) |
| // on the basis of them not being meaningful content. |
| unsigned m_unignoredContentObjectCount { 0 }; |
| |
| Timer m_performCacheUpdateTimer; |
| |
| AXTextStateChangeIntent m_textSelectionIntent; |
| WeakHashSet<AccessibilityObject> m_deferredRendererChangedList; |
| WeakHashSet<AccessibilityObject> m_deferredRecomputeActiveSummaryList; |
| WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredRecomputeIsIgnoredList; |
| WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_deferredRecomputeTableIsExposedList; |
| WeakHashSet<AccessibilityNodeObject> m_deferredRecomputeTableCellSlotsList; |
| WeakHashSet<AccessibilityNodeObject> 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; |
| Vector<CanvasFocusPathBoundsChange> m_deferredCanvasFocusPathBoundsChanges; |
| std::optional<std::pair<WeakPtr<Element, WeakPtrImplWithEventTargetData>, WeakPtr<Element, WeakPtrImplWithEventTargetData>>> m_deferredFocusedNodeChange; |
| std::optional<DeferredRemoteFrameFocus> m_deferredRemoteFrameFocus; |
| // A std::nullopt means no focus change is set up for suppression. |
| // A non-std::nullopt value of nullptr means to suppress the next focus clear. |
| // A non-std::nullopt value of an element means to suppress the focus change to that element. |
| // |
| // In all three cases, the term "suppression" refers to the act of not surfacing a focus |
| // change notification to assistive technologies. |
| std::optional<WeakPtr<Element, WeakPtrImplWithEventTargetData>> m_suppressedFocusChange; |
| WeakHashSet<AccessibilityObject> m_deferredUnconnectedObjects; |
| #if PLATFORM(MAC) |
| HashMap<PreSortedObjectType, Vector<Ref<AccessibilityObject>>, IntHash<PreSortedObjectType>, WTF::StrongEnumHashTraits<PreSortedObjectType>> m_deferredUnsortedObjects; |
| Vector<WeakPtr<Document, WeakPtrImplWithEventTargetData>> m_deferredDocumentsWithNewRenderTrees; |
| #endif |
| Vector<DeferredNotificationData> m_deferredNotifications; |
| Vector<Ref<AccessibilityObject>> m_deferredToggledPopovers; |
| |
| const Ref<AXGeometryManager> m_geometryManager; |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| Timer m_buildIsolatedTreeTimer; |
| bool m_deferredRegenerateIsolatedTree { false }; |
| DeferrableOneShotTimer m_selectedTextRangeTimer; |
| Markable<AXID> m_lastDebouncedTextRangeObject; |
| |
| Timer m_updateTreeSnapshotTimer; |
| #endif |
| bool m_isSynchronizingSelection { false }; |
| bool m_performingDeferredCacheUpdate { false }; |
| // True while remove(AXID) is tearing down an object. |
| bool m_isRemovingNode { false }; |
| double m_loadingProgress { 0 }; |
| |
| // Tracks focus landing inside an aria-hidden region. After one rendering update |
| // (giving web developers a chance to move focus in a rAF callback), if focus is |
| // still inside the aria-hidden region, the aria-hidden is permanently overridden. |
| WeakPtr<Element, WeakPtrImplWithEventTargetData> m_pendingAriaHiddenFocusTarget; |
| bool m_needsAriaHiddenFocusCheck { false }; |
| |
| unsigned m_cacheUpdateDeferredCount { 0 }; |
| |
| #if PLATFORM(IOS_FAMILY) |
| bool m_willPresentDatePopover; |
| #endif |
| |
| // Relationships between objects. |
| HashMap<AXID, AXRelations> m_relations; |
| bool m_relationsNeedUpdate { true }; |
| bool m_hasAriaOwnsRelations { false }; |
| bool m_doneInitialRelationsBuild { false }; |
| HashSet<AXID> m_relationTargets; |
| HashMap<AXID, AXRelations> m_recentlyRemovedRelations; |
| WeakHashSet<Element, WeakPtrImplWithEventTargetData> m_elementsWithRelationAttributes; |
| // Ids referenced by a relation attribute (e.g. aria-labelledby) whose target didn't exist when |
| // relations were last built. If an element with one of these ids is later inserted, we must |
| // re-resolve relations. |
| HashSet<AtomString> m_unresolvedRelationTargetIds; |
| // All ids referenced by a relation attribute (resolved or not) as of the last relations build. |
| // Used to decide whether an id-attribute change can affect any relation. |
| HashSet<AtomString> m_referencedRelationTargetIds; |
| |
| #if USE(ATSPI) |
| ListHashSet<RefPtr<AccessibilityObject>> m_deferredParentChangedList; |
| #endif |
| |
| #if PLATFORM(MAC) |
| Markable<AXID> m_lastTextFieldAXID; |
| VisibleSelection m_lastSelection; |
| #endif |
| |
| WeakHashMap<RenderObject, Vector<AXStitchGroup>, SingleThreadWeakPtrImpl> m_stitchGroups; |
| }; |
| |
| inline bool AXObjectCache::accessibilityEnabled() |
| { |
| return !isAccessibilityModeOff(accessibilityMode()); |
| } |
| |
| inline void AXObjectCache::enableAccessibility([[maybe_unused]] AXThreadModePreconditions preconditions) |
| { |
| if (accessibilityMode() == AccessibilityMode::AXThread) { |
| // If we're in AXThread mode, there's nothing more to do. |
| #if !ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| AX_ASSERT_NOT_REACHED(); |
| #endif |
| return; |
| } |
| |
| #if ENABLE(ACCESSIBILITY_ISOLATED_TREE) |
| if (transitionToAXThreadModeIfNeeded(preconditions)) |
| return; |
| #endif |
| // We may not always be allowed to transition to AXThread-mode based on |
| // various factors (e.g. feature flags, system defaults), so make sure |
| // we move to MainThread mode at a minimum. |
| std::ignore = attemptMainThreadModeTransition(); |
| } |
| |
| inline bool AXObjectCache::forceDeferredSpellChecking() |
| { |
| return gForceDeferredSpellChecking; |
| } |
| |
| inline void AXObjectCache::setForceDeferredSpellChecking(bool shouldForce) |
| { |
| gForceDeferredSpellChecking = shouldForce; |
| } |
| |
| #if PLATFORM(COCOA) |
| WEBCORE_EXPORT RetainPtr<NSString> notifyPriorityToAXValueString(NotifyPriority); |
| WEBCORE_EXPORT RetainPtr<NSString> interruptBehaviorToAXValueString(InterruptBehavior); |
| #endif |
| |
| } // namespace WebCore |