/*
 * Copyright (C) 2019-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. AND ITS CONTRIBUTORS ``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 ITS 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.
 */

#include "config.h"

#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
#include "AXIsolatedObject.h"

#include "AXGeometryManager.h"
#include "AXIsolatedTree.h"
#include "AXLogger.h"
#include "AXLoggerBase.h"
#include "AXObjectCacheInlines.h"
#include "AXTextMarker.h"
#include "AXTextRun.h"
#include "AXUtilities.h"
#include "AccessibilityNodeObject.h"
#include "DateComponents.h"
#include "Element.h"
#include "HTMLNames.h"
#include "Logging.h"
#include "PathUtilities.h"
#include "RenderObject.h"
#include "SharedBuffer.h"
#include "WebAnimation.h"
#include <wtf/text/MakeString.h>

#if ENABLE(MODEL_ELEMENT_ACCESSIBILITY)
#include "ModelPlayerAccessibilityChildren.h"
#endif

#if PLATFORM(MAC)
#import <pal/spi/mac/HIServicesSPI.h>
#endif

#if PLATFORM(COCOA)
#include <pal/spi/cocoa/AccessibilitySupportSoftLink.h>
#endif

namespace WebCore {

using namespace HTMLNames;

AXIsolatedObject::AXIsolatedObject(IsolatedObjectData&& data)
    : AXCoreObject(data.axID, data.role, data.getsGeometryFromChildren)
    , m_unresolvedChildrenIDs(WTF::move(data.childrenIDs))
    , m_properties(WTF::move(data.properties))
    , m_tree(WTF::move(data.tree))
    , m_parentID(data.parentID)
    , m_propertyFlags(data.propertyFlags)
{
    AX_ASSERT(!isMainThread());
}

Ref<AXIsolatedObject> AXIsolatedObject::create(IsolatedObjectData&& data)
{
    return adoptRef(*new AXIsolatedObject(WTF::move(data)));
}

AXIsolatedObject::~AXIsolatedObject()
{
    AX_BROKEN_ASSERT(!wrapper());
}

void AXIsolatedObject::updateFromData(IsolatedObjectData&& data)
{
    AX_ASSERT(!isMainThread());

    if (data.axID != objectID() || data.tree->treeID() != treeID()) {
        // Our data should only be updated from the same main-thread equivalent object.
        AX_ASSERT_NOT_REACHED();
        return;
    }

    m_role = data.role;
    m_parentID = data.parentID;
    m_unresolvedChildrenIDs = WTF::move(data.childrenIDs);
    m_childrenDirty = true;
    m_getsGeometryFromChildren = data.getsGeometryFromChildren;

    m_properties = WTF::move(data.properties);
    m_propertyFlags = data.propertyFlags;
}

String AXIsolatedObject::debugDescriptionInternal(bool verbose, std::optional<OptionSet<AXDebugStringOption>> debugOptions) const
{
    StringBuilder result;
    result.append("{"_s);
    result.append("role: "_s, roleToString(role()));
    result.append(", ID "_s, objectID().loggingString());

    if (debugOptions) {
        if (verbose || *debugOptions & AXDebugStringOption::Ignored)
            result.append(isIgnored() ? ", ignored"_s : emptyString());

        if (verbose || *debugOptions & AXDebugStringOption::RelativeFrame) {
            FloatRect frame = relativeFrame();
            result.append(", relativeFrame ((x: "_s, frame.x(), ", y: "_s, frame.y(), "), (w: "_s, frame.width(), ", h: "_s, frame.height(), "))"_s);
        }

        if (verbose || *debugOptions & AXDebugStringOption::RemoteFrameOffset)
            result.append(", remoteFrameOffset ("_s, remoteFrameOffset().x(), ", "_s, remoteFrameOffset().y(), ")"_s);
    }

    result.append("}"_s);
    return result.toString();
}

bool isDefaultValue(AXProperty property, AXPropertyValueVariant& value)
{
    return WTF::switchOn(value,
        [](std::nullptr_t&) { return true; },
        [](Markable<AXID> typedValue) { return !typedValue; },
        [&](String& typedValue) {
            return typedValue.isEmpty(); // null or empty
        },
        [](bool typedValue) { return !typedValue; },
        [](int typedValue) { return !typedValue; },
        [](unsigned typedValue) { return !typedValue; },
        [](double typedValue) { return typedValue == 0.0; },
        [](float typedValue) { return typedValue == 0.0; },
        [](uint64_t typedValue) { return !typedValue; },
        [](AccessibilityButtonState& typedValue) { return typedValue == AccessibilityButtonState::Off; },
        [&](Color& typedValue) {
            if (property == AXProperty::ColorValue)
                return typedValue == Color::black;
            if (property == AXProperty::TextColor)
                return false;
            return typedValue.toColorTypeLossy<SRGBA<uint8_t>>() == Accessibility::defaultColor();
        },
        [](std::unique_ptr<URL>& typedValue) { return !typedValue || *typedValue == URL(); },
        [](LayoutRect& typedValue) { return typedValue == LayoutRect(); },
        [](IntPoint& typedValue) { return typedValue == IntPoint(); },
        [](IntRect& typedValue) { return typedValue == IntRect(); },
        [](FloatPoint& typedValue) { return typedValue == FloatPoint(); },
        [](FloatRect& typedValue) { return typedValue == FloatRect(); },
        [](std::pair<unsigned, unsigned>& typedValue) {
            // (0, 1) is the default for an index range.
            return typedValue == std::pair<unsigned, unsigned>(0, 1);
        },
        [](Vector<AccessibilityText>& typedValue) { return typedValue.isEmpty(); },
        [](Vector<AXID>& typedValue) { return typedValue.isEmpty(); },
        [](Vector<std::pair<Markable<AXID>, Markable<AXID>>>& typedValue) { return typedValue.isEmpty(); },
        [](Vector<String>& typedValue) { return typedValue.isEmpty(); },
        [](OptionSet<AXAncestorFlag>& typedValue) { return typedValue.isEmpty(); },
#if PLATFORM(COCOA)
        [](RetainPtr<NSAttributedString>& typedValue) { return !typedValue; },
        [](RetainPtr<NSView>& typedValue) { return !typedValue; },
        [](RetainPtr<id>& typedValue) { return !typedValue; },
#endif
        [](InputType::Type&) { return false; },
        [](Vector<Vector<Markable<AXID>>>& typedValue) { return typedValue.isEmpty(); },
        [](Vector<AXStitchGroup>& typedValue) { return typedValue.isEmpty(); },
        [](CharacterRange& typedValue) { return !typedValue.location && !typedValue.length; },
        [](std::unique_ptr<AXIDAndCharacterRange>& typedValue) {
            return !typedValue || (!typedValue->first && !typedValue->second.location && !typedValue->second.length);
        },
        [](std::unique_ptr<AXTextRuns>& typedValue) { return !typedValue || !typedValue->size(); },
        [](RetainPtr<CTFontRef>& typedValue) { return !typedValue; },
        [](FontOrientation typedValue) { return typedValue == FontOrientation::Horizontal; },
        [](AXTextRunLineID typedValue) { return !typedValue; },
        [](WallTime& time) { return !time; },
        [](ElementName& name) { return name == ElementName::Unknown; },
        [](DateComponentsType& typedValue) { return typedValue == DateComponentsType::Invalid; },
        [](AccessibilityOrientation) { return false; },
        [](Style::SpeakAs& typedValue) { return typedValue.isNormal(); },
        [](FrameIdentifier&) { return false; },
        [](auto&) {
            AX_ASSERT_NOT_REACHED();
            return false;
        }
    );
}

AccessibilityObject* AXIsolatedObject::associatedAXObject() const
{
    // It is only safe to call this on an AXIsolatedObject when done via a synchronous call from the
    // accessibility thread. Otherwise, |this| could be deleted by the secondary thread (who owns the
    // lifetime of isolated objects) in the middle of this method.
    AX_ASSERT(isMainThread());

    CheckedPtr axObjectCache = this->axObjectCache();
    return axObjectCache ? axObjectCache->objectForID(objectID()) : nullptr;
}

void AXIsolatedObject::setMathscripts(AXProperty property, AccessibilityObject& object)
{
    AccessibilityMathMultiscriptPairs pairs;
    if (property == AXProperty::MathPrescripts)
        object.mathPrescripts(pairs);
    else if (property == AXProperty::MathPostscripts)
        object.mathPostscripts(pairs);

    if (pairs.isEmpty())
        return;

    auto idPairs = pairs.map([](auto& mathPair) {
        return std::pair { mathPair.first ? Markable { mathPair.first->objectID() } : std::nullopt, mathPair.second ? Markable { mathPair.second->objectID() } : std::nullopt };
    });
    setProperty(property, WTF::move(idPairs));
}

void AXIsolatedObject::setObjectProperty(AXProperty property, AXCoreObject* object)
{
    setProperty(property, object ? Markable { object->objectID() } : std::nullopt);
}

void AXIsolatedObject::setObjectVectorProperty(AXProperty property, const AccessibilityChildrenVector& objects)
{
    setProperty(property, axIDs(objects));
}

void AXIsolatedObject::setProperty(AXProperty property, AXPropertyValueVariant&& value)
{
    if (const bool* boolValue = std::get_if<bool>(&value)) {
        if (std::optional propertyFlag = convertToPropertyFlag(property)) {
            setPropertyFlag(*propertyFlag, *boolValue);
            return;
        }
    }

    if (isDefaultValue(property, value))
        removePropertyInVector(property);
    else {
        setPropertyInVector(property, WTF::move(value));

        if (property == AXProperty::RelativeFrame) {
            // If we're setting a RelativeFrame, clear the getsGeometryFromChildren flag
            // since the element now has an explicit frame (e.g., from drawFocusIfNeeded).
            m_getsGeometryFromChildren = false;
        }
    }
}

void AXIsolatedObject::detachRemoteParts(AccessibilityDetachmentType)
{
    AX_ASSERT(!isMainThread());

    for (const auto& child : m_children)
        child->detachFromParent();

    for (const auto& childID : m_unresolvedChildrenIDs) {
        // Also loop through unresolved IDs in case they have become resolved.
        if (RefPtr child = tree().objectForID(childID))
            child->detachFromParent();
    }
    m_unresolvedChildrenIDs.clear();
    m_children.clear();
    m_childrenDirty = false;
}

#if !PLATFORM(MAC)
bool AXIsolatedObject::isDetached() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}
#endif

void AXIsolatedObject::detachFromParent()
{
    m_parentID = { };
}

void AXIsolatedObject::setChildrenIDs(Vector<AXID>&& ids)
{
    m_unresolvedChildrenIDs = WTF::move(ids);
    m_childrenDirty = true;
}

