/*
 * 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 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; }

    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&);
    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*);

    enum class ForceAXThreadMode : bool { No, Yes };
    static void enableAccessibility(ForceAXThreadMode = ForceAXThreadMode::No);
    // 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(ForceAXThreadMode = ForceAXThreadMode::No);
    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&);

    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(ForceAXThreadMode);
    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&);
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 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 NODELETE 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 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;

#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]] ForceAXThreadMode forceAXThread)
{
    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(forceAXThread))
        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
