blob: 68edf681ec493fed6e16d8d978ba8fb455a56cea [file]
/*
* Copyright (C) 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"
#include "AXUtilities.h"
#include "AXLoggerBase.h"
#include "AXObjectCache.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSProperty.h"
#include "CSSValueList.h"
#include "DocumentView.h"
#include "ElementInlines.h"
#include "HTMLImageElement.h"
#include "HTMLMapElement.h"
#include "HTMLMediaElement.h"
#include "HTMLNames.h"
#include "Node.h"
#include "RenderImage.h"
#include "RenderStyleConstants.h"
#include "RenderTreeBuilder.h"
#include "StylePropertiesInlines.h"
#include <wtf/CheckedPtr.h>
#include <wtf/RefPtr.h>
namespace WebCore {
using namespace HTMLNames;
RefPtr<ContainerNode> composedParentIgnoringDocumentFragments(const Node& node)
{
RefPtr ancestor = node.parentInComposedTree();
while (is<DocumentFragment>(ancestor.get()))
ancestor = ancestor->parentInComposedTree();
return ancestor;
}
RefPtr<ContainerNode> composedParentIgnoringDocumentFragments(const Node* node)
{
return node ? composedParentIgnoringDocumentFragments(*node) : nullptr;
}
NodeName elementName(Node* node)
{
auto* element = dynamicDowncast<Element>(node);
return element ? element->elementName() : ElementName::Unknown;
}
NodeName elementName(Node& node)
{
auto* element = dynamicDowncast<Element>(node);
return element ? element->elementName() : ElementName::Unknown;
}
const RenderStyle* safeStyleFrom(Element& element)
{
// We cannot resolve style (as computedStyle() does) if we are downstream of an existing render tree
// update. Otherwise, a RELEASE_ASSERT preventing re-entrancy will be hit inside RenderTreeBuilder.
return RenderTreeBuilder::current() ? element.existingComputedStyle() : element.computedStyle();
}
bool hasAccNameAttribute(Element& element)
{
auto trimmed = [&] (const auto& attribute) {
const auto& value = element.attributeWithDefaultARIA(attribute);
if (value.isEmpty())
return emptyString();
auto copy = value.string();
return copy.trim(isASCIIWhitespace);
};
// Avoid calculating the actual description here (e.g. resolving aria-labelledby), as it's expensive.
// The spec is generally permissive in allowing user agents to not ensure complete validity of these attributes.
// For example, https://w3c.github.io/svg-aam/#include_elements:
// "It has an ‘aria-labelledby’ attribute or ‘aria-describedby’ attribute containing valid IDREF tokens. User agents MAY include elements with these attributes without checking for validity."
if (trimmed(aria_labelAttr).length() || trimmed(aria_labelledbyAttr).length() || trimmed(aria_labeledbyAttr).length() || trimmed(aria_descriptionAttr).length() || trimmed(aria_describedbyAttr).length())
return true;
return element.attributeWithoutSynchronization(titleAttr).length();
}
RenderImage* toSimpleImage(RenderObject& renderer)
{
CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer);
if (!renderImage)
return nullptr;
// Exclude ImageButtons because they are treated as buttons, not as images.
RefPtr node = renderer.node();
if (is<HTMLInputElement>(node))
return nullptr;
// ImageMaps are not simple images.
if (renderImage->imageMap())
return nullptr;
if (RefPtr imgElement = dynamicDowncast<HTMLImageElement>(node); imgElement && imgElement->hasAttributeWithoutSynchronization(usemapAttr))
return nullptr;
#if ENABLE(VIDEO)
// Exclude video and audio elements.
if (is<HTMLMediaElement>(node))
return nullptr;
#endif // ENABLE(VIDEO)
return renderImage.unsafeGet();
}
// FIXME: This probably belongs on Element.
bool hasRole(Element& element, StringView role)
{
auto roleValue = element.attributeWithDefaultARIA(roleAttr);
if (role.isNull())
return roleValue.isEmpty();
if (roleValue.isEmpty())
return false;
return SpaceSplitString::spaceSplitStringContainsValue(roleValue, role, SpaceSplitString::ShouldFoldCase::Yes);
}
bool hasAnyRole(Element& element, Vector<StringView>&& roles)
{
auto roleValue = element.attributeWithDefaultARIA(roleAttr);
if (roleValue.isEmpty())
return false;
for (const auto& role : roles) {
AX_ASSERT(!role.isEmpty());
if (SpaceSplitString::spaceSplitStringContainsValue(roleValue, role, SpaceSplitString::ShouldFoldCase::Yes))
return true;
}
return false;
}
bool hasAnyRole(Element* element, Vector<StringView>&& roles)
{
return element ? hasAnyRole(*element, WTF::move(roles)) : false;
}
bool hasTableRole(Element& element)
{
return hasAnyRole(element, { "grid"_s, "table"_s, "treegrid"_s });
}
bool hasCellARIARole(Element& element)
{
return hasAnyRole(element, { "gridcell"_s, "cell"_s, "columnheader"_s, "rowheader"_s });
}
bool hasPresentationRole(Element& element)
{
return hasAnyRole(element, { "presentation"_s, "none"_s });
}
bool isRowGroup(Element& element)
{
auto name = element.elementName();
return name == ElementName::HTML_thead || name == ElementName::HTML_tbody || name == ElementName::HTML_tfoot || hasRole(element, "rowgroup"_s);
}
bool isRowGroup(Node* node)
{
auto* element = dynamicDowncast<Element>(node);
return element && isRowGroup(*element);
}
void dumpAccessibilityTreeToStderr(Document& document)
{
if (CheckedPtr cache = document.existingAXObjectCache())
cache->treeData().dumpToStderr();
}
String roleToString(AccessibilityRole role)
{
switch (role) {
case AccessibilityRole::Application:
return "Application"_s;
case AccessibilityRole::ApplicationAlert:
return "ApplicationAlert"_s;
case AccessibilityRole::ApplicationAlertDialog:
return "ApplicationAlertDialog"_s;
case AccessibilityRole::ApplicationDialog:
return "ApplicationDialog"_s;
case AccessibilityRole::ApplicationLog:
return "ApplicationLog"_s;
case AccessibilityRole::ApplicationMarquee:
return "ApplicationMarquee"_s;
case AccessibilityRole::ApplicationStatus:
return "ApplicationStatus"_s;
case AccessibilityRole::ApplicationTimer:
return "ApplicationTimer"_s;
case AccessibilityRole::Audio:
return "Audio"_s;
case AccessibilityRole::Blockquote:
return "Blockquote"_s;
case AccessibilityRole::Button:
return "Button"_s;
case AccessibilityRole::Canvas:
return "Canvas"_s;
case AccessibilityRole::Caption:
return "Caption"_s;
case AccessibilityRole::Cell:
return "Cell"_s;
case AccessibilityRole::Checkbox:
return "Checkbox"_s;
case AccessibilityRole::Code:
return "Code"_s;
case AccessibilityRole::ColorWell:
return "ColorWell"_s;
case AccessibilityRole::Column:
return "Column"_s;
case AccessibilityRole::ColumnHeader:
return "ColumnHeader"_s;
case AccessibilityRole::ComboBox:
return "ComboBox"_s;
case AccessibilityRole::DateTime:
return "DateTime"_s;
case AccessibilityRole::Definition:
return "Definition"_s;
case AccessibilityRole::Deletion:
return "Deletion"_s;
case AccessibilityRole::DescriptionList:
return "DescriptionList"_s;
case AccessibilityRole::DescriptionListTerm:
return "DescriptionListTerm"_s;
case AccessibilityRole::DescriptionListDetail:
return "DescriptionListDetail"_s;
case AccessibilityRole::Details:
return "Details"_s;
case AccessibilityRole::Directory:
return "Directory"_s;
case AccessibilityRole::Document:
return "Document"_s;
case AccessibilityRole::DocumentArticle:
return "DocumentArticle"_s;
case AccessibilityRole::DocumentMath:
return "DocumentMath"_s;
case AccessibilityRole::DocumentNote:
return "DocumentNote"_s;
case AccessibilityRole::Emphasis:
return "Emphasis"_s;
case AccessibilityRole::Feed:
return "Feed"_s;
case AccessibilityRole::Figure:
return "Figure"_s;
case AccessibilityRole::Footnote:
return "Footnote"_s;
case AccessibilityRole::Form:
return "Form"_s;
case AccessibilityRole::FrameHost:
return "FrameHost"_s;
case AccessibilityRole::Generic:
return "Generic"_s;
case AccessibilityRole::GraphicsDocument:
return "GraphicsDocument"_s;
case AccessibilityRole::GraphicsObject:
return "GraphicsObject"_s;
case AccessibilityRole::GraphicsSymbol:
return "GraphicsSymbol"_s;
case AccessibilityRole::Grid:
return "Grid"_s;
case AccessibilityRole::GridCell:
return "GridCell"_s;
case AccessibilityRole::Group:
return "Group"_s;
case AccessibilityRole::Heading:
return "Heading"_s;
case AccessibilityRole::HorizontalRule:
return "HorizontalRule"_s;
case AccessibilityRole::Ignored:
return "Ignored"_s;
case AccessibilityRole::Inline:
return "Inline"_s;
case AccessibilityRole::Image:
return "Image"_s;
case AccessibilityRole::ImageMap:
return "ImageMap"_s;
case AccessibilityRole::Insertion:
return "Insertion"_s;
case AccessibilityRole::Label:
return "Label"_s;
case AccessibilityRole::LandmarkBanner:
return "LandmarkBanner"_s;
case AccessibilityRole::LandmarkComplementary:
return "LandmarkComplementary"_s;
case AccessibilityRole::LandmarkContentInfo:
return "LandmarkContentInfo"_s;
case AccessibilityRole::LandmarkDocRegion:
return "LandmarkDocRegion"_s;
case AccessibilityRole::LandmarkMain:
return "LandmarkMain"_s;
case AccessibilityRole::LandmarkNavigation:
return "LandmarkNavigation"_s;
case AccessibilityRole::LandmarkRegion:
return "LandmarkRegion"_s;
case AccessibilityRole::LandmarkSearch:
return "LandmarkSearch"_s;
case AccessibilityRole::Legend:
return "Legend"_s;
case AccessibilityRole::Link:
return "Link"_s;
case AccessibilityRole::LineBreak:
return "LineBreak"_s;
case AccessibilityRole::List:
return "List"_s;
case AccessibilityRole::ListBox:
return "ListBox"_s;
case AccessibilityRole::ListBoxOption:
return "ListBoxOption"_s;
case AccessibilityRole::ListItem:
return "ListItem"_s;
case AccessibilityRole::ListMarker:
return "ListMarker"_s;
case AccessibilityRole::LocalFrame:
return "LocalFrame"_s;
case AccessibilityRole::Mark:
return "Mark"_s;
case AccessibilityRole::MathElement:
return "MathElement"_s;
case AccessibilityRole::Menu:
return "Menu"_s;
case AccessibilityRole::MenuBar:
return "MenuBar"_s;
case AccessibilityRole::MenuItem:
return "MenuItem"_s;
case AccessibilityRole::MenuItemCheckbox:
return "MenuItemCheckbox"_s;
case AccessibilityRole::MenuItemRadio:
return "MenuItemRadio"_s;
case AccessibilityRole::MenuListPopup:
return "MenuListPopup"_s;
case AccessibilityRole::MenuListOption:
return "MenuListOption"_s;
case AccessibilityRole::Meter:
return "Meter"_s;
case AccessibilityRole::Model:
return "Model"_s;
case AccessibilityRole::Paragraph:
return "Paragraph"_s;
case AccessibilityRole::PopUpButton:
return "PopUpButton"_s;
case AccessibilityRole::Pre:
return "Pre"_s;
case AccessibilityRole::Presentational:
return "Presentational"_s;
case AccessibilityRole::ProgressIndicator:
return "ProgressIndicator"_s;
case AccessibilityRole::RadioButton:
return "RadioButton"_s;
case AccessibilityRole::RadioGroup:
return "RadioGroup"_s;
case AccessibilityRole::RemoteFrame:
return "RemoteFrame"_s;
case AccessibilityRole::RowHeader:
return "RowHeader"_s;
case AccessibilityRole::Row:
return "Row"_s;
case AccessibilityRole::RowGroup:
return "RowGroup"_s;
case AccessibilityRole::RubyInline:
return "RubyInline"_s;
case AccessibilityRole::RubyText:
return "RubyText"_s;
case AccessibilityRole::ScrollArea:
return "ScrollArea"_s;
case AccessibilityRole::ScrollBar:
return "ScrollBar"_s;
case AccessibilityRole::SearchField:
return "SearchField"_s;
case AccessibilityRole::SectionFooter:
return "SectionFooter"_s;
case AccessibilityRole::SectionHeader:
return "SectionHeader"_s;
case AccessibilityRole::Slider:
return "Slider"_s;
case AccessibilityRole::SliderThumb:
return "SliderThumb"_s;
case AccessibilityRole::SpinButton:
return "SpinButton"_s;
case AccessibilityRole::SpinButtonPart:
return "SpinButtonPart"_s;
case AccessibilityRole::Splitter:
return "Splitter"_s;
case AccessibilityRole::StaticText:
return "StaticText"_s;
case AccessibilityRole::Strong:
return "Strong"_s;
case AccessibilityRole::Subscript:
return "Subscript"_s;
case AccessibilityRole::Suggestion:
return "Suggestion"_s;
case AccessibilityRole::Summary:
return "Summary"_s;
case AccessibilityRole::Superscript:
return "Superscript"_s;
case AccessibilityRole::Switch:
return "Switch"_s;
case AccessibilityRole::SVGRoot:
return "SVGRoot"_s;
case AccessibilityRole::SVGText:
return "SVGText"_s;
case AccessibilityRole::SVGTSpan:
return "SVGTSpan"_s;
case AccessibilityRole::SVGTextPath:
return "SVGTextPath"_s;
case AccessibilityRole::TabGroup:
return "TabGroup"_s;
case AccessibilityRole::TabList:
return "TabList"_s;
case AccessibilityRole::TabPanel:
return "TabPanel"_s;
case AccessibilityRole::Tab:
return "Tab"_s;
case AccessibilityRole::Table:
return "Table"_s;
case AccessibilityRole::TableHeaderContainer:
return "TableHeaderContainer"_s;
case AccessibilityRole::Term:
return "Term"_s;
case AccessibilityRole::TextArea:
return "TextArea"_s;
case AccessibilityRole::TextField:
return "TextField"_s;
case AccessibilityRole::TextGroup:
return "TextGroup"_s;
case AccessibilityRole::Time:
return "Time"_s;
case AccessibilityRole::Tree:
return "Tree"_s;
case AccessibilityRole::TreeGrid:
return "TreeGrid"_s;
case AccessibilityRole::TreeItem:
return "TreeItem"_s;
case AccessibilityRole::ToggleButton:
return "ToggleButton"_s;
case AccessibilityRole::Toolbar:
return "Toolbar"_s;
case AccessibilityRole::Unknown:
return "Unknown"_s;
case AccessibilityRole::UserInterfaceTooltip:
return "UserInterfaceTooltip"_s;
case AccessibilityRole::Video:
return "Video"_s;
case AccessibilityRole::WebApplication:
return "WebApplication"_s;
case AccessibilityRole::WebArea:
return "WebArea"_s;
}
AX_ASSERT_NOT_REACHED();
return ""_s;
}
bool needsLayoutOrStyleRecalc(const Document& document)
{
if (RefPtr frameView = document.view()) {
if (frameView->needsLayout() || frameView->checkedLayoutContext()->isLayoutPending())
return true;
}
return document.hasPendingStyleRecalc();
}
std::optional<CursorType> cursorTypeFrom(const StyleProperties& properties)
{
for (auto property : properties) {
if (property.id() == CSSPropertyCursor) {
if (RefPtr primitiveValue = dynamicDowncast<CSSPrimitiveValue>(property.value()))
return fromCSSValue<CursorType>(*primitiveValue);
if (RefPtr valueList = dynamicDowncast<CSSValueList>(property.value()); valueList && valueList->size() >= 2) {
if (RefPtr primitiveValue = dynamicDowncast<CSSPrimitiveValue>((*valueList)[valueList->size() - 1]))
return fromCSSValue<CursorType>(*primitiveValue);
}
}
}
return std::nullopt;
}
RefPtr<Node> lastNode(const FixedVector<AXID>& axIDs, AXObjectCache& cache)
{
AX_ASSERT(isMainThread());
for (auto axID = axIDs.rbegin(); axID != axIDs.rend(); ++axID) {
if (RefPtr object = cache.objectForID(*axID)) {
if (RefPtr node = object->node())
return node;
}
}
return nullptr;
}
} // namespace WebCore