const AXCoreObject::AccessibilityChildrenVector& AXIsolatedObject::children(bool updateChildrenIfNeeded)
{
#if USE(APPLE_INTERNAL_SDK)
    AX_ASSERT(_AXSIsolatedTreeModeFunctionIsAvailable() && ((_AXSIsolatedTreeMode_Soft() == AXSIsolatedTreeModeSecondaryThread && !isMainThread())
        || (_AXSIsolatedTreeMode_Soft() == AXSIsolatedTreeModeMainThread && isMainThread())));
#elif USE(ATSPI)
    AX_ASSERT(!isMainThread());
#endif
    if (updateChildrenIfNeeded && m_childrenDirty) {
        unsigned index = 0;
        Vector<AXID> unresolvedIDs;
        m_children = WTF::compactMap(m_unresolvedChildrenIDs, [&] (auto& childID) -> std::optional<Ref<AXCoreObject>> {
            if (RefPtr child = tree().objectForID(childID)) {
                if (setChildIndexInParent(*child, index))
                    ++index;
                return child.releaseNonNull();
            }
            unresolvedIDs.append(childID);
            return std::nullopt;
        });
        m_childrenDirty = false;
        m_unresolvedChildrenIDs = WTF::move(unresolvedIDs);
        // Having any unresolved children IDs at this point means we should've had a child / children, but they didn't
        // exist in tree().objectForID(), so we were never able to hydrate it into an object.
        AX_BROKEN_ASSERT(m_unresolvedChildrenIDs.isEmpty());

#if ASSERT_ENABLED
        verifyChildrenIndexInParent();
#endif
    }
    return m_children;
}

AXIsolatedTree::CachedUnignoredChildren& AXIsolatedObject::ensureCachedUnignoredChildren()
{
    auto& cache = tree().cachedUnignoredChildrenMap();
    auto result = cache.ensure(objectID(), [&] {
        auto children = AXCoreObject::unignoredChildren();
        bool hasPotentialStitchable = false;
        bool hasCrossFrameChild = false;
        for (const auto& child : children) {
            if (!hasPotentialStitchable && child->hasStitchableRole())
                hasPotentialStitchable = true;
#if ENABLE_ACCESSIBILITY_LOCAL_FRAME
            if (child->isLocalFrame())
                hasCrossFrameChild = true;
            if (hasPotentialStitchable && hasCrossFrameChild)
                break;
#else
            if (hasPotentialStitchable)
                break;
#endif
        }
        return AXIsolatedTree::CachedUnignoredChildren { WTF::move(children), hasPotentialStitchable, hasCrossFrameChild };
    });
    return result.iterator->value;
}

#if ENABLE(INCLUDE_IGNORED_IN_CORE_AX_TREE)
AXCoreObject::AccessibilityChildrenVector AXIsolatedObject::unignoredChildren(bool)
{
    AX_ASSERT(!isMainThread());
    return ensureCachedUnignoredChildren().children;
}
#endif

AXCoreObject::AccessibilityChildrenVector AXIsolatedObject::stitchedUnignoredChildren()
{
    AX_ASSERT(!isMainThread());
    auto& entry = ensureCachedUnignoredChildren();
    if (!entry.hasPotentialStitchable)
        return entry.children;
    auto copy = entry.children;
    copy.removeAllMatching([] (const auto& child) {
        if (!child->hasStitchableRole())
            return false;
        std::optional stitchedIntoID = child->stitchedIntoID();
        return stitchedIntoID && *stitchedIntoID != child->objectID();
    });
    return copy;
}

size_t AXIsolatedObject::stitchedUnignoredChildrenCount()
{
    AX_ASSERT(!isMainThread());
    auto& entry = ensureCachedUnignoredChildren();
    if (!entry.hasPotentialStitchable)
        return entry.children.size();
    size_t count = 0;
    for (const auto& child : entry.children) {
        if (!child->hasStitchableRole()) {
            ++count;
            continue;
        }
        std::optional stitchedIntoID = child->stitchedIntoID();
        if (!stitchedIntoID || *stitchedIntoID == child->objectID())
            ++count;
    }
    return count;
}

const AXCoreObject::AccessibilityChildrenVector* AXIsolatedObject::cachedUnignoredChildren()
{
    AX_ASSERT(!isMainThread());
    return &ensureCachedUnignoredChildren().children;
}

const AXCoreObject::AccessibilityChildrenVector* AXIsolatedObject::cachedStitchedUnignoredChildren()
{
    AX_ASSERT(!isMainThread());
    auto& entry = ensureCachedUnignoredChildren();
    if (!entry.hasPotentialStitchable)
        return &entry.children;
    return nullptr;
}

std::optional<bool> AXIsolatedObject::cachedHasCrossFrameChild()
{
    AX_ASSERT(!isMainThread());
#if ENABLE_ACCESSIBILITY_LOCAL_FRAME
    auto& entry = ensureCachedUnignoredChildren();
    return entry.hasCrossFrameChild || (entry.children.isEmpty() && crossFrameChildObject());
#else
    return false;
#endif
}

AXCoreObject::AccessibilityChildrenVector AXIsolatedObject::crossFrameUnignoredChildrenInRange(size_t start, size_t maxCount)
{
    AX_ASSERT(!isMainThread());
    auto& entry = ensureCachedUnignoredChildren();

#if ENABLE_ACCESSIBILITY_LOCAL_FRAME
    if (entry.hasCrossFrameChild || (entry.children.isEmpty() && crossFrameChildObject())) {
        auto children = crossFrameUnignoredChildren();
        if (start >= children.size())
            return { };
        size_t count = std::min(maxCount, children.size() - start);
        return AccessibilityChildrenVector { children.span().subspan(start, count) };
    }
#endif

    if (entry.children.isEmpty())
        return { };

    if (!entry.hasPotentialStitchable) {
        if (start >= entry.children.size())
            return { };
        size_t count = std::min(maxCount, entry.children.size() - start);
        return AccessibilityChildrenVector { entry.children.span().subspan(start, count) };
    }

    // Has potential stitchable children -- need to skip stitched-away elements
    // while computing the range relative to the stitched result.
    AccessibilityChildrenVector result;
    size_t stitchedIndex = 0;
    for (const auto& child : entry.children) {
        if (child->hasStitchableRole()) {
            std::optional stitchedIntoID = child->stitchedIntoID();
            if (stitchedIntoID && *stitchedIntoID != child->objectID())
                continue;
        }
        if (stitchedIndex >= start + maxCount)
            break;
        if (stitchedIndex >= start)
            result.append(child);
        ++stitchedIndex;
    }
    return result;
}

void AXIsolatedObject::setSelectedChildren(const AccessibilityChildrenVector& selectedChildren)
{
    AX_ASSERT(selectedChildren.isEmpty() || selectedChildren[0]->isAXIsolatedObjectInstance());

    auto childrenIDs = axIDs(selectedChildren);
    performFunctionOnMainThread([selectedChildrenIDs = WTF::move(childrenIDs), protectedThis = Ref { *this }] (auto* object) {
        if (selectedChildrenIDs.isEmpty()) {
            object->setSelectedChildren({ });
            return;
        }

        CheckedPtr axObjectCache = protectedThis->axObjectCache();
        if (!axObjectCache)
            return;

        object->setSelectedChildren(axObjectCache->objectsForIDs(selectedChildrenIDs));
    });
}

bool AXIsolatedObject::isInDescriptionListTerm() const
{
    return Accessibility::findAncestor<AXIsolatedObject>(*this, false, [&] (const auto& ancestor) {
        return ancestor.role() == AccessibilityRole::DescriptionListTerm;
    });
}

AXIsolatedObject* AXIsolatedObject::cellForColumnAndRow(unsigned columnIndex, unsigned rowIndex)
{
    size_t index = indexOfProperty(AXProperty::CellSlots);
    if (index == notFound)
        return nullptr;

    auto cellID = WTF::switchOn(m_properties[index].second,
        [&] (Vector<Vector<Markable<AXID>>>& cellSlots) -> std::optional<AXID> {
            if (rowIndex >= cellSlots.size() || columnIndex >= cellSlots[rowIndex].size())
                return std::nullopt;
            return cellSlots[rowIndex][columnIndex];
        },
        [] (auto&) -> std::optional<AXID> { return std::nullopt; }
    );
    return tree().objectForID(cellID);
}

void AXIsolatedObject::accessibilityText(Vector<AccessibilityText>& texts) const
{
    texts = vectorAttributeValue<AccessibilityText>(AXProperty::AccessibilityText);
}

const Vector<AXStitchGroup>* AXIsolatedObject::stitchGroupsView() const
{
    size_t index = indexOfProperty(AXProperty::StitchGroups);
    if (index == notFound)
        return nullptr;

    return WTF::switchOn(m_properties[index].second,
        [] (const Vector<AXStitchGroup>& typedValue) -> const Vector<AXStitchGroup>* { return &typedValue; },
        [] (auto&) -> const Vector<AXStitchGroup>* { return nullptr; }
    );
}

std::optional<AXStitchGroup> AXIsolatedObject::stitchGroup(IncludeGroupMembers includeGroupMembers) const
{
    if (!AXObjectCache::isAXTextStitchingEnabled())
        return { };

    RefPtr blockFlowAncestor = downcast<AXIsolatedObject>(blockFlowAncestorForStitchable());
    if (!blockFlowAncestor)
        return { };

    return stitchGroupFromGroups(blockFlowAncestor->stitchGroupsView(), includeGroupMembers);
}

void AXIsolatedObject::insertMathPairs(Vector<std::pair<Markable<AXID>, Markable<AXID>>>& isolatedPairs, AccessibilityMathMultiscriptPairs& pairs)
{
    for (const auto& pair : isolatedPairs) {
        AccessibilityMathMultiscriptPair prescriptPair;
        if (auto* object = tree().objectForID(pair.first))
            prescriptPair.first = object;
        if (auto* object = tree().objectForID(pair.second))
            prescriptPair.second = object;
        pairs.append(prescriptPair);
    }
}

void AXIsolatedObject::mathPrescripts(AccessibilityMathMultiscriptPairs& pairs)
{
    auto isolatedPairs = vectorAttributeValue<std::pair<Markable<AXID>, Markable<AXID>>>(AXProperty::MathPrescripts);
    insertMathPairs(isolatedPairs, pairs);
}

void AXIsolatedObject::mathPostscripts(AccessibilityMathMultiscriptPairs& pairs)
{
    auto isolatedPairs = vectorAttributeValue<std::pair<Markable<AXID>, Markable<AXID>>>(AXProperty::MathPostscripts);
    insertMathPairs(isolatedPairs, pairs);
}

std::optional<AXCoreObject::AccessibilityChildrenVector> AXIsolatedObject::mathRadicand()
{
    if (indexOfProperty(AXProperty::MathRadicand) != notFound) {
        Vector<Ref<AXCoreObject>> radicand;
        fillChildrenVectorForProperty(AXProperty::MathRadicand, radicand);
        return { radicand };
    }
    return std::nullopt;
}

AXIsolatedObject* AXIsolatedObject::scrollBar(AccessibilityOrientation orientation)
{
    return objectAttributeValue(orientation == AccessibilityOrientation::Vertical ? AXProperty::VerticalScrollBar : AXProperty::HorizontalScrollBar);
}

void AXIsolatedObject::setARIAGrabbed(bool value)
{
    performFunctionOnMainThread([value] (auto* object) {
        object->setARIAGrabbed(value);
    });
}

void AXIsolatedObject::setIsExpanded(bool value)
{
    performFunctionOnMainThread([value] (auto* object) {
        object->setIsExpanded(value);
    });
}

bool AXIsolatedObject::performDismissAction()
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> bool {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->performDismissAction();
        return false;
    }, Accessibility::InteractiveTimeout, false);
}

void AXIsolatedObject::performDismissActionIgnoringResult()
{
    performFunctionOnMainThread([] (auto* axObject) {
        axObject->performDismissActionIgnoringResult();
    });
}

FloatSize AXIsolatedObject::imageDataSize() const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> FloatSize {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->imageDataSize();
        return { };
    }, Accessibility::GeneralPropertyTimeout, FloatSize());
}

RefPtr<SharedBuffer> AXIsolatedObject::imageData(const AXImageDataParameters& parameters) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([parameters, context = mainThreadContext()] () -> RefPtr<SharedBuffer> {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->imageData(parameters);
        return nullptr;
    }, Accessibility::ImageDataTimeout, nullptr);
}

void AXIsolatedObject::scrollToMakeVisible() const
{
    performFunctionOnMainThread([] (auto* axObject) {
        axObject->scrollToMakeVisible();
    });
}

void AXIsolatedObject::scrollToMakeVisibleWithSubFocus(IntRect&& rect) const
{
    performFunctionOnMainThread([rect = WTF::move(rect)] (auto* axObject) mutable {
        axObject->scrollToMakeVisibleWithSubFocus(WTF::move(rect));
    });
}

void AXIsolatedObject::scrollToGlobalPoint(IntPoint&& point) const
{
    performFunctionOnMainThread([point = WTF::move(point)] (auto* axObject) mutable {
        axObject->scrollToGlobalPoint(WTF::move(point));
    });
}

bool AXIsolatedObject::setValue(float value)
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&value, context = mainThreadContext()] () -> bool {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->setValue(value);
        return false;
    }, Accessibility::InteractiveTimeout, false);
}

void AXIsolatedObject::setValueIgnoringResult(float value)
{
    performFunctionOnMainThread([value] (auto* object) {
        object->setValueIgnoringResult(value);
    });
}

bool AXIsolatedObject::setValue(const String& value)
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&value, context = mainThreadContext()] () -> bool {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->setValue(value);
        return false;
    }, Accessibility::InteractiveTimeout, false);
}

void AXIsolatedObject::setValueIgnoringResult(const String& value)
{
    performFunctionOnMainThread([value = value.isolatedCopy()] (auto* object) {
        object->setValueIgnoringResult(value);
    });
}

void AXIsolatedObject::setSelected(bool value)
{
    performFunctionOnMainThread([value] (auto* object) {
        object->setSelected(value);
    });
}

void AXIsolatedObject::setSelectedRows(AccessibilityChildrenVector&& selectedRows)
{
    auto rowIDs = axIDs(selectedRows);
    performFunctionOnMainThread([selectedRowIDs = WTF::move(rowIDs), protectedThis = Ref { *this }] (auto* object) {
        if (selectedRowIDs.isEmpty()) {
            object->setSelectedRows({ });
            return;
        }

        CheckedPtr axObjectCache = protectedThis->axObjectCache();
        if (!axObjectCache)
            return;

        object->setSelectedRows(axObjectCache->objectsForIDs(selectedRowIDs));
    });
}

void AXIsolatedObject::setFocused(bool value)
{
    performFunctionOnMainThread([value] (auto* object) {
        object->setFocused(value);
    });
}

String AXIsolatedObject::selectedText() const
{
    AX_ASSERT(!isMainThread());
    return selectedTextMarkerRange().toString();
}

void AXIsolatedObject::setSelectedText(const String& value)
{
    performFunctionOnMainThread([value = value.isolatedCopy()] (auto* object) {
        object->setSelectedText(value);
    });
}

void AXIsolatedObject::setSelectedTextRange(CharacterRange&& range)
{
    performFunctionOnMainThread([range = WTF::move(range)] (auto* object) mutable {
        object->setSelectedTextRange(WTF::move(range));
    });
}

SRGBA<uint8_t> AXIsolatedObject::colorValue() const
{
    size_t index = indexOfProperty(AXProperty::ColorValue);
    if (index == notFound) {
        // Don't fallback to returning the default Color() value, as that is transparent black,
        // but we want to return opaque black as a default for this property.
        return Color::black;
    }

    return WTF::switchOn(m_properties[index].second,
        [] (const Color& typedValue) -> SRGBA<uint8_t> { return typedValue.toColorTypeLossy<SRGBA<uint8_t>>(); },
        [] (const auto&) -> SRGBA<uint8_t> {
            AX_ASSERT_NOT_REACHED();
            return Color().toColorTypeLossy<SRGBA<uint8_t>>();
        }
    );
}

RefPtr<AXCoreObject> AXIsolatedObject::accessibilityHitTest(const IntPoint& point) const
{
    // For layout tests, we want to exercise hit testing using the accessibility thread, so don't
    // use any caching or main-thread calls in testing contexts.
    if (AXObjectCache::clientIsInTestMode()) [[unlikely]] {
        // In layout tests, we pass page-relative coordinates. Convert to screen for approximateHitTest, which works in screen-space.
#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)
#if PLATFORM(MAC)
        if (RefPtr root = tree().rootNode()) {
            auto rootPosition = root->screenRelativePosition();
            auto rootSize = root->size();
            IntPoint screenPoint(rootPosition.x() + point.x(), rootPosition.y() + rootSize.height() - point.y());
            return approximateHitTest(screenPoint);
        }
#else
        // If ITM exists on non-macOS platforms, the coordinate conversion above (using a bottom-left origin) will be incorrect.
        AX_ASSERT_NOT_REACHED();
#endif // PLATFORM(MAC)
#endif // ENABLE(ACCESSIBILITY_LOCAL_FRAME)
        return approximateHitTest(point);
    }

    // Check if we have a cached result for this point.
    RefPtr geometryManager = tree().geometryManager();
    if (auto cachedID = geometryManager ? geometryManager->cachedHitTestResult(point) : std::nullopt)
        return tree().objectForID(*cachedID);

    struct HitTestResult {
        AXID resultID;
        bool shouldCache;
    };
    auto hitTestOnMainThread = [axID = objectID(), treeID = treeID(), point] -> std::optional<HitTestResult> {
        if (WeakPtr<AXObjectCache> cache = AXTreeStore<AXObjectCache>::axObjectCacheForID(treeID)) {
            RefPtr object = cache->objectForID(axID);
            auto pageRelativePoint = cache->mapScreenPointToPagePoint(point);
            if (RefPtr hitTestResult = object ? object->accessibilityHitTest(pageRelativePoint) : nullptr) {
                // Don't cache elements with running animations since their bounds may change frequently.
                bool shouldCache = true;
                if (RefPtr element = hitTestResult->element()) {
                    if (auto* animations = element->animations(std::nullopt)) {
                        for (auto& animation : *animations) {
                            if (animation->playState() == WebAnimation::PlayState::Running) {
                                shouldCache = false;
                                break;
                            }
                        }
                    }
                    if (shouldCache && element->hasRunningTransitions(std::nullopt))
                        shouldCache = false;
                }

                return HitTestResult { hitTestResult->objectID(), shouldCache };
            }
        }
        return std::nullopt;
    };

    auto mainThreadValue = Accessibility::retrieveValueFromMainThreadWithTimeout(hitTestOnMainThread, Accessibility::HitTestTimeout);
    if (std::optional<std::optional<HitTestResult>> optionalResult = mainThreadValue.value) {
        if (*optionalResult) {
            HitTestResult result = **optionalResult;
            if (geometryManager) {
                if (result.shouldCache) {
                    geometryManager->cacheHitTestResult(result.resultID, point);
                    geometryManager->expandHitTestCacheAroundPoint(point, *treeID());
                }
            }
            return tree().objectForID(result.resultID);
        }
        return nullptr;
    }

    // If we're here (because !mainThreadValue.value), the request to the main-thread timed out.
    // Let's use the accessibility thread to serve an approximate hit test. One optimization we
    // could consider is computing the hit-test on the accessibility thread while waiting for the
    // main-thread to compute the result (or timeout).
    return approximateHitTest(point);
}

RefPtr<AXIsolatedObject> AXIsolatedObject::approximateHitTest(const IntPoint& point) const
{
    FloatRect bounds;
    IntPoint adjustedPoint = point;
#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)
    bounds = screenRelativeRect();
#else
    if (!AXObjectCache::clientIsInTestMode()) [[likely]] {
        // For "real" off-main-thread hit tests (i.e. those forwarded to us by WKAccessibilityWebPageObjectMac),
        // the coordinates are in the screen space. Note this may not be true for non-Mac platforms when we
        // expand ITM to said other platforms (e.g. iOS).
        bounds = screenRelativeRect();
    } else {
        // In a layout text context, the passed coordinates are page-relative, so use relative-frame.
        bounds = relativeFrame();
    }

    adjustedPoint.moveBy(-remoteFrameOffset());
#endif

    if (!bounds.contains(adjustedPoint) && !bounds.isEmpty()) {
        // If our bounds are empty, we cannot possibly contain the hit-point. However, this may happen
        // because we haven't got geometry for |this| yet, but maybe our children contain the hit-point,
        // so check them before exiting. If our bounds are not empty and we don't contain the hit-point,
        // we can exit now.
        //
        // This early-exit makes the assumption that parents always contain their children's bounds, which
        // is generally true, but not always. This is OK since it's an approximate hit-test, but maybe we
        // can improve this heuristic in the future.
        return nullptr;
    }

    AXIsolatedObject* mutableThis = const_cast<AXIsolatedObject*>(this);
    auto children = mutableThis->unignoredChildren();
    for (int i = children.size() - 1; i >= 0; --i) {
        RefPtr child = downcast<AXIsolatedObject>(children[i].ptr());
        if (!child)
            continue;

        if (child->isTableColumn()) {
            // Returning columns via hit testing is typically not what ATs expect as they are mock objects
            // and thus not backed by any real DOM node. Returning nullptr allows us to return the table
            // cell (or cell contents) instead, which is typically more useful for ATs like Hover Text.
            continue;
        }

#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)
        if (child->role() == AccessibilityRole::LocalFrame) {
            // LocalFrames have no unique platform wrapper (they delegate to the child tree), so we must explicitly
            // cross into the child tree here.
            // RemoteFrames don't have this problem since they have a wrapper that points to the remote accessibility
            // object, and the frameworks recursively hit test in the bridged process.
            // This also helps guard against potential frame wrapper issues, like the one noted in
            // AXIsolatedTree::applyPendingChangesLocked.
            if (RefPtr crossFrameChild = child->crossFrameChildObject()) {
                if (RefPtr hitChild = crossFrameChild->approximateHitTest(point))
                    return hitChild;
            }
            continue;
        }
#endif

        if (RefPtr hitChild = child->approximateHitTest(point))
            return hitChild;
    }

    if (bounds.isEmpty())
        return nullptr;

    RefPtr result = mutableThis;
    if (result && result->isIgnored()) {
        // FIXME: If |result| is the label of a control, a hit test should return the control.

        result = result->parentObjectUnignored();
    }

    if (result) {
        if (std::optional stitchedIntoID = result->stitchedIntoID()) {
            if (auto* stitchRepresentative = tree().objectForID(*stitchedIntoID))
                return stitchRepresentative;
        }
    }
    return result;
}

TextEmissionBehavior AXIsolatedObject::textEmissionBehavior() const
{
    if (hasPropertyFlag(AXProperty::IsTextEmissionBehaviorNewline))
        return TextEmissionBehavior::Newline;
    if (hasPropertyFlag(AXProperty::IsTextEmissionBehaviorDoubleNewline))
        return TextEmissionBehavior::DoubleNewline;
    if (hasPropertyFlag(AXProperty::IsTextEmissionBehaviorTab))
        return TextEmissionBehavior::Tab;

    return TextEmissionBehavior::None;
}

IntPoint AXIsolatedObject::intPointAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return IntPoint();

    return WTF::switchOn(m_properties[index].second,
        [] (const IntPoint& typedValue) -> IntPoint { return typedValue; },
        [] (auto&) { return IntPoint(); }
    );
}

AXIsolatedObject* AXIsolatedObject::objectAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return nullptr;

    return tree().objectForID(WTF::switchOn(m_properties[index].second,
        [] (const Markable<AXID>& typedValue) -> std::optional<AXID> { return typedValue; },
        [] (auto&) { return std::optional<AXID> { }; }
    ));
}

template<typename T>
T AXIsolatedObject::rectAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return T { };

    return WTF::switchOn(m_properties[index].second,
        [] (const T& typedValue) -> T { return typedValue; },
        [] (auto&) { return T { }; }
    );
}

template<typename T>
Vector<T> AXIsolatedObject::vectorAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return { };

    return WTF::switchOn(m_properties[index].second,
        [] (const Vector<T>& typedValue) -> Vector<T> { return typedValue; },
        [] (auto&) { return Vector<T>(); }
    );
}

Style::SpeakAs AXIsolatedObject::speakAsAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return CSS::Keyword::Normal { };

    return WTF::switchOn(m_properties[index].second,
        [](const Style::SpeakAs& typedValue) -> Style::SpeakAs { return typedValue; },
        [](auto&) -> Style::SpeakAs { return CSS::Keyword::Normal { }; }
    );
}

std::pair<unsigned, unsigned> AXIsolatedObject::indexRangePairAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return std::pair<unsigned, unsigned>(0, 1);

    return WTF::switchOn(m_properties[index].second,
        [] (const std::pair<unsigned, unsigned>& typedValue) -> std::pair<unsigned, unsigned> { return typedValue; },
        [] (auto&) { return std::pair<unsigned, unsigned>(0, 1); }
    );
}

template<typename T>
std::optional<T> AXIsolatedObject::optionalAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return std::nullopt;

    return WTF::switchOn(m_properties[index].second,
        [] (const T& typedValue) -> std::optional<T> { return typedValue; },
        [] (const auto&) -> std::optional<T> {
            AX_ASSERT_NOT_REACHED();
            return std::nullopt;
        }
    );
}

uint64_t AXIsolatedObject::uint64AttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return 0;

    return WTF::switchOn(m_properties[index].second,
        [] (const uint64_t& typedValue) -> uint64_t { return typedValue; },
        [] (auto&) -> uint64_t { return 0; }
    );
}

URL AXIsolatedObject::urlAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return URL();

    return WTF::switchOn(m_properties[index].second,
        [] (const std::unique_ptr<URL>& typedValue) -> URL {
            AX_ASSERT(typedValue.get());
            return *typedValue.get();
        },
        [] (auto&) { return URL(); }
    );
}

bool AXIsolatedObject::supportsPath() const
{
    return boolAttributeValue(AXProperty::SupportsPath) || AXCoreObject::supportsPath();
}

// Collects viewport-relative per-line rects for a text object, clamped to
// the most recently painted (visible) lines. When trimming is enabled for a
// side, one leading/trailing whitespace character is skipped so the
// VoiceOver cursor hugs actual text content and doesn't clip adjacent elements.
static Vector<FloatRect> collectPaintedLineRects(const AXIsolatedObject& object, const HashMap<AXID, LineRange>& paintedText, bool trimLeading = true, bool trimTrailing = true)
{
    const auto* runs = object.textRuns();
    if (!runs || !runs->size())
        return { };

    unsigned start = 0;
    unsigned end = runs->totalLength();
    if (!paintedText.isEmpty()) {
        // When paint data exists, only include lines that were actually painted
        // (visible). If no paint data exists yet (e.g. before the first
        // paint cycle), fall through and use all text so that paths are
        // available immediately from text runs cached at tree-build time.
        auto iterator = paintedText.find(object.objectID());
        if (iterator == paintedText.end())
            return { };
        const auto& paintedRange = iterator->value;
        start = paintedRange.startLineIndex ? runs->runLengthSumTo(paintedRange.startLineIndex - 1) : 0;
        end = runs->runLengthSumTo(paintedRange.endLineIndex);
        if (start >= end)
            return { };
    }

    // Only skip the first/last whitespace, as multiple spaces may be intentional (e.g. &nbsp;)
    // and thus should have some representation.
    if (trimLeading && start < end && start < runs->text.length() && runs->text[start] == ' ')
        ++start;
    if (trimTrailing && end > start && end - 1 < runs->text.length() && runs->text[end - 1] == ' ')
        --end;
    if (start >= end)
        return { };

    auto rects = runs->localRectsPerLine(start, end, object.fontOrientation());
    auto frame = object.relativeFrame();
    for (auto& rect : rects)
        rect.move(frame.x(), frame.y());
    return rects;
}

// Clips, inflates, ensures overlap, and builds a shrink-wrapped path from
// per-line rects. Returns an empty path if fewer than 2 rects remain, as
// there's no point in exposing a path in that case (the element's rect is fine).
static Path buildPathFromLineRects(Vector<FloatRect>&& rects, const FloatRect& clipRect, FontOrientation orientation)
{
    rects.removeAllMatching([&clipRect](auto& rect) {
        return !rect.intersects(clipRect);
    });

    for (auto& rect : rects)
        rect.intersect(clipRect);
    if (rects.size() < 2)
        return { };

    // ATs like VoiceOver use the path to render their cursor.
    // Add a bit of padding to guarantee we avoid visually clipping text
    // underneath the cursor.
    static constexpr float lineRectPadding = 2;
    for (auto& rect : rects)
        rect.inflate(lineRectPadding);

    // Ensure adjacent line rects overlap by at least 1px in the block direction.
    // Without this, pathWithShrinkWrappedRects may produce a path with hairline
    // gaps between lines where the cursor appears to "break" visually.
    bool isHorizontal = orientation == FontOrientation::Horizontal;
    for (size_t i = 1; i < rects.size(); ++i) {
        if (isHorizontal) {
            float gap = rects[i].y() - rects[i - 1].maxY();
            if (gap >= 0)
                rects[i].shiftYEdgeTo(rects[i - 1].maxY() - 1);
        } else {
            // Columns may progress left-to-right (vertical-lr) or
            // right-to-left (vertical-rl). Close gaps in either direction.
            if (rects[i].x() > rects[i - 1].maxX())
                rects[i].shiftXEdgeTo(rects[i - 1].maxX() - 1);
            else if (rects[i].maxX() < rects[i - 1].x())
                rects[i].shiftMaxXEdgeTo(rects[i - 1].x() + 1);
        }
    }

    return PathUtilities::pathWithShrinkWrappedRects(rects, 0);
}

// Walks the link's entire subtree to determine if it qualifies for a
// shrink-wrapped text path (2-3 lines of simple inline text). Returns an empty
// path for non-qualifying links (non-text content, block-flow containers,
// unignored groups, 1 or 4+ lines of text). We avoid 4+ line links because
// sometimes authors put lots of content into one link, in which a case rect-cursor
// looks better.
static Path elementPathForLink(const AXIsolatedObject& link, AXIsolatedTree& tree, const HashMap<AXID, LineRange>& paintedText)
{
    // If the link has a cached path (border-radius, clip-path), use it.
    if (RefPtr geometryManager = tree.geometryManager()) {
        if (std::optional cachedPath = geometryManager->cachedPathForID(link.objectID()))
            return *cachedPath;
    }

    // Walk the entire subtree collecting per-line rects from text descendants.
    Vector<FloatRect> lineRects;
    unsigned totalLines = 0;
    bool bail = false;

    auto walkDescendants = [&](const AXIsolatedObject& object, auto& self) -> void {
        if (bail)
            return;
        for (const auto& child : const_cast<AXIsolatedObject&>(object).children()) {
            if (bail)
                return;

            Ref isolatedChild = downcast<AXIsolatedObject>(child.get());
            if (isolatedChild->isStaticText()) {
                auto rects = collectPaintedLineRects(isolatedChild.get(), paintedText);
                totalLines += rects.size();
                lineRects.appendVector(WTF::move(rects));
                if (totalLines >= 4) {
                    bail = true;
                    return;
                }
                continue;
            }

            // Pass through ignored, non-block-flow groups.
            if (isolatedChild->isGroup() && isolatedChild->isIgnored() && !isolatedChild->isBlockFlow()) {
                self(isolatedChild.get(), self);
                continue;
            }

            // Non-text, unignored group, or block-flow group.
            bail = true;
            return;
        }
    };
    walkDescendants(link, walkDescendants);

    if (bail || totalLines <= 1)
        return { };

    // Build a shrink-wrapped path for 2-3 lines of text.
    return buildPathFromLineRects(WTF::move(lineRects), link.relativeFrame(), link.fontOrientation());
}

Path AXIsolatedObject::elementPath() const
{
    const auto& paintedText = tree().mostRecentlyPaintedText();

    // Stitch group representatives aggregate rects from all group members.
    // Check this first because the representative's own text runs may not span
    // multiple lines, even though the combined text of all members does.
    if (auto group = stitchGroupIfRepresentative()) {
        Vector<FloatRect> rects;
        auto clipFrame = relativeFrame();
        auto& members = group->members();
        for (size_t i = 0; i < members.size(); ++i) {
            RefPtr member = tree().objectForID(members[i]);
            if (!member)
                continue;
            clipFrame.unite(member->relativeFrame());
            // Only trim the leading space of the first member and trailing
            // space of the last member. Internal spaces are part of the
            // combined text and should not be trimmed.
            bool isFirst = !i;
            bool isLast = i == members.size() - 1;
            rects.appendVector(collectPaintedLineRects(*member, paintedText, /* trimLeading */ isFirst, /* trimTrailing */ isLast));
        }
        auto path = buildPathFromLineRects(WTF::move(rects), clipFrame, fontOrientation());
        if (!path.isEmpty())
            return path;
    }

    // Multi-line text objects compute their path on-demand from text runs.
    if (const auto* runs = textRuns(); runs && runs->size() >= 2) {
        bool isMultiLine = false;
        for (size_t i = 1; i < runs->size(); ++i) {
            if (runs->lineID(i) != runs->lineID(i - 1)) {
                isMultiLine = true;
                break;
            }
        }
        if (isMultiLine) {
            auto rects = collectPaintedLineRects(*this, paintedText);
            auto path = buildPathFromLineRects(WTF::move(rects), relativeFrame(), fontOrientation());
            if (!path.isEmpty())
                return path;
        }
    }

    // Labels remapped to StaticText don't have their own text runs.
    // Aggregate text runs from descendant objects (which may be ignored,
    // since the label subsumes its text children).
    if (isStaticTextLabel()) {
        Vector<FloatRect> allRects;
        auto clipFrame = relativeFrame();

        auto collectFromDescendants = [&](const AXIsolatedObject& object, auto& self) -> void {
            for (const auto& child : const_cast<AXIsolatedObject&>(object).children()) {
                Ref isolatedChild = downcast<AXIsolatedObject>(child.get());
                if (isolatedChild->isStaticText() && isolatedChild->textRuns()) {
                    clipFrame.unite(isolatedChild->relativeFrame());
                    allRects.appendVector(collectPaintedLineRects(isolatedChild.get(), paintedText));
                } else
                    self(isolatedChild.get(), self);
            }
        };
        collectFromDescendants(*this, collectFromDescendants);

        auto path = buildPathFromLineRects(WTF::move(allRects), clipFrame, fontOrientation());
        if (!path.isEmpty())
            return path;
    }

    // Links: build a shrink-wrapped path from descendant text if the link
    // contains only simple inline text spanning 2-3 lines.
    if (isLink())
        return elementPathForLink(*this, tree(), paintedText);

    // For other path types (border-radius, SVG, etc.), read from the geometry
    // manager's cache.
    if (RefPtr geometryManager = tree().geometryManager()) {
        auto cachedPath = geometryManager->cachedPathForID(objectID());
        return cachedPath.value_or(Path { });
    }
    return { };
}

static Color getColor(const AXPropertyValueVariant& value)
{
    return WTF::switchOn(value,
        [] (const Color& typedValue) -> Color { return typedValue; },
        [] (auto&) { return Color(); }
    );
}

#ifndef NDEBUG
Color AXIsolatedObject::cachedTextColor() const
{
    size_t index = indexOfProperty(AXProperty::TextColor);
    return index == notFound ? Color() : getColor(m_properties[index].second);
}
#endif

static RetainPtr<CTFontRef> getFont(const AXPropertyValueVariant& value)
{
    return WTF::switchOn(value,
        [] (const RetainPtr<CTFontRef>& typedValue) -> RetainPtr<CTFontRef> { return typedValue; },
        [] (auto&) { return RetainPtr<CTFontRef>(); }
    );
}

#ifndef NDEBUG
#if PLATFORM(COCOA)
RetainPtr<CTFontRef> AXIsolatedObject::cachedFont() const
{
    size_t index = indexOfProperty(AXProperty::Font);
    return index == notFound ? RetainPtr<CTFontRef>() : getFont(m_properties[index].second);
}
#endif // PLATFORM(COCOA)
#endif // NDEBUG

Color AXIsolatedObject::colorAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound) {
        if (property == AXProperty::TextColor) {
            if (RefPtr parent = parentObject())
                return parent->textColor();
        }
        return Accessibility::defaultColor();
    }

#ifndef NDEBUG
    if (RefPtr parent = parentObject(); parent && property == AXProperty::TextColor)
        AX_ASSERT(parent->cachedTextColor() != getColor(m_properties[index].second));
#endif

    return getColor(m_properties[index].second);
}

RetainPtr<CTFontRef> AXIsolatedObject::fontAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound) {
        RefPtr parent = parentObject();
        return parent ? parent->font() : nullptr;
    }

#ifndef NDEBUG
    if (RefPtr parent = parentObject(); parent && property == AXProperty::Font)
        AX_ASSERT(parent->cachedFont() != getFont(m_properties[index].second));
#endif

    return getFont(m_properties[index].second);
}

float AXIsolatedObject::floatAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return 0.0f;

    return WTF::switchOn(m_properties[index].second,
        [] (const float& typedValue) -> float { return typedValue; },
        [] (auto&) { return 0.0f; }
    );
}

double AXIsolatedObject::doubleAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return 0.0;

    return WTF::switchOn(m_properties[index].second,
        [] (const double& typedValue) -> double { return typedValue; },
        [] (auto&) { return 0.0; }
    );
}

unsigned AXIsolatedObject::unsignedAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return 0u;

    return WTF::switchOn(m_properties[index].second,
        [] (const unsigned& typedValue) -> unsigned { return typedValue; },
        [] (auto&) { return 0u; }
    );
}

bool AXIsolatedObject::boolAttributeValue(AXProperty property) const
{
    uint16_t propertyIndex = static_cast<uint16_t>(property);
    if (propertyIndex > lastPropertyFlagIndex) {
        size_t index = indexOfProperty(property);
        if (index == notFound)
            return false;
        return WTF::switchOn(m_properties[index].second,
            [] (const bool& typedValue) { return typedValue; },
            [] (auto&) { return false; }
        );
    }
    return hasPropertyFlag(static_cast<AXPropertyFlag>(1 << propertyIndex));
}

String AXIsolatedObject::stringAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return emptyString();

    return WTF::switchOn(m_properties[index].second,
        [] (const String& typedValue) { return typedValue; },
        [] (auto&) { return emptyString(); }
    );
}

String AXIsolatedObject::stringAttributeValueNullIfMissing(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return nullString();

    return WTF::switchOn(m_properties[index].second,
        [] (const String& typedValue) { return typedValue; },
        [] (auto&) { return nullString(); }
    );
}

int AXIsolatedObject::intAttributeValue(AXProperty property) const
{
    size_t index = indexOfProperty(property);
    if (index == notFound)
        return 0;

    return WTF::switchOn(m_properties[index].second,
        [] (const int& typedValue) { return typedValue; },
        [] (auto&) { return 0; }
    );
}

const AXTextRuns* AXIsolatedObject::textRuns() const
{
    size_t index = indexOfProperty(AXProperty::TextRuns);
    if (index == notFound)
        return nullptr;

    return WTF::switchOn(m_properties[index].second,
        [] (const std::unique_ptr<AXTextRuns>& typedValue) -> const AXTextRuns* { return typedValue.get(); },
        [] (auto&) -> const AXTextRuns* { return nullptr; }
    );
}

template<typename T>
T AXIsolatedObject::getOrRetrievePropertyValue(AXProperty property)
{
    if (std::optional value = optionalAttributeValue<T>(property))
        return *value;

    AXPropertyValueVariant value = nullptr;
    Accessibility::performFunctionOnMainThreadAndWait([&property, &value, context = mainThreadContext()] () {
        RefPtr axObject = context.axObjectOnMainThread();
        if (!axObject)
            return;

        switch (property) {
        case AXProperty::InnerHTML:
            value = axObject->innerHTML().isolatedCopy();
            break;
        case AXProperty::OuterHTML:
            value = axObject->outerHTML().isolatedCopy();
            break;
        default:
            break;
        }
    });
    // Cache value so that there is no need to access the main thread in subsequent calls.
    setPropertyInVector(property, WTF::move(value));

    return propertyValue<T>(property);
}

void AXIsolatedObject::fillChildrenVectorForProperty(AXProperty property, AccessibilityChildrenVector& children) const
{
    Vector<AXID> childIDs = vectorAttributeValue<AXID>(property);
    children.reserveCapacity(childIDs.size());
    for (const auto& childID : childIDs) {
        if (RefPtr object = tree().objectForID(childID))
            children.append(object.releaseNonNull());
    }
}

void AXIsolatedObject::updateBackingStore()
{
    AX_ASSERT(!isMainThread());

    if (AXIsolatedTree::anyTreeNeedsTearDown()) [[unlikely]] {
        AXTreeStore<AXIsolatedTree>::applyPendingChangesForAllIsolatedTrees();
        // Lean on the assumption that applyPendingChangesForAllIsolatedTrees() clears this
        // flag (as it should) so we aren't constantly re-entering this branch for no reason.
        AX_ASSERT(!AXIsolatedTree::anyTreeNeedsTearDown());
        return;
    }

    if (RefPtr tree = this->tree())
        tree->applyPendingChanges();
}

std::optional<SimpleRange> AXIsolatedObject::rangeForCharacterRange(const CharacterRange& axRange) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->rangeForCharacterRange(axRange) : std::nullopt;
}

#if PLATFORM(MAC)
AXTextMarkerRange AXIsolatedObject::selectedTextMarkerRange() const
{
    return tree().selectedTextMarkerRange();
}
#endif // PLATFORM(MAC)

IntRect AXIsolatedObject::boundsForRange(const SimpleRange& range) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->boundsForRange(range) : IntRect();
}

VisiblePosition AXIsolatedObject::visiblePositionForPoint(const IntPoint& point) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionForPoint(point) : VisiblePosition();
}

VisiblePosition AXIsolatedObject::nextLineEndPosition(const VisiblePosition&) const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

VisiblePosition AXIsolatedObject::previousLineStartPosition(const VisiblePosition&) const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

VisiblePosition AXIsolatedObject::visiblePositionForIndex(int index) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionForIndex(index) : VisiblePosition();
}

int AXIsolatedObject::indexForVisiblePosition(const VisiblePosition&) const
{
    AX_ASSERT_NOT_REACHED();
    return 0;
}

Vector<SimpleRange> AXIsolatedObject::findTextRanges(const AccessibilitySearchTextCriteria& criteria) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&criteria, context = mainThreadContext()] () -> Vector<SimpleRange> {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->findTextRanges(criteria);
        return { };
    }, Accessibility::InteractiveTimeout, Vector<SimpleRange> { });
}

Vector<String> AXIsolatedObject::performTextOperation(const AccessibilityTextOperation& textOperation)
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&textOperation, context = mainThreadContext()] () -> Vector<String> {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->performTextOperation(textOperation);
        return Vector<String>();
    }, Accessibility::InteractiveTimeout, Vector<String> { });
}

String AXIsolatedObject::textUnderElement(TextUnderElementMode) const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

std::optional<SimpleRange> AXIsolatedObject::misspellingRange(const SimpleRange& range, AccessibilitySearchDirection direction) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->misspellingRange(range, direction) : std::nullopt;
}

LayoutRect AXIsolatedObject::elementRect() const
{
#if PLATFORM(MAC)
    // It is not expected for elementRect to be called directly or indirectly when serving a request for VoiceOver.
    // If this does happen, we should either see if AXIsolatedObject::relativeFrame can be used instead, or do the
    // work to cache the correct elementRect value.
    AX_ASSERT(_AXGetClientForCurrentRequestUntrusted() != kAXClientTypeVoiceOver);
#endif

    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> LayoutRect {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->elementRect();
        return { };
    }, Accessibility::BoundingBoxTimeout, relativeFrame());
}

IntPoint AXIsolatedObject::remoteFrameOffset() const
{
    RefPtr root = tree().rootNode();
    return root ? root->propertyValue<IntPoint>(AXProperty::RemoteFrameOffset) : IntPoint();
}

FloatPoint AXIsolatedObject::screenRelativePosition() const
{
#if !ENABLE(ACCESSIBILITY_LOCAL_FRAME)
    if (auto point = optionalAttributeValue<FloatPoint>(AXProperty::ScreenRelativePosition))
        return *point;
#endif
    return convertFrameToSpace(relativeFrame(), AccessibilityConversionSpace::Screen).location();
}

FloatRect AXIsolatedObject::screenRelativeRect() const
{
#if !ENABLE(ACCESSIBILITY_LOCAL_FRAME)
    if (auto point = optionalAttributeValue<FloatPoint>(AXProperty::ScreenRelativePosition))
        return { *point, size() };
#endif
    return convertFrameToSpace(relativeFrame(), AccessibilityConversionSpace::Screen);
}

#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)
IntPoint AXIsolatedObject::frameScreenPosition() const
{
    return tree().frameGeometry().screenPosition;
}

AffineTransform AXIsolatedObject::frameScreenTransform() const
{
    return tree().frameGeometry().screenTransform;
}
#endif

static Seconds NODELETE relativeFrameTimeout(bool shouldServeInitialFrame)
{
    // If the request demands that we don't serve the (probably somewhat inaccurate) initial frame, use a much
    // longer timeout (5 seconds). In practice, at the time of writing, this should only be true for tests.
    // For requests that prioritize responsiveness (shouldServeInitialFrame), use a short timeout.
    if (shouldServeInitialFrame) [[likely]] {
        // Note this is shorter than the standard Accessibility::BoundingBoxTimeout constant. This is because
        // we should only hit this codepath for non-layer-based SVG objects, which are rare, and not something
        // we want to hold requests on with a longer timeout when the main-thread is busy. In normal, "ambient"
        // conditions on a webpage, 8ms should be more than enough. I measured this on gmail.com and the mean
        // duration of 320 main-thread trips was 1.8ms.
        return 8_ms;
    }
    return 5_s;
}

FloatRect AXIsolatedObject::relativeFrame() const
{
    FloatRect relativeFrame;

    if (std::optional cachedRelativeFrame = this->cachedRelativeFrame()) {
        // We should not have cached a relative frame for elements that get their geometry from their children.
        AX_ASSERT(!m_getsGeometryFromChildren);
        relativeFrame = *cachedRelativeFrame;

        if (isStaticText()) {
            if (std::optional stitchGroup = stitchGroupIfRepresentative()) {
                // |this| is a stitching of multiple objects, so we need to combine all of their frames.

                RefPtr tree = this->tree();
                for (AXID axID : stitchGroup->members()) {
                    if (axID == objectID())
                        continue;

                    if (RefPtr object = tree->objectForID(axID)) {
                        if (std::optional otherCachedFrame = object->cachedRelativeFrame()) {
                            if (object->isAXHidden())
                                continue;
                            relativeFrame = unionRect(relativeFrame, *otherCachedFrame);
                        }
                    }
                }
            }
        }
    } else if (m_getsGeometryFromChildren) {
        auto frame = enclosingIntRect(relativeFrameFromChildren());
        if (!frame.isEmpty())
            relativeFrame = frame;
        // Either we had no children, or our children had empty frames. The right thing to do would be to return
        // a rect at the position of the nearest render tree ancestor with some made-up size (AccessibilityNodeObject::boundingBoxRect does this).
        // However, we don't have access to the render tree in this context (only the AX isolated tree, which is too sparse for this purpose), so
        // until we cache the necessary information let's go to the main-thread.
    } else if (role() == AccessibilityRole::Column || role() == AccessibilityRole::TableHeaderContainer)
        relativeFrame = exposedTableAncestor() ? relativeFrameFromChildren() : FloatRect();
    else if (isExposableTable()) {
        // If we are an exposable-to-accessibility table, we must have at least one valid row, so see if
        // our row(s) have cached geometry we can use. For tables, this will probably be more accurate
        // than the ancestor bounding-box fallback below.
        for (const auto& child : const_cast<AXIsolatedObject*>(this)->unignoredChildren()) {
            if (std::optional cachedFrame = downcast<AXIsolatedObject>(child)->cachedRelativeFrame())
                relativeFrame.unite(*cachedFrame);
        }
    }

    // Mock objects and SVG objects need use the main thread since they do not have render nodes and are not painted with layers, respectively.
    // FIXME: Remove isNonLayerSVGObject when LBSE is enabled & SVG frames are cached.
    bool shouldServeInitialFrame = AXObjectCache::shouldServeInitialCachedFrame();
    if (!shouldServeInitialFrame || isNonLayerSVGObject()) {
        auto mainThreadValue = Accessibility::retrieveValueFromMainThreadWithTimeout([context = mainThreadContext()] () -> FloatRect {
            if (RefPtr axObject = context.axObjectOnMainThread())
                return axObject->relativeFrame();
            return { };
        }, relativeFrameTimeout(shouldServeInitialFrame));

        if (std::optional rect = mainThreadValue.value)
            return WTF::move(*rect);
    }

    // Having an empty relative frame at this point means a frame hasn't been cached yet.
    if (relativeFrame.isEmpty()) {
        std::optional<IntRect> rectFromLabels;
        if (isControl()) {
            // For controls, we can try to use the frame of any associated labels.
            // Prefer ARIA labels first, fall back to native labels if none provide geometry.
            auto uniteLabelsIntoRect = [&rectFromLabels](const AccessibilityChildrenVector& labels) {
                for (const auto& label : labels) {
                    std::optional frame = downcast<AXIsolatedObject>(label)->cachedRelativeFrame();
                    if (!frame)
                        continue;
                    if (!rectFromLabels)
                        rectFromLabels = *frame;
                    else if (rectFromLabels->intersects(*frame))
                        rectFromLabels->unite(*frame);
                }
            };
            uniteLabelsIntoRect(labeledByObjects());
            if (!rectFromLabels)
                uniteLabelsIntoRect(nativeLabeledByObjects());
        }

        if (rectFromLabels && !rectFromLabels->isEmpty())
            relativeFrame = *rectFromLabels;
        else {
            // InitialLocalRect stores the correct size, but not position, of the element before it is painted.
            // We find the position of the nearest painted ancestor to use as the position until the object's frame
            // is cached during painting.
            relativeFrame = rectAttributeValue<FloatRect>(AXProperty::InitialLocalRect);

            std::optional<IntRect> ancestorRelativeFrame;
            Accessibility::findAncestor<AXIsolatedObject>(*this, false, [&] (const auto& object) {
                ancestorRelativeFrame = object.cachedRelativeFrame();
                return ancestorRelativeFrame;
            });

            if (ancestorRelativeFrame) {
                if (relativeFrame.isEmpty() && !isIgnored()) {
                    // It's possible our initial frame rect was empty too. For things exposed in the accessibility
                    // tree (i.e. they aren't ignored), it's important to expose a non-empty frame, as some ATs
                    // like VoiceOver will ignore elements with empty frames.
                    relativeFrame = *ancestorRelativeFrame;
                } else
                    relativeFrame.setLocation(ancestorRelativeFrame->location());
            }
        }

        // If an assistive technology is requesting the frame for something,
        // chances are it's on-screen, so clamp to 0,0 if necessary.
        if (relativeFrame.x() < 0)
            relativeFrame.setX(0);
        if (relativeFrame.y() < 0)
            relativeFrame.setY(0);
    }
    relativeFrame.moveBy({ remoteFrameOffset() });
    return relativeFrame;
}

FloatRect AXIsolatedObject::relativeFrameFromChildren() const
{
    FloatRect rect;
    for (const auto& child : const_cast<AXIsolatedObject*>(this)->unignoredChildren())
        rect.unite(child->relativeFrame());
    return rect;
}

FloatRect AXIsolatedObject::convertFrameToSpace(const FloatRect& rect, AccessibilityConversionSpace space) const
{
    if (space == AccessibilityConversionSpace::Screen) {
#if !PLATFORM(MAC)
        // This function assumes we are in macOS coordinate space (bottom-left origin).
        // If this code ever runs on iOS, it will be wrong and need to be fixed.
        AX_ASSERT_NOT_REACHED();
#endif
#if ENABLE(ACCESSIBILITY_LOCAL_FRAME)
        auto screenPosition = frameScreenPosition();
        auto screenTransform = frameScreenTransform();
        auto scaledRect = screenTransform.mapRect(rect);

        // screenPosition tracks the document origin, which moves with scroll.
        // The viewport is fixed on screen, so subtract the scroll and content
        // inset offsets that contentsToView baked into screenPosition.
        if (isScrollArea() && !parent()) {
            auto viewOriginScrollPosition = screenTransform.mapPoint(FloatPoint(tree().frameViewOriginScrollPosition()));
            screenPosition.move(-roundToInt(viewOriginScrollPosition.x()), -roundToInt(viewOriginScrollPosition.y()));
        }

        // Screen coordinates use bottom-left origin (on macOS).
        FloatPoint position = {
            screenPosition.x() + scaledRect.x(),
            screenPosition.y() - scaledRect.maxY()
        };
        return { position, scaledRect.size() };
#else
        if (RefPtr rootNode = tree().rootNode()) {
            auto rootPoint = rootNode->propertyValue<FloatPoint>(AXProperty::ScreenRelativePosition);
            auto rootRelativeFrame = rootNode->relativeFrame();
            // Relative frames are top-left origin, but screen relative positions are bottom-left origin.
            FloatPoint position = { rootPoint.x() + rect.x(), rootPoint.y() + (rootRelativeFrame.maxY() - rect.maxY()) };
            return { WTF::move(position), rect.size() };
        }
#endif // ENABLE(ACCESSIBILITY_LOCAL_FRAME)
    }

    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&rect, &space, context = mainThreadContext()] () -> FloatRect {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->convertFrameToSpace(rect, space);
        return { };
    }, Accessibility::BoundingBoxTimeout, rect);
}

bool AXIsolatedObject::replaceTextInRange(const String& replacementText, const CharacterRange& textRange)
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([text = replacementText.isolatedCopy(), &textRange, context = mainThreadContext()] () -> bool {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->replaceTextInRange(text, textRange);
        return false;
    }, Accessibility::InteractiveTimeout, false);
}

bool AXIsolatedObject::insertText(const String& text)
{
    AXTRACE(makeString("AXIsolatedObject::insertText text = "_s, text));

    // Dispatch to the main thread without waiting since AXObject::insertText waits for the UI process that can be waiting resulting in a deadlock. That is the case when running LayoutTests.
    // The return value of insertText is not used, so not waiting does not result in any loss of functionality.
    performFunctionOnMainThread([text = text.isolatedCopy()] (auto* axObject) {
        axObject->insertText(text);
    });
    return true;
}

bool AXIsolatedObject::press()
{
    AX_ASSERT(isMainThread());

    if (RefPtr object = associatedAXObject())
        return object->press();
    return false;
}

bool AXIsolatedObject::syncPress()
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> bool {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->press();
        return false;
    }, Accessibility::InteractiveTimeout, false);
}

void AXIsolatedObject::increment()
{
    performFunctionOnMainThread([] (auto* axObject) {
        axObject->increment();
    });
}

void AXIsolatedObject::decrement()
{
    performFunctionOnMainThread([] (auto* axObject) {
        axObject->decrement();
    });
}

void AXIsolatedObject::syncIncrement()
{
    Accessibility::performFunctionOnMainThreadAndWaitWithTimeout([context = mainThreadContext()] {
        if (RefPtr axObject = context.axObjectOnMainThread())
            axObject->increment();
    }, Accessibility::InteractiveTimeout);
}

void AXIsolatedObject::syncDecrement()
{
    Accessibility::performFunctionOnMainThreadAndWaitWithTimeout([context = mainThreadContext()] {
        if (RefPtr axObject = context.axObjectOnMainThread())
            axObject->decrement();
    }, Accessibility::InteractiveTimeout);
}

bool AXIsolatedObject::isAccessibilityNodeObject() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isAccessibilityRenderObject() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isNativeTextControl() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

int AXIsolatedObject::insertionPointLineNumber() const
{
    AX_ASSERT(!isMainThread());

    if (!boolAttributeValue(AXProperty::CanBeMultilineTextField))
        return 0;

    auto selectedMarkerRange = selectedTextMarkerRange();
    if (selectedMarkerRange.start().isNull() || !selectedMarkerRange.isCollapsed()) {
        // If the selection is not collapsed, we don't know whether the insertion point is at the start or the end, so return -1.
        return -1;
    }

    if (isTextControl()) {
        RefPtr selectionObject = selectedMarkerRange.start().isolatedObject();
        if (selectionObject && isAncestorOfObject(*selectionObject))
            return selectedMarkerRange.start().lineIndex();
    }
    return -1;
}

String AXIsolatedObject::identifierAttribute() const
{
    if (AXIsolatedTree::shouldCacheIdentifierAttribute())
        return stringAttributeValue(AXProperty::IdentifierAttribute);

    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> String {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->identifierAttribute().isolatedCopy();
        return { };
    }, Accessibility::GeneralPropertyTimeout, emptyString());
}

CharacterRange AXIsolatedObject::doAXRangeForLine(unsigned lineIndex) const
{
    AX_ASSERT(!isMainThread());
    return AXTextMarker { *this, 0 }.characterRangeForLine(lineIndex);
}

String AXIsolatedObject::doAXStringForRange(const CharacterRange& range) const
{
    AX_ASSERT(!isMainThread());
    return textMarkerRange().toString().substring(range.location, range.length);
}

CharacterRange AXIsolatedObject::characterRangeForPoint(const IntPoint& point) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&point, context = mainThreadContext()] () -> CharacterRange {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->characterRangeForPoint(point);
        return { };
    }, Accessibility::GeneralPropertyTimeout, CharacterRange { });
}

CharacterRange AXIsolatedObject::doAXRangeForIndex(unsigned index) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&index, context = mainThreadContext()] () -> CharacterRange {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->doAXRangeForIndex(index);
        return { };
    }, Accessibility::GeneralPropertyTimeout, CharacterRange { });
}

CharacterRange AXIsolatedObject::doAXStyleRangeForIndex(unsigned index) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&index, context = mainThreadContext()] () -> CharacterRange {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->doAXStyleRangeForIndex(index);
        return { };
    }, Accessibility::GeneralPropertyTimeout, CharacterRange { });
}

IntRect AXIsolatedObject::doAXBoundsForRange(const CharacterRange& axRange) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&axRange, context = mainThreadContext()] () -> IntRect {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->doAXBoundsForRange(axRange);
        return { };
    }, Accessibility::GeneralPropertyTimeout, IntRect { });
}

IntRect AXIsolatedObject::doAXBoundsForRangeUsingCharacterOffset(const CharacterRange& axRange) const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([&axRange, context = mainThreadContext()] () -> IntRect {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->doAXBoundsForRangeUsingCharacterOffset(axRange);
        return { };
    }, Accessibility::GeneralPropertyTimeout, IntRect { });
}


unsigned AXIsolatedObject::doAXLineForIndex(unsigned index)
{
    AX_ASSERT(!isMainThread());
    return AXTextMarker { *this, 0 }.lineNumberForIndex(index);
}

VisibleSelection AXIsolatedObject::selection() const
{
    AX_ASSERT(isMainThread());

    RefPtr object = associatedAXObject();
    return object ? object->selection() : VisibleSelection();
}

void AXIsolatedObject::setSelectedVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const
{
    AX_ASSERT(isMainThread());

    if (RefPtr object = associatedAXObject())
        object->setSelectedVisiblePositionRange(visiblePositionRange);
}

#if ENABLE(MODEL_ELEMENT_ACCESSIBILITY)

ModelPlayerAccessibilityChildren AXIsolatedObject::modelElementChildren()
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] -> ModelPlayerAccessibilityChildren {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->modelElementChildren();
        return { };
    }, Accessibility::GeneralPropertyTimeout, ModelPlayerAccessibilityChildren { });
}

#endif

std::optional<SimpleRange> AXIsolatedObject::simpleRange() const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->simpleRange() : std::nullopt;
}

VisiblePositionRange AXIsolatedObject::visiblePositionRange() const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionRange() : VisiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::visiblePositionRangeForLine(unsigned index) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionRangeForLine(index) : VisiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::visiblePositionRangeForUnorderedPositions(const VisiblePosition& position1, const VisiblePosition& position2) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionRangeForUnorderedPositions(position1, position2) : visiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::leftLineVisiblePositionRange(const VisiblePosition& position) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->leftLineVisiblePositionRange(position) : VisiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::rightLineVisiblePositionRange(const VisiblePosition& position) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->rightLineVisiblePositionRange(position) : VisiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::styleRangeForPosition(const VisiblePosition& position) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->styleRangeForPosition(position) : VisiblePositionRange();
}

VisiblePositionRange AXIsolatedObject::lineRangeForPosition(const VisiblePosition& position) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->lineRangeForPosition(position) : VisiblePositionRange();
}

VisiblePosition AXIsolatedObject::visiblePositionForIndex(unsigned index, bool lastIndexOK) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->visiblePositionForIndex(index, lastIndexOK) : VisiblePosition();
}

int AXIsolatedObject::lineForPosition(const VisiblePosition& position) const
{
    AX_ASSERT(isMainThread());
    RefPtr axObject = associatedAXObject();
    return axObject ? axObject->lineForPosition(position) : -1;
}

bool AXIsolatedObject::isMockObject() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isNonNativeTextControl() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isOnScreen() const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> bool {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->isOnScreen();
        return false;
    }, Accessibility::VisibilityCheckTimeout, false);
}

bool AXIsolatedObject::isOffScreen() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isPressed() const
{
#if PLATFORM(MAC)
    AX_ASSERT_NOT_REACHED();
#endif
    return boolAttributeValue(AXProperty::IsPressed);
}

bool AXIsolatedObject::isFocused() const
{
    if (role() == AccessibilityRole::WebArea) {
        // Matching AccessibilityNodeObject::isFocused, the web area is focused when
        // the corresponding document's frame selection is focused and active.
        return boolAttributeValue(AXProperty::IsFocusedWebArea);
    }
    return tree().focusedNodeID() == objectID();
}

bool AXIsolatedObject::isSelectedOptionActive() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

Vector<AXTextMarkerRange> AXIsolatedObject::misspellingRanges() const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> Vector<AXTextMarkerRange> {
        if (RefPtr axObject = context.axObjectOnMainThread())
            return axObject->misspellingRanges();
        return { };
    }, Accessibility::SpellCheckTimeout, Vector<AXTextMarkerRange> { });
}

bool AXIsolatedObject::hasRowGroupTag() const
{
    auto elementName = this->elementName();
    return elementName == ElementName::HTML_thead || elementName == ElementName::HTML_tbody || elementName == ElementName::HTML_tfoot;
}

bool AXIsolatedObject::hasSameFont(AXCoreObject& otherObject)
{
    AX_ASSERT(!isMainThread());

    // Having a font only really makes sense for text, so if this or otherObject isn't text, find the first text descendant to compare.
    RefPtr thisText = selfOrFirstTextDescendant();
    if (!thisText) {
        // We can't make a meaningful comparison unless we have two objects to compare, so return false.
        return false;
    }

    RefPtr otherText = otherObject.selfOrFirstTextDescendant();
    if (!otherText)
        return false;

    return thisText->font() == otherText->font();
}

bool AXIsolatedObject::hasSameFontColor(AXCoreObject& otherObject)
{
    AX_ASSERT(!isMainThread());

    RefPtr thisText = downcast<AXIsolatedObject>(selfOrFirstTextDescendant());
    if (!thisText)
        return false;

    RefPtr otherText = downcast<AXIsolatedObject>(otherObject.selfOrFirstTextDescendant());
    if (!otherText)
        return false;

    return thisText->colorAttributeValue(AXProperty::TextColor) == otherText->colorAttributeValue(AXProperty::TextColor);
}

bool AXIsolatedObject::hasSameStyle(AXCoreObject& otherObject)
{
    AX_ASSERT(!isMainThread());

    RefPtr thisText = selfOrFirstTextDescendant();
    if (!thisText)
        return false;
    RefPtr otherText = otherObject.selfOrFirstTextDescendant();
    if (!otherText)
        return false;

    return thisText->stylesForAttributedString() == otherText->stylesForAttributedString();
}

AXTextMarkerRange AXIsolatedObject::textInputMarkedTextMarkerRange() const
{
    size_t index = indexOfProperty(AXProperty::TextInputMarkedTextMarkerRange);
    if (index == notFound)
        return nullptr;

    return WTF::switchOn(m_properties[index].second,
        [&] (const std::unique_ptr<AXIDAndCharacterRange>& typedValue) -> AXTextMarkerRange {
            auto start = static_cast<unsigned>(typedValue->second.location);
            auto end = start + static_cast<unsigned>(typedValue->second.length);
            return { tree().treeID(), typedValue->first, start, end };
        },
        [] (auto&) -> AXTextMarkerRange { return { }; }
    );
}

// The attribute this value is exposed as is not used by VoiceOver or any other AX client on macOS, so we intentionally don't cache it.
// Re-visit if ITM expands to more platforms, or if AX clients need to start using this.
String AXIsolatedObject::linkRelValue() const
{
    return Accessibility::retrieveValueFromMainThreadWithTimeoutAndDefault([context = mainThreadContext()] () -> String {
        if (RefPtr object = context.axObjectOnMainThread())
            return object->linkRelValue().isolatedCopy();
        return { };
    }, Accessibility::GeneralPropertyTimeout, String { });
}

#if ENABLE_ACCESSIBILITY_LOCAL_FRAME

AXIsolatedObject* AXIsolatedObject::crossFrameParentObject() const
{
    if (role() != AccessibilityRole::ScrollArea)
        return nullptr;

    auto parentFrameID = optionalAttributeValue<FrameIdentifier>(AXProperty::CrossFrameParentFrameID);
    if (!parentFrameID)
        return nullptr;

    auto optionalParentObjectID = optionalAttributeValue<Markable<AXID>>(AXProperty::CrossFrameParentAXID);

    // TODO: add helpers to retrieve an AXID directly to clean this up.
    if (!optionalParentObjectID)
        return nullptr;

    auto markableParentObjectID = *optionalParentObjectID;
    if (!markableParentObjectID)
        return nullptr;

    auto parentObjectID = *markableParentObjectID;

    RefPtr parentTree = AXIsolatedTree::treeForFrameID(*parentFrameID);
    if (!parentTree)
        return nullptr;

    return parentTree->objectForID(parentObjectID);
}

AXIsolatedObject* AXIsolatedObject::crossFrameChildObject() const
{
    if (role() != AccessibilityRole::LocalFrame)
        return nullptr;

    auto frameID = optionalAttributeValue<FrameIdentifier>(AXProperty::CrossFrameChildFrameID);
    if (RefPtr childTree = frameID ? AXIsolatedTree::treeForFrameID(*frameID) : nullptr) {
        childTree->applyPendingChanges();
        return childTree->rootNode();
    }
    return nullptr;
}

bool AXIsolatedObject::isFrameGeometryInitialized() const
{
    return tree().isFrameGeometryInitialized();
}

#endif // ENABLE_ACCESSIBILITY_LOCAL_FRAME

AXIsolatedObject* AXIsolatedObject::focusedUIElementInAnyLocalFrame() const
{
#if ENABLE_ACCESSIBILITY_LOCAL_FRAME
    // Each frame's isolated tree tracks focus independently in its own focusedNodeID. Follow the
    // focus down through local-frame boundaries: when a tree's focused node proxies a child local
    // frame (an AXLocalFrame, for which crossFrameChildObject() is non-null), the real focus lives
    // inside that child frame, so descend into the child tree's focused node. Returning the deepest
    // focused node yields the actual focused element (e.g. a text field inside an iframe). This
    // mirrors the cross-frame walk in AccessibilityObject::focusedUIElementInAnyLocalFrame().
    RefPtr<AXIsolatedTree> focusTree = &tree();
    RefPtr focus = focusTree->focusedNode();
    while (focus) {
        RefPtr crossFrameChild = focus->crossFrameChildObject();
        if (!crossFrameChild)
            break;
        RefPtr childTree = &crossFrameChild->tree();
        RefPtr childFocus = childTree->focusedNode();
        if (!childFocus)
            break;
        focusTree = WTF::move(childTree);
        focus = WTF::move(childFocus);
    }
    // focusTree now holds the deepest focused node; return it via objectForID (a raw, non-lifetime-bound
    // accessor, as parentObject() uses) so we neither leak an uncounted raw pointer nor need unsafeGet().
    return focusTree->objectForID(focusTree->focusedNodeID());
#else
    return tree().objectForID(tree().focusedNodeID());
#endif
}

Element* AXIsolatedObject::element() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

Node* AXIsolatedObject::node() const
{
    return nullptr;
}

RenderObject* AXIsolatedObject::renderer() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

bool AXIsolatedObject::supportsHasPopup() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::supportsChecked() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isModalNode() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::isTableCell() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

AXCoreObject* AXIsolatedObject::parentTableIfTableCell() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

AXCoreObject* AXIsolatedObject::parentTable() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

bool AXIsolatedObject::isTableRow() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

AXCoreObject* AXIsolatedObject::parentTableIfExposedTableRow() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

bool AXIsolatedObject::isDescendantOfRole(AccessibilityRole) const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

bool AXIsolatedObject::inheritsPresentationalRole() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

void AXIsolatedObject::setAccessibleName(const AtomString&)
{
    AX_ASSERT_NOT_REACHED();
}

String AXIsolatedObject::textContentPrefixFromListMarker() const
{
    return propertyValue<String>(AXProperty::TextContentPrefixFromListMarker);
}

String AXIsolatedObject::stringValue() const
{
    size_t index = indexOfProperty(AXProperty::StringValue);
    if (index == notFound) {
        if (hasStitchableRole()) {
            std::optional stitchGroup = stitchGroupIfRepresentative();
            if (!stitchGroup)
                return textMarkerRange().toString(IncludeListMarkerText::No);

            // |this| is the sum of several stitched text-like objects. Our string value should
            // include all of them.
            //
            // We can compute the stringValue of rendered text using AXProperty::TextRuns.
            // See AccessibilityObject::shouldCacheStringValue.
            RefPtr tree = std::get<RefPtr<AXIsolatedTree>>(axTreeForID(treeID()));
            if (!tree)
                return textMarkerRange().toString(IncludeListMarkerText::No);

            StringBuilder builder;
            for (AXID axID : stitchGroup->members()) {
                RefPtr object = tree->objectForID(axID);
                if (!object || object->isAXHidden())
                    continue;

                if (const auto* runs = object->textRuns())
                    builder.append(runs->toString());
                else
                    builder.append(object->listMarkerText());
            }

            return builder.toString();
        }
        return emptyString();
    }

    return WTF::switchOn(m_properties[index].second,
        [] (const String& typedValue) { return typedValue; },
        [] (auto&) { return emptyString(); }
    );
}

String AXIsolatedObject::text() const
{
    AX_ASSERT_NOT_REACHED();
    return String();
}

#if !PLATFORM(COCOA)
unsigned AXIsolatedObject::textLength() const
{
    AX_ASSERT_NOT_REACHED();
    return 0;
}
#endif

AXObjectCache* AXIsolatedObject::axObjectCache() const
{
    AX_ASSERT(isMainThread());
    return tree().axObjectCache();
}

Element* AXIsolatedObject::actionElement() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

Widget* AXIsolatedObject::widget() const
{
    RefPtr object = associatedAXObject();
    return object ? object->widget() : nullptr;
}

PlatformWidget AXIsolatedObject::platformWidget() const
{
#if PLATFORM(COCOA)
    return propertyValue<RetainPtr<NSView>>(AXProperty::PlatformWidget).unsafeGet();
#else
    return m_platformWidget;
#endif
}

Widget* AXIsolatedObject::widgetForAttachmentView() const
{
    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

Page* AXIsolatedObject::page() const
{
    AX_ASSERT(isMainThread());

    if (RefPtr axObject = associatedAXObject())
        return axObject->page();

    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

Document* AXIsolatedObject::document() const
{
    AX_ASSERT(isMainThread());

    if (RefPtr axObject = associatedAXObject())
        return axObject->document();

    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

LocalFrameView* AXIsolatedObject::documentFrameView() const
{
    AX_ASSERT(isMainThread());

    if (RefPtr axObject = associatedAXObject())
        return axObject->documentFrameView();

    AX_ASSERT_NOT_REACHED();
    return nullptr;
}

AXCoreObject::AccessibilityChildrenVector AXIsolatedObject::relatedObjects(AXRelation relation) const
{
    if (auto relatedObjectIDs = tree().relatedObjectIDsFor(*this, relation))
        return tree().objectsForIDs(*relatedObjectIDs);
    return { };
}

String AXIsolatedObject::innerHTML() const
{
    return const_cast<AXIsolatedObject*>(this)->getOrRetrievePropertyValue<String>(AXProperty::InnerHTML);
}

String AXIsolatedObject::outerHTML() const
{
    return const_cast<AXIsolatedObject*>(this)->getOrRetrievePropertyValue<String>(AXProperty::OuterHTML);
}

AXCoreObject::AccessibilityChildrenVector AXIsolatedObject::rowHeaders()
{
    AccessibilityChildrenVector headers;
    if (isTable()) {
        auto rowsCopy = rows();
        for (const auto& row : rowsCopy) {
            if (RefPtr header = row->rowHeader())
                headers.append(header.releaseNonNull());
        }
    } else if (isExposedTableCell()) {
        RefPtr parent = exposedTableAncestor();
        if (!parent)
            return { };

        auto rowRange = rowIndexRange();
        auto colRange = columnIndexRange();
        for (unsigned column = 0; column < colRange.first; column++) {
            RefPtr tableCell = parent->cellForColumnAndRow(column, rowRange.first);
            if (!tableCell || tableCell == this || headers.contains(Ref { *tableCell }))
                continue;

            if (tableCell->cellScope() == "rowgroup"_s && isTableCellInSameRowGroup(*tableCell))
                headers.append(tableCell.releaseNonNull());
            else if (tableCell->isRowHeader())
                headers.append(tableCell.releaseNonNull());
        }
    }
    return headers;
}

AXIsolatedObject* AXIsolatedObject::tableHeaderContainer()
{
    for (const auto& child : unignoredChildren()) {
        if (child->role() == AccessibilityRole::TableHeaderContainer)
            return downcast<AXIsolatedObject>(child.ptr());
    }
    return nullptr;
}

#if !PLATFORM(MAC)
IntPoint AXIsolatedObject::clickPoint()
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

Vector<String> AXIsolatedObject::determineDropEffects() const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

bool AXIsolatedObject::pressedIsPresent() const
{
    AX_ASSERT_NOT_REACHED();
    return false;
}

int AXIsolatedObject::layoutCount() const
{
    AX_ASSERT_NOT_REACHED();
    return 0;
}

Vector<String> AXIsolatedObject::classList() const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}

String AXIsolatedObject::computedRoleString() const
{
    AX_ASSERT_NOT_REACHED();
    return { };
}
#endif

} // namespace WebCore

#endif // ENABLE((ACCESSIBILITY_ISOLATED_TREE)
