diff options
Diffstat (limited to 'Source/WebCore/accessibility')
97 files changed, 9510 insertions, 4096 deletions
diff --git a/Source/WebCore/accessibility/AXObjectCache.cpp b/Source/WebCore/accessibility/AXObjectCache.cpp index fec39c7fb..0ceedd7d4 100644 --- a/Source/WebCore/accessibility/AXObjectCache.cpp +++ b/Source/WebCore/accessibility/AXObjectCache.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009, 2010, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -35,20 +35,23 @@ #include "AccessibilityARIAGrid.h" #include "AccessibilityARIAGridCell.h" #include "AccessibilityARIAGridRow.h" +#include "AccessibilityAttachment.h" #include "AccessibilityImageMapLink.h" +#include "AccessibilityLabel.h" #include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilityListBoxOption.h" +#include "AccessibilityMathMLElement.h" #include "AccessibilityMediaControls.h" #include "AccessibilityMenuList.h" #include "AccessibilityMenuListOption.h" #include "AccessibilityMenuListPopup.h" #include "AccessibilityProgressIndicator.h" #include "AccessibilityRenderObject.h" +#include "AccessibilitySVGElement.h" #include "AccessibilitySVGRoot.h" #include "AccessibilityScrollView.h" #include "AccessibilityScrollbar.h" -#include "AccessibilitySearchFieldButtons.h" #include "AccessibilitySlider.h" #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" @@ -56,37 +59,61 @@ #include "AccessibilityTableColumn.h" #include "AccessibilityTableHeaderContainer.h" #include "AccessibilityTableRow.h" +#include "AccessibilityTree.h" +#include "AccessibilityTreeItem.h" #include "Document.h" #include "Editor.h" +#include "ElementIterator.h" #include "FocusController.h" #include "Frame.h" #include "HTMLAreaElement.h" +#include "HTMLCanvasElement.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLMeterElement.h" #include "HTMLNames.h" +#include "InlineElementBox.h" +#include "MathMLElement.h" #include "Page.h" +#include "RenderAttachment.h" +#include "RenderLineBreak.h" #include "RenderListBox.h" +#include "RenderMathMLOperator.h" #include "RenderMenuList.h" #include "RenderMeter.h" #include "RenderProgress.h" +#include "RenderSVGRoot.h" #include "RenderSlider.h" #include "RenderTable.h" #include "RenderTableCell.h" #include "RenderTableRow.h" #include "RenderView.h" +#include "SVGElement.h" #include "ScrollView.h" -#include <wtf/PassRefPtr.h> +#include "TextBoundaries.h" +#include "TextIterator.h" +#include "htmlediting.h" +#include <wtf/DataLog.h> #if ENABLE(VIDEO) #include "MediaControlElements.h" #endif +#if COMPILER(MSVC) +// See https://msdn.microsoft.com/en-us/library/1wea5zwe.aspx +#pragma warning(disable: 4701) +#endif + namespace WebCore { using namespace HTMLNames; +// Post value change notifications for password fields or elements contained in password fields at a 40hz interval to thwart analysis of typing cadence +static double AccessibilityPasswordValueChangeNotificationInterval = 0.025; +static double AccessibilityLiveRegionChangedNotificationInterval = 0.020; +static double AccessibilityFocusAriaModalNodeNotificationInterval = 0.050; + AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const { HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id); @@ -104,7 +131,34 @@ void AXComputedObjectAttributeCache::setIgnored(AXID id, AccessibilityObjectIncl m_idMapping.set(id, attributes); } } - + +AccessibilityReplacedText::AccessibilityReplacedText(const VisibleSelection& selection) +{ + if (AXObjectCache::accessibilityEnabled()) { + m_replacedRange.startIndex.value = indexForVisiblePosition(selection.start(), m_replacedRange.startIndex.scope); + if (selection.isRange()) { + m_replacedText = AccessibilityObject::stringForVisiblePositionRange(selection); + m_replacedRange.endIndex.value = indexForVisiblePosition(selection.end(), m_replacedRange.endIndex.scope); + } else + m_replacedRange.endIndex = m_replacedRange.startIndex; + } +} + +void AccessibilityReplacedText::postTextStateChangeNotification(AXObjectCache* cache, AXTextEditType type, const String& text, const VisibleSelection& selection) +{ + if (!cache) + return; + if (!AXObjectCache::accessibilityEnabled()) + return; + + VisiblePosition position = selection.start(); + auto* node = highestEditableRoot(position.deepEquivalent(), HasEditableAXRole); + if (m_replacedText.length()) + cache->postTextReplacementNotification(node, AXTextEditTypeDelete, m_replacedText, type, text, position); + else + cache->postTextStateChangeNotification(node, type, text, position); +} + bool AXObjectCache::gAccessibilityEnabled = false; bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; @@ -118,15 +172,33 @@ void AXObjectCache::disableAccessibility() gAccessibilityEnabled = false; } +void AXObjectCache::setEnhancedUserInterfaceAccessibility(bool flag) +{ + gAccessibilityEnhancedUserInterfaceEnabled = flag; +#if PLATFORM(MAC) +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 + if (flag) + enableAccessibility(); +#endif +#endif +} + AXObjectCache::AXObjectCache(Document& document) : m_document(document) - , m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) + , m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired) + , m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired) + , m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired) + , m_focusAriaModalNodeTimer(*this, &AXObjectCache::focusAriaModalNodeTimerFired) + , m_currentAriaModalNode(nullptr) { + findAriaModalNodes(); } AXObjectCache::~AXObjectCache() { m_notificationPostTimer.stop(); + m_liveRegionChangedPostTimer.stop(); + m_focusAriaModalNodeTimer.stop(); for (const auto& object : m_objects.values()) { detachWrapper(object.get(), CacheDestroyed); @@ -135,46 +207,131 @@ AXObjectCache::~AXObjectCache() } } +void AXObjectCache::findAriaModalNodes() +{ + // Traverse the DOM tree to look for the aria-modal=true nodes. + for (Element* element = ElementTraversal::firstWithin(document().rootNode()); element; element = ElementTraversal::nextIncludingPseudo(*element)) { + + // Must have dialog or alertdialog role + if (!nodeHasRole(element, "dialog") && !nodeHasRole(element, "alertdialog")) + continue; + if (!equalLettersIgnoringASCIICase(element->attributeWithoutSynchronization(aria_modalAttr), "true")) + continue; + + m_ariaModalNodesSet.add(element); + } + + // Set the current valid aria-modal node if possible. + updateCurrentAriaModalNode(); +} + +void AXObjectCache::updateCurrentAriaModalNode() +{ + // There might be multiple nodes with aria-modal=true set. + // We use this function to pick the one we want. + m_currentAriaModalNode = nullptr; + if (m_ariaModalNodesSet.isEmpty()) + return; + + // We only care about the nodes which are visible. + ListHashSet<RefPtr<Node>> visibleNodes; + for (auto& object : m_ariaModalNodesSet) { + if (isNodeVisible(object)) + visibleNodes.add(object); + } + + if (visibleNodes.isEmpty()) + return; + + // If any of the node are keyboard focused, we want to pick that. + Node* focusedNode = document().focusedElement(); + for (auto& object : visibleNodes) { + if (focusedNode != nullptr && focusedNode->isDescendantOf(object.get())) { + m_currentAriaModalNode = object.get(); + break; + } + } + + // If none of the nodes are focused, we want to pick the last dialog in the DOM. + if (!m_currentAriaModalNode) + m_currentAriaModalNode = visibleNodes.last().get(); +} + +bool AXObjectCache::isNodeVisible(Node* node) const +{ + if (!is<Element>(node)) + return false; + + RenderObject* renderer = node->renderer(); + if (!renderer) + return false; + const RenderStyle& style = renderer->style(); + if (style.display() == NONE || style.visibility() != VISIBLE) + return false; + + // We also need to consider aria hidden status. + if (!isNodeAriaVisible(node)) + return false; + + return true; +} + +Node* AXObjectCache::ariaModalNode() +{ + // This function returns the valid aria modal node. + if (m_ariaModalNodesSet.isEmpty()) + return nullptr; + + // Check the current valid aria modal node first. + // Usually when one dialog sets aria-modal=true, that dialog is the one we want. + if (isNodeVisible(m_currentAriaModalNode)) + return m_currentAriaModalNode; + + // Recompute the valid aria modal node when m_currentAriaModalNode is null or hidden. + updateCurrentAriaModalNode(); + return isNodeVisible(m_currentAriaModalNode) ? m_currentAriaModalNode : nullptr; +} + AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) { // Find the corresponding accessibility object for the HTMLAreaElement. This should be // in the list of children for its corresponding image. if (!areaElement) - return 0; + return nullptr; HTMLImageElement* imageElement = areaElement->imageElement(); if (!imageElement) - return 0; + return nullptr; AccessibilityObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement); if (!axRenderImage) - return 0; + return nullptr; for (const auto& child : axRenderImage->children()) { - if (!child->isImageMapLink()) + if (!is<AccessibilityImageMapLink>(*child)) continue; - if (toAccessibilityImageMapLink(child.get())->areaElement() == areaElement) + if (downcast<AccessibilityImageMapLink>(*child).areaElement() == areaElement) return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) { if (!gAccessibilityEnabled) - return 0; + return nullptr; // get the focused node in the page Document* focusedDocument = page->focusController().focusedOrMainFrame().document(); Element* focusedElement = focusedDocument->focusedElement(); - if (focusedElement && isHTMLAreaElement(focusedElement)) - return focusedImageMapUIElement(toHTMLAreaElement(focusedElement)); + if (is<HTMLAreaElement>(focusedElement)) + return focusedImageMapUIElement(downcast<HTMLAreaElement>(focusedElement)); AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument); if (!obj) - return 0; + return nullptr; if (obj->shouldFocusActiveDescendant()) { if (AccessibilityObject* descendant = obj->activeDescendant()) @@ -191,12 +348,12 @@ AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) AccessibilityObject* AXObjectCache::get(Widget* widget) { if (!widget) - return 0; + return nullptr; AXID axID = m_widgetObjectMapping.get(widget); ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); if (!axID) - return 0; + return nullptr; return m_objects.get(axID); } @@ -204,12 +361,12 @@ AccessibilityObject* AXObjectCache::get(Widget* widget) AccessibilityObject* AXObjectCache::get(RenderObject* renderer) { if (!renderer) - return 0; + return nullptr; AXID axID = m_renderObjectMapping.get(renderer); ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); if (!axID) - return 0; + return nullptr; return m_objects.get(axID); } @@ -217,7 +374,7 @@ AccessibilityObject* AXObjectCache::get(RenderObject* renderer) AccessibilityObject* AXObjectCache::get(Node* node) { if (!node) - return 0; + return nullptr; AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0; ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); @@ -230,14 +387,14 @@ AccessibilityObject* AXObjectCache::get(Node* node) // rendered, but later something changes and it gets a renderer (like if it's // reparented). remove(nodeID); - return 0; + return nullptr; } if (renderID) return m_objects.get(renderID); if (!nodeID) - return 0; + return nullptr; return m_objects.get(nodeID); } @@ -246,13 +403,19 @@ AccessibilityObject* AXObjectCache::get(Node* node) // FIXME: This should take a const char*, but one caller passes nullAtom. bool nodeHasRole(Node* node, const String& role) { - if (!node || !node->isElementNode()) + if (!node || !is<Element>(node)) return false; - return equalIgnoringCase(toElement(node)->fastGetAttribute(roleAttr), role); + auto& roleValue = downcast<Element>(*node).attributeWithoutSynchronization(roleAttr); + if (role.isNull()) + return roleValue.isEmpty(); + if (roleValue.isEmpty()) + return false; + + return SpaceSplitString(roleValue, true).contains(role); } -static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer) +static Ref<AccessibilityObject> createFromRenderer(RenderObject* renderer) { // FIXME: How could renderer->node() ever not be an Element? Node* node = renderer->node(); @@ -264,62 +427,80 @@ static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer return AccessibilityList::create(renderer); // aria tables - if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) + if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid") || nodeHasRole(node, "table")) return AccessibilityARIAGrid::create(renderer); if (nodeHasRole(node, "row")) return AccessibilityARIAGridRow::create(renderer); - if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) + if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "cell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) return AccessibilityARIAGridCell::create(renderer); + // aria tree + if (nodeHasRole(node, "tree")) + return AccessibilityTree::create(renderer); + if (nodeHasRole(node, "treeitem")) + return AccessibilityTreeItem::create(renderer); + + if (node && is<HTMLLabelElement>(node) && nodeHasRole(node, nullAtom)) + return AccessibilityLabel::create(renderer); + #if ENABLE(VIDEO) // media controls if (node && node->isMediaControlElement()) return AccessibilityMediaControl::create(renderer); #endif -#if ENABLE(SVG) - if (renderer->isSVGRoot()) + if (is<RenderSVGRoot>(*renderer)) return AccessibilitySVGRoot::create(renderer); -#endif - - // Search field buttons - if (node && node->isElementNode() && toElement(node)->isSearchFieldCancelButtonElement()) - return AccessibilitySearchFieldCancelButton::create(renderer); - if (renderer->isBoxModelObject()) { - RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); - if (cssBox->isListBox()) - return AccessibilityListBox::create(toRenderListBox(cssBox)); - if (cssBox->isMenuList()) - return AccessibilityMenuList::create(toRenderMenuList(cssBox)); + if (is<SVGElement>(node)) + return AccessibilitySVGElement::create(renderer); + +#if ENABLE(MATHML) + // The mfenced element creates anonymous RenderMathMLOperators which should be treated + // as MathML elements and assigned the MathElementRole so that platform logic regarding + // inclusion and role mapping is not bypassed. + bool isAnonymousOperator = renderer->isAnonymous() && is<RenderMathMLOperator>(*renderer); + if (isAnonymousOperator || is<MathMLElement>(node)) + return AccessibilityMathMLElement::create(renderer, isAnonymousOperator); +#endif + + if (is<RenderBoxModelObject>(*renderer)) { + RenderBoxModelObject& cssBox = downcast<RenderBoxModelObject>(*renderer); + if (is<RenderListBox>(cssBox)) + return AccessibilityListBox::create(&downcast<RenderListBox>(cssBox)); + if (is<RenderMenuList>(cssBox)) + return AccessibilityMenuList::create(&downcast<RenderMenuList>(cssBox)); // standard tables - if (cssBox->isTable()) - return AccessibilityTable::create(toRenderTable(cssBox)); - if (cssBox->isTableRow()) - return AccessibilityTableRow::create(toRenderTableRow(cssBox)); - if (cssBox->isTableCell()) - return AccessibilityTableCell::create(toRenderTableCell(cssBox)); - -#if ENABLE(PROGRESS_ELEMENT) + if (is<RenderTable>(cssBox)) + return AccessibilityTable::create(&downcast<RenderTable>(cssBox)); + if (is<RenderTableRow>(cssBox)) + return AccessibilityTableRow::create(&downcast<RenderTableRow>(cssBox)); + if (is<RenderTableCell>(cssBox)) + return AccessibilityTableCell::create(&downcast<RenderTableCell>(cssBox)); + // progress bar - if (cssBox->isProgress()) - return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); + if (is<RenderProgress>(cssBox)) + return AccessibilityProgressIndicator::create(&downcast<RenderProgress>(cssBox)); + +#if ENABLE(ATTACHMENT_ELEMENT) + if (is<RenderAttachment>(cssBox)) + return AccessibilityAttachment::create(&downcast<RenderAttachment>(cssBox)); #endif #if ENABLE(METER_ELEMENT) - if (cssBox->isMeter()) - return AccessibilityProgressIndicator::create(toRenderMeter(cssBox)); + if (is<RenderMeter>(cssBox)) + return AccessibilityProgressIndicator::create(&downcast<RenderMeter>(cssBox)); #endif // input type=range - if (cssBox->isSlider()) - return AccessibilitySlider::create(toRenderSlider(cssBox)); + if (is<RenderSlider>(cssBox)) + return AccessibilitySlider::create(&downcast<RenderSlider>(cssBox)); } return AccessibilityRenderObject::create(renderer); } -static PassRefPtr<AccessibilityObject> createFromNode(Node* node) +static Ref<AccessibilityObject> createFromNode(Node* node) { return AccessibilityNodeObject::create(node); } @@ -327,16 +508,16 @@ static PassRefPtr<AccessibilityObject> createFromNode(Node* node) AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) { if (!widget) - return 0; + return nullptr; if (AccessibilityObject* obj = get(widget)) return obj; - RefPtr<AccessibilityObject> newObj = 0; - if (widget->isFrameView()) - newObj = AccessibilityScrollView::create(toScrollView(widget)); - else if (widget->isScrollbar()) - newObj = AccessibilityScrollbar::create(toScrollbar(widget)); + RefPtr<AccessibilityObject> newObj; + if (is<ScrollView>(*widget)) + newObj = AccessibilityScrollView::create(downcast<ScrollView>(widget)); + else if (is<Scrollbar>(*widget)) + newObj = AccessibilityScrollbar::create(downcast<Scrollbar>(widget)); // Will crash later if we have two objects for the same widget. ASSERT(!get(widget)); @@ -344,7 +525,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now. ASSERT(newObj); if (!newObj) - return 0; + return nullptr; getAXID(newObj.get()); @@ -358,7 +539,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) AccessibilityObject* AXObjectCache::getOrCreate(Node* node) { if (!node) - return 0; + return nullptr; if (AccessibilityObject* obj = get(node)) return obj; @@ -367,20 +548,25 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node) return getOrCreate(node->renderer()); if (!node->parentElement()) - return 0; + return nullptr; // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree. // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes. - bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); - bool isHidden = !node->renderer() && isNodeAriaVisible(node); + bool inCanvasSubtree = lineageOfType<HTMLCanvasElement>(*node->parentElement()).first(); + bool isHidden = isNodeAriaVisible(node); bool insideMeterElement = false; #if ENABLE(METER_ELEMENT) - insideMeterElement = isHTMLMeterElement(node->parentElement()); + insideMeterElement = is<HTMLMeterElement>(*node->parentElement()); #endif if (!inCanvasSubtree && !isHidden && !insideMeterElement) - return 0; + return nullptr; + + // Fallback content is only focusable as long as the canvas is displayed and visible. + // Update the style before Element::isFocusable() gets called. + if (inCanvasSubtree) + node->document().updateStyleIfNeeded(); RefPtr<AccessibilityObject> newObj = createFromNode(node); @@ -405,7 +591,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node) AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) { if (!renderer) - return 0; + return nullptr; if (AccessibilityObject* obj = get(renderer)) return obj; @@ -433,7 +619,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) AccessibilityObject* AXObjectCache::rootObject() { if (!gAccessibilityEnabled) - return 0; + return nullptr; return getOrCreate(m_document.view()); } @@ -441,16 +627,16 @@ AccessibilityObject* AXObjectCache::rootObject() AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame) { if (!gAccessibilityEnabled) - return 0; + return nullptr; if (!frame) - return 0; + return nullptr; return getOrCreate(frame->view()); } AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) { - RefPtr<AccessibilityObject> obj = 0; + RefPtr<AccessibilityObject> obj = nullptr; // will be filled in... switch (role) { @@ -482,13 +668,13 @@ AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) obj = AccessibilitySpinButtonPart::create(); break; default: - obj = 0; + obj = nullptr; } if (obj) getAXID(obj.get()); else - return 0; + return nullptr; m_objects.set(obj->axObjectID(), obj); obj->init(); @@ -525,6 +711,8 @@ void AXObjectCache::remove(RenderObject* renderer) AXID axID = m_renderObjectMapping.get(renderer); remove(axID); m_renderObjectMapping.remove(renderer); + if (is<RenderBlock>(*renderer)) + m_deferredIsIgnoredChangeList.remove(downcast<RenderBlock>(renderer)); } void AXObjectCache::remove(Node* node) @@ -539,6 +727,12 @@ void AXObjectCache::remove(Node* node) remove(axID); m_nodeObjectMapping.remove(node); + // Cleanup for aria modal nodes. + if (m_currentAriaModalNode == node) + m_currentAriaModalNode = nullptr; + if (m_ariaModalNodesSet.contains(node)) + m_ariaModalNodesSet.remove(node); + if (node->renderer()) { remove(node->renderer()); return; @@ -556,7 +750,7 @@ void AXObjectCache::remove(Widget* view) } -#if !PLATFORM(WIN) || OS(WINCE) +#if !PLATFORM(WIN) AXID AXObjectCache::platformGenerateAXID() const { static AXID lastUsedID = 0; @@ -641,8 +835,30 @@ void AXObjectCache::handleMenuOpened(Node* node) postNotification(getOrCreate(node), &document(), AXMenuOpened); } -void AXObjectCache::childrenChanged(Node* node) +void AXObjectCache::handleLiveRegionCreated(Node* node) +{ + if (!is<Element>(node) || !node->renderer()) + return; + + Element* element = downcast<Element>(node); + String liveRegionStatus = element->attributeWithoutSynchronization(aria_liveAttr); + if (liveRegionStatus.isEmpty()) { + const AtomicString& ariaRole = element->attributeWithoutSynchronization(roleAttr); + if (!ariaRole.isEmpty()) + liveRegionStatus = AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityObject::ariaRoleToWebCoreRole(ariaRole)); + } + + if (AccessibilityObject::liveRegionStatusIsEnabled(liveRegionStatus)) + postNotification(getOrCreate(node), &document(), AXLiveRegionCreated); +} + +void AXObjectCache::childrenChanged(Node* node, Node* newChild) { + if (newChild) { + handleMenuOpened(newChild); + handleLiveRegionCreated(newChild); + } + childrenChanged(get(node)); } @@ -650,8 +866,11 @@ void AXObjectCache::childrenChanged(RenderObject* renderer, RenderObject* newChi { if (!renderer) return; - if (newChild) + + if (newChild) { handleMenuOpened(newChild->node()); + handleLiveRegionCreated(newChild->node()); + } childrenChanged(get(renderer)); } @@ -664,15 +883,14 @@ void AXObjectCache::childrenChanged(AccessibilityObject* obj) obj->childrenChanged(); } -void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&) +void AXObjectCache::notificationPostTimerFired() { Ref<Document> protectorForCacheOwner(m_document); m_notificationPostTimer.stop(); - // In DRT, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior + // In tests, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior // when the notification list is cleared at the end. Instead copy this list at the start. - auto notifications = m_notificationsToPost; - m_notificationsToPost.clear(); + auto notifications = WTFMove(m_notificationsToPost); for (const auto& note : notifications) { AccessibilityObject* obj = note.first.get(); @@ -685,21 +903,42 @@ void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&) #ifndef NDEBUG // Make sure none of the render views are in the process of being layed out. // Notifications should only be sent after the renderer has finished - if (obj->isAccessibilityRenderObject()) { - AccessibilityRenderObject* renderObj = toAccessibilityRenderObject(obj); - RenderObject* renderer = renderObj->renderer(); - if (renderer) + if (is<AccessibilityRenderObject>(*obj)) { + if (auto* renderer = downcast<AccessibilityRenderObject>(*obj).renderer()) ASSERT(!renderer->view().layoutState()); } #endif AXNotification notification = note.second; + + // Ensure that this menu really is a menu. We do this check here so that we don't have to create + // the axChildren when the menu is marked as opening. + if (notification == AXMenuOpened) { + obj->updateChildrenIfNecessary(); + if (obj->roleValue() != MenuRole) + continue; + } + postPlatformNotification(obj, notification); if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) childrenChanged(obj->parentObject()); } } + +void AXObjectCache::passwordNotificationPostTimerFired() +{ +#if PLATFORM(COCOA) + m_passwordNotificationPostTimer.stop(); + + // In tests, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior + // when the notification list is cleared at the end. Instead copy this list at the start. + auto notifications = WTFMove(m_passwordNotificationsToPost); + + for (auto& notification : notifications) + postTextStateChangePlatformNotification(notification.get(), AXTextEditTypeInsert, " ", VisiblePosition()); +#endif +} void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, PostTarget postTarget, PostType postType) { @@ -777,7 +1016,7 @@ void AXObjectCache::handleMenuItemSelected(Node* node) if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox")) return; - if (!toElement(node)->focused() && !equalIgnoringCase(toElement(node)->fastGetAttribute(aria_selectedAttr), "true")) + if (!downcast<Element>(*node).focused() && !equalLettersIgnoringASCIICase(downcast<Element>(*node).attributeWithoutSynchronization(aria_selectedAttr), "true")) return; postNotification(getOrCreate(node), &document(), AXMenuListItemSelected); @@ -808,16 +1047,273 @@ void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) postNotification(renderer, AXSelectedChildrenChanged, TargetObservableParent); } -void AXObjectCache::nodeTextChangeNotification(Node* node, AXTextChange textChange, unsigned offset, const String& text) +#ifndef NDEBUG +void AXObjectCache::showIntent(const AXTextStateChangeIntent &intent) +{ + switch (intent.type) { + case AXTextStateChangeTypeUnknown: + dataLog("Unknown"); + break; + case AXTextStateChangeTypeEdit: + dataLog("Edit::"); + break; + case AXTextStateChangeTypeSelectionMove: + dataLog("Move::"); + break; + case AXTextStateChangeTypeSelectionExtend: + dataLog("Extend::"); + break; + case AXTextStateChangeTypeSelectionBoundary: + dataLog("Boundary::"); + break; + } + switch (intent.type) { + case AXTextStateChangeTypeUnknown: + break; + case AXTextStateChangeTypeEdit: + switch (intent.change) { + case AXTextEditTypeUnknown: + dataLog("Unknown"); + break; + case AXTextEditTypeDelete: + dataLog("Delete"); + break; + case AXTextEditTypeInsert: + dataLog("Insert"); + break; + case AXTextEditTypeDictation: + dataLog("DictationInsert"); + break; + case AXTextEditTypeTyping: + dataLog("TypingInsert"); + break; + case AXTextEditTypeCut: + dataLog("Cut"); + break; + case AXTextEditTypePaste: + dataLog("Paste"); + break; + case AXTextEditTypeAttributesChange: + dataLog("AttributesChange"); + break; + } + break; + case AXTextStateChangeTypeSelectionMove: + case AXTextStateChangeTypeSelectionExtend: + case AXTextStateChangeTypeSelectionBoundary: + switch (intent.selection.direction) { + case AXTextSelectionDirectionUnknown: + dataLog("Unknown::"); + break; + case AXTextSelectionDirectionBeginning: + dataLog("Beginning::"); + break; + case AXTextSelectionDirectionEnd: + dataLog("End::"); + break; + case AXTextSelectionDirectionPrevious: + dataLog("Previous::"); + break; + case AXTextSelectionDirectionNext: + dataLog("Next::"); + break; + case AXTextSelectionDirectionDiscontiguous: + dataLog("Discontiguous::"); + break; + } + switch (intent.selection.direction) { + case AXTextSelectionDirectionUnknown: + case AXTextSelectionDirectionBeginning: + case AXTextSelectionDirectionEnd: + case AXTextSelectionDirectionPrevious: + case AXTextSelectionDirectionNext: + switch (intent.selection.granularity) { + case AXTextSelectionGranularityUnknown: + dataLog("Unknown"); + break; + case AXTextSelectionGranularityCharacter: + dataLog("Character"); + break; + case AXTextSelectionGranularityWord: + dataLog("Word"); + break; + case AXTextSelectionGranularityLine: + dataLog("Line"); + break; + case AXTextSelectionGranularitySentence: + dataLog("Sentence"); + break; + case AXTextSelectionGranularityParagraph: + dataLog("Paragraph"); + break; + case AXTextSelectionGranularityPage: + dataLog("Page"); + break; + case AXTextSelectionGranularityDocument: + dataLog("Document"); + break; + case AXTextSelectionGranularityAll: + dataLog("All"); + break; + } + break; + case AXTextSelectionDirectionDiscontiguous: + break; + } + break; + } + dataLog("\n"); +} +#endif + +void AXObjectCache::setTextSelectionIntent(const AXTextStateChangeIntent& intent) +{ + m_textSelectionIntent = intent; +} + +void AXObjectCache::setIsSynchronizingSelection(bool isSynchronizing) +{ + m_isSynchronizingSelection = isSynchronizing; +} + +static bool isPasswordFieldOrContainedByPasswordField(AccessibilityObject* object) +{ + return object && (object->isPasswordField() || object->isContainedByPasswordField()); +} + +void AXObjectCache::postTextStateChangeNotification(Node* node, const AXTextStateChangeIntent& intent, const VisibleSelection& selection) { if (!node) return; +#if PLATFORM(COCOA) stopCachingComputedObjectAttributes(); - // Delegate on the right platform - AccessibilityObject* obj = getOrCreate(node); - nodeTextChangePlatformNotification(obj, textChange, offset, text); + postTextStateChangeNotification(getOrCreate(node), intent, selection); +#else + postNotification(node->renderer(), AXObjectCache::AXSelectedTextChanged, TargetObservableParent); + UNUSED_PARAM(intent); + UNUSED_PARAM(selection); +#endif +} + +void AXObjectCache::postTextStateChangeNotification(const Position& position, const AXTextStateChangeIntent& intent, const VisibleSelection& selection) +{ + Node* node = position.deprecatedNode(); + if (!node) + return; + + stopCachingComputedObjectAttributes(); + +#if PLATFORM(COCOA) + AccessibilityObject* object = getOrCreate(node); + if (object && object->accessibilityIsIgnored()) { + if (position.atLastEditingPositionForNode()) { + if (AccessibilityObject* nextSibling = object->nextSiblingUnignored(1)) + object = nextSibling; + } else if (position.atFirstEditingPositionForNode()) { + if (AccessibilityObject* previousSibling = object->previousSiblingUnignored(1)) + object = previousSibling; + } + } + + postTextStateChangeNotification(object, intent, selection); +#else + postTextStateChangeNotification(node, intent, selection); +#endif +} + +void AXObjectCache::postTextStateChangeNotification(AccessibilityObject* object, const AXTextStateChangeIntent& intent, const VisibleSelection& selection) +{ + stopCachingComputedObjectAttributes(); + +#if PLATFORM(COCOA) + if (object) { + if (isPasswordFieldOrContainedByPasswordField(object)) + return; + + if (auto observableObject = object->observableObject()) + object = observableObject; + } + + const AXTextStateChangeIntent& newIntent = (intent.type == AXTextStateChangeTypeUnknown || (m_isSynchronizingSelection && m_textSelectionIntent.type != AXTextStateChangeTypeUnknown)) ? m_textSelectionIntent : intent; + postTextStateChangePlatformNotification(object, newIntent, selection); +#else + UNUSED_PARAM(object); + UNUSED_PARAM(intent); + UNUSED_PARAM(selection); +#endif + + setTextSelectionIntent(AXTextStateChangeIntent()); + setIsSynchronizingSelection(false); +} + +void AXObjectCache::postTextStateChangeNotification(Node* node, AXTextEditType type, const String& text, const VisiblePosition& position) +{ + if (!node) + return; + if (type == AXTextEditTypeUnknown) + return; + + stopCachingComputedObjectAttributes(); + + AccessibilityObject* object = getOrCreate(node); +#if PLATFORM(COCOA) + if (object) { + if (enqueuePasswordValueChangeNotification(object)) + return; + object = object->observableObject(); + } + + postTextStateChangePlatformNotification(object, type, text, position); +#else + nodeTextChangePlatformNotification(object, textChangeForEditType(type), position.deepEquivalent().deprecatedEditingOffset(), text); +#endif +} + +void AXObjectCache::postTextReplacementNotification(Node* node, AXTextEditType deletionType, const String& deletedText, AXTextEditType insertionType, const String& insertedText, const VisiblePosition& position) +{ + if (!node) + return; + if (deletionType != AXTextEditTypeDelete) + return; + if (!(insertionType == AXTextEditTypeInsert || insertionType == AXTextEditTypeTyping || insertionType == AXTextEditTypeDictation || insertionType == AXTextEditTypePaste)) + return; + + stopCachingComputedObjectAttributes(); + + AccessibilityObject* object = getOrCreate(node); +#if PLATFORM(COCOA) + if (object) { + if (enqueuePasswordValueChangeNotification(object)) + return; + object = object->observableObject(); + } + + postTextReplacementPlatformNotification(object, deletionType, deletedText, insertionType, insertedText, position); +#else + nodeTextChangePlatformNotification(object, textChangeForEditType(deletionType), position.deepEquivalent().deprecatedEditingOffset(), deletedText); + nodeTextChangePlatformNotification(object, textChangeForEditType(insertionType), position.deepEquivalent().deprecatedEditingOffset(), insertedText); +#endif +} + +bool AXObjectCache::enqueuePasswordValueChangeNotification(AccessibilityObject* object) +{ + if (!isPasswordFieldOrContainedByPasswordField(object)) + return false; + + AccessibilityObject* observableObject = object->observableObject(); + if (!observableObject) { + ASSERT_NOT_REACHED(); + // return true even though the enqueue didn't happen because this is a password field and caller shouldn't post a notification + return true; + } + + m_passwordNotificationsToPost.add(observableObject); + if (!m_passwordNotificationPostTimer.isActive()) + m_passwordNotificationPostTimer.startOneShot(AccessibilityPasswordValueChangeNotificationInterval); + + return true; } void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent) @@ -834,6 +1330,67 @@ void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent l frameLoadingEventPlatformNotification(obj, loadingEvent); } +void AXObjectCache::postLiveRegionChangeNotification(AccessibilityObject* object) +{ + if (m_liveRegionChangedPostTimer.isActive()) + m_liveRegionChangedPostTimer.stop(); + + if (!m_liveRegionObjectsSet.contains(object)) + m_liveRegionObjectsSet.add(object); + + m_liveRegionChangedPostTimer.startOneShot(AccessibilityLiveRegionChangedNotificationInterval); +} + +void AXObjectCache::liveRegionChangedNotificationPostTimerFired() +{ + m_liveRegionChangedPostTimer.stop(); + + if (m_liveRegionObjectsSet.isEmpty()) + return; + + for (auto& object : m_liveRegionObjectsSet) + postNotification(object.get(), object->document(), AXObjectCache::AXLiveRegionChanged); + m_liveRegionObjectsSet.clear(); +} + +static AccessibilityObject* firstFocusableChild(AccessibilityObject* obj) +{ + if (!obj) + return nullptr; + + for (auto* child = obj->firstChild(); child; child = child->nextSibling()) { + if (child->canSetFocusAttribute()) + return child; + if (AccessibilityObject* focusable = firstFocusableChild(child)) + return focusable; + } + return nullptr; +} + +void AXObjectCache::focusAriaModalNode() +{ + if (m_focusAriaModalNodeTimer.isActive()) + m_focusAriaModalNodeTimer.stop(); + + m_focusAriaModalNodeTimer.startOneShot(AccessibilityFocusAriaModalNodeNotificationInterval); +} + +void AXObjectCache::focusAriaModalNodeTimerFired() +{ + if (!m_currentAriaModalNode) + return; + + // Don't set focus if we are already focusing onto some element within + // the dialog. + if (m_currentAriaModalNode->contains(document().focusedElement())) + return; + + if (AccessibilityObject* currentAriaModalNodeObject = getOrCreate(m_currentAriaModalNode)) { + if (AccessibilityObject* focusable = firstFocusableChild(currentAriaModalNodeObject)) + focusable->setFocused(true); + } +} + void AXObjectCache::handleScrollbarUpdate(ScrollView* view) { if (!view) @@ -874,7 +1431,7 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen handleAriaRoleChanged(element); else if (attrName == altAttr || attrName == titleAttr) textChanged(element); - else if (attrName == forAttr && isHTMLLabelElement(element)) + else if (attrName == forAttr && is<HTMLLabelElement>(*element)) labelChanged(element); if (!attrName.localName().string().startsWith("aria-")) @@ -895,17 +1452,44 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen else if (attrName == aria_expandedAttr) handleAriaExpandedChange(element); else if (attrName == aria_hiddenAttr) - childrenChanged(element->parentNode()); + childrenChanged(element->parentNode(), element); else if (attrName == aria_invalidAttr) postNotification(element, AXObjectCache::AXInvalidStatusChanged); + else if (attrName == aria_modalAttr) + handleAriaModalChange(element); else postNotification(element, AXObjectCache::AXAriaAttributeChanged); } +void AXObjectCache::handleAriaModalChange(Node* node) +{ + if (!is<Element>(node)) + return; + + if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog")) + return; + + stopCachingComputedObjectAttributes(); + if (equalLettersIgnoringASCIICase(downcast<Element>(*node).attributeWithoutSynchronization(aria_modalAttr), "true")) { + // Add the newly modified node to the modal nodes set, and set it to be the current valid aria modal node. + // We will recompute the current valid aria modal node in ariaModalNode() when this node is not visible. + m_ariaModalNodesSet.add(node); + m_currentAriaModalNode = node; + } else { + // Remove the node from the modal nodes set. There might be other visible modal nodes, so we recompute here. + m_ariaModalNodesSet.remove(node); + updateCurrentAriaModalNode(); + } + if (m_currentAriaModalNode) + focusAriaModalNode(); + + startCachingComputedObjectAttributesUntilTreeMutates(); +} + void AXObjectCache::labelChanged(Element* element) { - ASSERT(isHTMLLabelElement(element)); - HTMLElement* correspondingControl = toHTMLLabelElement(element)->control(); + ASSERT(is<HTMLLabelElement>(*element)); + HTMLElement* correspondingControl = downcast<HTMLLabelElement>(*element).control(); textChanged(correspondingControl); } @@ -944,11 +1528,555 @@ VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& AXObjectCache* cache = renderer->document().axObjectCache(); if (!cache->isIDinUse(textMarkerData.axID)) return VisiblePosition(); + + return visiblePos; +} + +CharacterOffset AXObjectCache::characterOffsetForTextMarkerData(TextMarkerData& textMarkerData) +{ + if (!isNodeInUse(textMarkerData.node)) + return CharacterOffset(); + + if (textMarkerData.ignored) + return CharacterOffset(); + + CharacterOffset result = CharacterOffset(textMarkerData.node, textMarkerData.characterStartIndex, textMarkerData.characterOffset); + // When we are at a line wrap and the VisiblePosition is upstream, it means the text marker is at the end of the previous line. + // We use the previous CharacterOffset so that it will match the Range. + if (textMarkerData.affinity == UPSTREAM) + return previousCharacterOffset(result, false); + return result; +} + +CharacterOffset AXObjectCache::traverseToOffsetInRange(RefPtr<Range>range, int offset, TraverseOption option, bool stayWithinRange) +{ + if (!range) + return CharacterOffset(); + + bool toNodeEnd = option & TraverseOptionToNodeEnd; + bool validateOffset = option & TraverseOptionValidateOffset; + + int offsetInCharacter = 0; + int cumulativeOffset = 0; + int remaining = 0; + int lastLength = 0; + Node* currentNode = nullptr; + bool finished = false; + int lastStartOffset = 0; + + TextIterator iterator(range.get(), TextIteratorEntersTextControls); + + // When the range has zero length, there might be replaced node or brTag that we need to increment the characterOffset. + if (iterator.atEnd()) { + currentNode = &range->startContainer(); + lastStartOffset = range->startOffset(); + if (offset > 0 || toNodeEnd) { + if (AccessibilityObject::replacedNodeNeedsCharacter(currentNode) || (currentNode->renderer() && currentNode->renderer()->isBR())) + cumulativeOffset++; + lastLength = cumulativeOffset; + + // When going backwards, stayWithinRange is false. + // Here when we don't have any character to move and we are going backwards, we traverse to the previous node. + if (!lastLength && toNodeEnd && !stayWithinRange) { + if (Node* preNode = previousNode(currentNode)) + return traverseToOffsetInRange(rangeForNodeContents(preNode), offset, option); + return CharacterOffset(); + } + } + } + + // Sometimes text contents in a node are splitted into several iterations, so that iterator.range()->startOffset() + // might not be the correct character count. Here we use a previousNode object to keep track of that. + Node* previousNode = nullptr; + for (; !iterator.atEnd(); iterator.advance()) { + int currentLength = iterator.text().length(); + bool hasReplacedNodeOrBR = false; + + Node& node = iterator.range()->startContainer(); + currentNode = &node; + + // When currentLength == 0, we check if there's any replaced node. + // If not, we skip the node with no length. + if (!currentLength) { + int subOffset = iterator.range()->startOffset(); + Node* childNode = node.traverseToChildAt(subOffset); + if (AccessibilityObject::replacedNodeNeedsCharacter(childNode)) { + cumulativeOffset++; + currentLength++; + currentNode = childNode; + hasReplacedNodeOrBR = true; + } else + continue; + } else { + // Ignore space, new line, tag node. + if (currentLength == 1) { + if (isSpaceOrNewline(iterator.text()[0])) { + // If the node has BR tag, we want to set the currentNode to it. + int subOffset = iterator.range()->startOffset(); + Node* childNode = node.traverseToChildAt(subOffset); + if (childNode && childNode->renderer() && childNode->renderer()->isBR()) { + currentNode = childNode; + hasReplacedNodeOrBR = true; + } else if (currentNode->isShadowRoot()) { + // Since we are entering text controls, we should set the currentNode + // to be the shadow host when there's no content. + currentNode = currentNode->shadowHost(); + continue; + } else if (currentNode != previousNode) { + // We should set the start offset and length for the current node in case this is the last iteration. + lastStartOffset = 1; + lastLength = 0; + continue; + } + } + } + cumulativeOffset += currentLength; + } + + if (currentNode == previousNode) { + lastLength += currentLength; + lastStartOffset = iterator.range()->endOffset() - lastLength; + } + else { + lastLength = currentLength; + lastStartOffset = hasReplacedNodeOrBR ? 0 : iterator.range()->startOffset(); + } + + // Break early if we have advanced enough characters. + bool offsetLimitReached = validateOffset ? cumulativeOffset + lastStartOffset >= offset : cumulativeOffset >= offset; + if (!toNodeEnd && offsetLimitReached) { + offsetInCharacter = validateOffset ? std::max(offset - lastStartOffset, 0) : offset - (cumulativeOffset - lastLength); + finished = true; + break; + } + previousNode = currentNode; + } + + if (!finished) { + offsetInCharacter = lastLength; + if (!toNodeEnd) + remaining = offset - cumulativeOffset; + } + + // Sometimes when we are getting the end CharacterOffset of a line range, the TextIterator will emit an extra space at the end + // and make the character count greater than the Range's end offset. + if (toNodeEnd && currentNode->isTextNode() && currentNode == &range->endContainer() && static_cast<int>(range->endOffset()) < lastStartOffset + offsetInCharacter) + offsetInCharacter = range->endOffset() - lastStartOffset; + + return CharacterOffset(currentNode, lastStartOffset, offsetInCharacter, remaining); +} + +int AXObjectCache::lengthForRange(Range* range) +{ + if (!range) + return -1; + + int length = 0; + for (TextIterator it(range); !it.atEnd(); it.advance()) { + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.text().length()) + length += it.text().length(); + else { + // locate the node and starting offset for this replaced range + Node& node = it.range()->startContainer(); + int offset = it.range()->startOffset(); + if (AccessibilityObject::replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + ++length; + } + } + + return length; +} + +RefPtr<Range> AXObjectCache::rangeForNodeContents(Node* node) +{ + if (!node) + return nullptr; + Document* document = &node->document(); + if (!document) + return nullptr; + auto range = Range::create(*document); + if (AccessibilityObject::replacedNodeNeedsCharacter(node)) { + // For replaced nodes without children, the node itself is included in the range. + if (range->selectNode(*node).hasException()) + return nullptr; + } else { + if (range->selectNodeContents(*node).hasException()) + return nullptr; + } + return WTFMove(range); +} + +static bool isReplacedNodeOrBR(Node* node) +{ + return node && (AccessibilityObject::replacedNodeNeedsCharacter(node) || node->hasTagName(brTag)); +} + +static bool characterOffsetsInOrder(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2) +{ + if (characterOffset1.isNull() || characterOffset2.isNull()) + return false; + + if (characterOffset1.node == characterOffset2.node) + return characterOffset1.offset <= characterOffset2.offset; + + Node* node1 = characterOffset1.node; + Node* node2 = characterOffset2.node; + if (!node1->offsetInCharacters() && !isReplacedNodeOrBR(node1) && node1->hasChildNodes()) + node1 = node1->traverseToChildAt(characterOffset1.offset); + if (!node2->offsetInCharacters() && !isReplacedNodeOrBR(node2) && node2->hasChildNodes()) + node2 = node2->traverseToChildAt(characterOffset2.offset); + + if (!node1 || !node2) + return false; + + RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(node1); + RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(node2); + + if (!range2) + return true; + if (!range1) + return false; + auto result = range1->compareBoundaryPoints(Range::START_TO_START, *range2); + if (result.hasException()) + return true; + return result.releaseReturnValue() <= 0; +} + +static Node* resetNodeAndOffsetForReplacedNode(Node* replacedNode, int& offset, int characterCount) +{ + // Use this function to include the replaced node itself in the range we are creating. + if (!replacedNode) + return nullptr; + + RefPtr<Range> nodeRange = AXObjectCache::rangeForNodeContents(replacedNode); + int nodeLength = TextIterator::rangeLength(nodeRange.get()); + offset = characterCount <= nodeLength ? replacedNode->computeNodeIndex() : replacedNode->computeNodeIndex() + 1; + return replacedNode->parentNode(); +} + +static bool setRangeStartOrEndWithCharacterOffset(Range& range, const CharacterOffset& characterOffset, bool isStart) +{ + if (characterOffset.isNull()) + return false; + + int offset = characterOffset.startIndex + characterOffset.offset; + Node* node = characterOffset.node; + ASSERT(node); + + bool replacedNodeOrBR = isReplacedNodeOrBR(node); + // For the non text node that has no children, we should create the range with its parent, otherwise the range would be collapsed. + // Example: <div contenteditable="true"></div>, we want the range to include the div element. + bool noChildren = !replacedNodeOrBR && !node->isTextNode() && !node->hasChildNodes(); + int characterCount = noChildren ? (isStart ? 0 : 1) : characterOffset.offset; - if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) + if (replacedNodeOrBR || noChildren) + node = resetNodeAndOffsetForReplacedNode(node, offset, characterCount); + + if (!node) + return false; + + if (isStart) { + if (range.setStart(*node, offset).hasException()) + return false; + } else { + if (range.setEnd(*node, offset).hasException()) + return false; + } + + return true; +} + +RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2) +{ + if (characterOffset1.isNull() || characterOffset2.isNull()) + return nullptr; + + bool alreadyInOrder = characterOffsetsInOrder(characterOffset1, characterOffset2); + CharacterOffset startCharacterOffset = alreadyInOrder ? characterOffset1 : characterOffset2; + CharacterOffset endCharacterOffset = alreadyInOrder ? characterOffset2 : characterOffset1; + + auto result = Range::create(m_document); + if (!setRangeStartOrEndWithCharacterOffset(result, startCharacterOffset, true)) + return nullptr; + if (!setRangeStartOrEndWithCharacterOffset(result, endCharacterOffset, false)) + return nullptr; + return WTFMove(result); +} + +void AXObjectCache::setTextMarkerDataWithCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return; + + Node* domNode = characterOffset.node; + if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) { + textMarkerData.ignored = true; + return; + } + + RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode); + if (!obj) + return; + + // Convert to visible position. + VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(characterOffset); + int vpOffset = 0; + if (!visiblePosition.isNull()) { + Position deepPos = visiblePosition.deepEquivalent(); + vpOffset = deepPos.deprecatedEditingOffset(); + } + + textMarkerData.axID = obj.get()->axObjectID(); + textMarkerData.node = domNode; + textMarkerData.characterOffset = characterOffset.offset; + textMarkerData.characterStartIndex = characterOffset.startIndex; + textMarkerData.offset = vpOffset; + textMarkerData.affinity = visiblePosition.affinity(); + + this->setNodeInUse(domNode); +} + +CharacterOffset AXObjectCache::startOrEndCharacterOffsetForRange(RefPtr<Range> range, bool isStart) +{ + if (!range) + return CharacterOffset(); + + // When getting the end CharacterOffset at node boundary, we don't want to collapse to the previous node. + if (!isStart && !range->endOffset()) + return characterOffsetForNodeAndOffset(range->endContainer(), 0, TraverseOptionIncludeStart); + + // If it's end text marker, we want to go to the end of the range, and stay within the range. + bool stayWithinRange = !isStart; + + Node& endNode = range->endContainer(); + if (endNode.offsetInCharacters() && !isStart) + return traverseToOffsetInRange(rangeForNodeContents(&endNode), range->endOffset(), TraverseOptionValidateOffset); + + Ref<Range> copyRange = *range; + // Change the start of the range, so the character offset starts from node beginning. + int offset = 0; + Node& node = copyRange->startContainer(); + if (node.offsetInCharacters()) { + CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(&node), range->startOffset(), TraverseOptionValidateOffset); + if (isStart) + return nodeStartOffset; + copyRange = Range::create(range->ownerDocument(), &range->startContainer(), 0, &range->endContainer(), range->endOffset()); + offset += nodeStartOffset.offset; + } + + return traverseToOffsetInRange(WTFMove(copyRange), offset, isStart ? TraverseOptionDefault : TraverseOptionToNodeEnd, stayWithinRange); +} + +void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart) +{ + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + + CharacterOffset characterOffset = startOrEndCharacterOffsetForRange(range, isStart); + if (characterOffset.isNull()) + return; + + setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset); +} + +CharacterOffset AXObjectCache::characterOffsetForNodeAndOffset(Node& node, int offset, TraverseOption option) +{ + Node* domNode = &node; + if (!domNode) + return CharacterOffset(); + + bool toNodeEnd = option & TraverseOptionToNodeEnd; + bool includeStart = option & TraverseOptionIncludeStart; + + // ignoreStart is used to determine if we should go to previous node or + // stay in current node when offset is 0. + if (!toNodeEnd && (offset < 0 || (!offset && !includeStart))) { + // Set the offset to the amount of characters we need to go backwards. + offset = - offset; + CharacterOffset charOffset = CharacterOffset(); + while (offset >= 0 && charOffset.offset <= offset) { + offset -= charOffset.offset; + domNode = previousNode(domNode); + if (domNode) { + charOffset = characterOffsetForNodeAndOffset(*domNode, 0, TraverseOptionToNodeEnd); + } else + return CharacterOffset(); + if (charOffset.offset == offset) + break; + } + if (offset > 0) + charOffset = characterOffsetForNodeAndOffset(*charOffset.node, charOffset.offset - offset, TraverseOptionIncludeStart); + return charOffset; + } + + RefPtr<Range> range = rangeForNodeContents(domNode); + + // Traverse the offset amount of characters forward and see if there's remaining offsets. + // Keep traversing to the next node when there's remaining offsets. + CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, option); + while (!characterOffset.isNull() && characterOffset.remaining() && !toNodeEnd) { + domNode = nextNode(domNode); + if (!domNode) + return CharacterOffset(); + range = rangeForNodeContents(domNode); + characterOffset = traverseToOffsetInRange(range, characterOffset.remaining(), option); + } + + return characterOffset; +} + +void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset); +} + +bool AXObjectCache::shouldSkipBoundary(const CharacterOffset& previous, const CharacterOffset& next) +{ + // Match the behavior of VisiblePosition, we should skip the node boundary when there's no visual space or new line character. + if (previous.isNull() || next.isNull()) + return false; + + if (previous.node == next.node) + return false; + + if (next.startIndex > 0 || next.offset > 0) + return false; + + CharacterOffset newLine = startCharacterOffsetOfLine(next); + if (next.isEqual(newLine)) + return false; + + return true; +} + +void AXObjectCache::textMarkerDataForNextCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + CharacterOffset next = characterOffset; + CharacterOffset previous = characterOffset; + bool shouldContinue; + do { + shouldContinue = false; + next = nextCharacterOffset(next, false); + if (shouldSkipBoundary(previous, next)) + next = nextCharacterOffset(next, false); + textMarkerDataForCharacterOffset(textMarkerData, next); + + // We should skip next CharactetOffset if it's visually the same. + if (!lengthForRange(rangeForUnorderedCharacterOffsets(previous, next).get())) + shouldContinue = true; + previous = next; + } while (textMarkerData.ignored || shouldContinue); +} + +void AXObjectCache::textMarkerDataForPreviousCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + CharacterOffset previous = characterOffset; + CharacterOffset next = characterOffset; + bool shouldContinue; + do { + shouldContinue = false; + previous = previousCharacterOffset(previous, false); + textMarkerDataForCharacterOffset(textMarkerData, previous); + + // We should skip previous CharactetOffset if it's visually the same. + if (!lengthForRange(rangeForUnorderedCharacterOffsets(previous, next).get())) + shouldContinue = true; + next = previous; + } while (textMarkerData.ignored || shouldContinue); +} + +Node* AXObjectCache::nextNode(Node* node) const +{ + if (!node) + return nullptr; + + return NodeTraversal::nextSkippingChildren(*node); +} + +Node* AXObjectCache::previousNode(Node* node) const +{ + if (!node) + return nullptr; + + // First child of body shouldn't have previous node. + if (node->parentNode() && node->parentNode()->renderer() && node->parentNode()->renderer()->isBody() && !node->previousSibling()) + return nullptr; + + return NodeTraversal::previousSkippingChildren(*node); +} + +VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) return VisiblePosition(); - return visiblePos; + // Create a collapsed range and use that to form a VisiblePosition, so that the case with + // composed characters will be covered. + auto range = rangeForUnorderedCharacterOffsets(characterOffset, characterOffset); + return range ? VisiblePosition(range->startPosition()) : VisiblePosition(); +} + +CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(const VisiblePosition& visiblePos) +{ + if (visiblePos.isNull()) + return CharacterOffset(); + + Position deepPos = visiblePos.deepEquivalent(); + Node* domNode = deepPos.deprecatedNode(); + ASSERT(domNode); + + if (domNode->offsetInCharacters()) + return traverseToOffsetInRange(rangeForNodeContents(domNode), deepPos.deprecatedEditingOffset(), TraverseOptionValidateOffset); + + RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode); + if (!obj) + return CharacterOffset(); + + // Use nextVisiblePosition to calculate how many characters we need to traverse to the current position. + VisiblePositionRange visiblePositionRange = obj->visiblePositionRange(); + VisiblePosition visiblePosition = visiblePositionRange.start; + int characterOffset = 0; + Position currentPosition = visiblePosition.deepEquivalent(); + + VisiblePosition previousVisiblePos; + while (!currentPosition.isNull() && !deepPos.equals(currentPosition)) { + previousVisiblePos = visiblePosition; + visiblePosition = obj->nextVisiblePosition(visiblePosition); + currentPosition = visiblePosition.deepEquivalent(); + Position previousPosition = previousVisiblePos.deepEquivalent(); + // Sometimes nextVisiblePosition will give the same VisiblePostion, + // we break here to avoid infinite loop. + if (currentPosition.equals(previousPosition)) + break; + characterOffset++; + + // When VisiblePostion moves to next node, it will count the leading line break as + // 1 offset, which we shouldn't include in CharacterOffset. + if (currentPosition.deprecatedNode() != previousPosition.deprecatedNode()) { + if (visiblePosition.characterBefore() == '\n') + characterOffset--; + } else { + // Sometimes VisiblePosition will move multiple characters, like emoji. + if (currentPosition.deprecatedNode()->offsetInCharacters()) + characterOffset += currentPosition.offsetInContainerNode() - previousPosition.offsetInContainerNode() - 1; + } + } + + // Sometimes when the node is a replaced node and is ignored in accessibility, we get a wrong CharacterOffset from it. + CharacterOffset result = traverseToOffsetInRange(rangeForNodeContents(obj->node()), characterOffset); + if (result.remainingOffset > 0 && !result.isNull() && isRendererReplacedElement(result.node->renderer())) + result.offset += result.remainingOffset; + return result; +} + +AccessibilityObject* AXObjectCache::accessibilityObjectForTextMarkerData(TextMarkerData& textMarkerData) +{ + if (!isNodeInUse(textMarkerData.node)) + return nullptr; + + Node* domNode = textMarkerData.node; + return this->getOrCreate(domNode); } void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) @@ -966,10 +2094,15 @@ void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerD if (!domNode) return; - if (domNode->isHTMLElement()) { - HTMLInputElement* inputElement = domNode->toInputElement(); - if (inputElement && inputElement->isPasswordField()) - return; + if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) + return; + + // If the visible position has an anchor type referring to a node other than the anchored node, we should + // set the text marker data with CharacterOffset so that the offset will correspond to the node. + CharacterOffset characterOffset = characterOffsetFromVisiblePosition(visiblePos); + if (deepPos.anchorType() == Position::PositionIsAfterAnchor || deepPos.anchorType() == Position::PositionIsAfterChildren) { + textMarkerDataForCharacterOffset(textMarkerData, characterOffset); + return; } // find or create an accessibility object for this node @@ -979,15 +2112,535 @@ void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerD textMarkerData.axID = obj.get()->axObjectID(); textMarkerData.node = domNode; textMarkerData.offset = deepPos.deprecatedEditingOffset(); - textMarkerData.affinity = visiblePos.affinity(); + textMarkerData.affinity = visiblePos.affinity(); + + textMarkerData.characterOffset = characterOffset.offset; + textMarkerData.characterStartIndex = characterOffset.startIndex; cache->setNodeInUse(domNode); } +CharacterOffset AXObjectCache::nextCharacterOffset(const CharacterOffset& characterOffset, bool ignoreNextNodeStart) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + // We don't always move one 'character' at a time since there might be composed characters. + int nextOffset = Position::uncheckedNextOffset(characterOffset.node, characterOffset.offset); + CharacterOffset next = characterOffsetForNodeAndOffset(*characterOffset.node, nextOffset); + + // To be consistent with VisiblePosition, we should consider the case that current node end to next node start counts 1 offset. + if (!ignoreNextNodeStart && !next.isNull() && !isReplacedNodeOrBR(next.node) && next.node != characterOffset.node) { + int length = TextIterator::rangeLength(rangeForUnorderedCharacterOffsets(characterOffset, next).get()); + if (length > nextOffset - characterOffset.offset) + next = characterOffsetForNodeAndOffset(*next.node, 0, TraverseOptionIncludeStart); + } + + return next; +} + +CharacterOffset AXObjectCache::previousCharacterOffset(const CharacterOffset& characterOffset, bool ignorePreviousNodeEnd) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + // To be consistent with VisiblePosition, we should consider the case that current node start to previous node end counts 1 offset. + if (!ignorePreviousNodeEnd && !characterOffset.offset) + return characterOffsetForNodeAndOffset(*characterOffset.node, 0); + + // We don't always move one 'character' a time since there might be composed characters. + int previousOffset = Position::uncheckedPreviousOffset(characterOffset.node, characterOffset.offset); + return characterOffsetForNodeAndOffset(*characterOffset.node, previousOffset, TraverseOptionIncludeStart); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfWord(const CharacterOffset& characterOffset, EWordSide side) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset c = characterOffset; + if (side == RightWordIfOnBoundary) { + CharacterOffset endOfParagraph = endCharacterOffsetOfParagraph(c); + if (c.isEqual(endOfParagraph)) + return c; + + // We should consider the node boundary that splits words. Otherwise VoiceOver won't see it as space. + c = nextCharacterOffset(characterOffset, false); + if (shouldSkipBoundary(characterOffset, c)) + c = nextCharacterOffset(c, false); + if (c.isNull()) + return characterOffset; + } + + return previousBoundary(c, startWordBoundary); +} + +CharacterOffset AXObjectCache::endCharacterOffsetOfWord(const CharacterOffset& characterOffset, EWordSide side) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset c = characterOffset; + if (side == LeftWordIfOnBoundary) { + CharacterOffset startOfParagraph = startCharacterOffsetOfParagraph(c); + if (c.isEqual(startOfParagraph)) + return c; + + c = previousCharacterOffset(characterOffset); + if (c.isNull()) + return characterOffset; + } else { + CharacterOffset endOfParagraph = endCharacterOffsetOfParagraph(characterOffset); + if (characterOffset.isEqual(endOfParagraph)) + return characterOffset; + } + + return nextBoundary(c, endWordBoundary); +} + +CharacterOffset AXObjectCache::previousWordStartCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset previousOffset = previousCharacterOffset(characterOffset); + if (previousOffset.isNull()) + return CharacterOffset(); + + return startCharacterOffsetOfWord(previousOffset, RightWordIfOnBoundary); +} + +CharacterOffset AXObjectCache::nextWordEndCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset nextOffset = nextCharacterOffset(characterOffset); + if (nextOffset.isNull()) + return CharacterOffset(); + + return endCharacterOffsetOfWord(nextOffset, LeftWordIfOnBoundary); +} + +RefPtr<Range> AXObjectCache::leftWordRange(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfWord(characterOffset, LeftWordIfOnBoundary); + CharacterOffset end = endCharacterOffsetOfWord(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +RefPtr<Range> AXObjectCache::rightWordRange(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfWord(characterOffset, RightWordIfOnBoundary); + CharacterOffset end = endCharacterOffsetOfWord(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +static UChar32 characterForCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull() || !characterOffset.node->isTextNode()) + return 0; + + UChar32 ch = 0; + unsigned offset = characterOffset.startIndex + characterOffset.offset; + if (offset < characterOffset.node->textContent().length()) + U16_NEXT(characterOffset.node->textContent(), offset, characterOffset.node->textContent().length(), ch); + return ch; +} + +UChar32 AXObjectCache::characterAfter(const CharacterOffset& characterOffset) +{ + return characterForCharacterOffset(nextCharacterOffset(characterOffset)); +} + +UChar32 AXObjectCache::characterBefore(const CharacterOffset& characterOffset) +{ + return characterForCharacterOffset(characterOffset); +} + +static bool characterOffsetNodeIsBR(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return false; + + return characterOffset.node->hasTagName(brTag); +} + +static Node* parentEditingBoundary(Node* node) +{ + if (!node) + return nullptr; + + Node* documentElement = node->document().documentElement(); + if (!documentElement) + return nullptr; + + Node* boundary = node; + while (boundary != documentElement && boundary->nonShadowBoundaryParentNode() && node->hasEditableStyle() == boundary->parentNode()->hasEditableStyle()) + boundary = boundary->nonShadowBoundaryParentNode(); + + return boundary; +} + +CharacterOffset AXObjectCache::nextBoundary(const CharacterOffset& characterOffset, BoundarySearchFunction searchFunction) +{ + if (characterOffset.isNull()) + return { }; + + Node* boundary = parentEditingBoundary(characterOffset.node); + if (!boundary) + return { }; + + RefPtr<Range> searchRange = rangeForNodeContents(boundary); + if (!searchRange) + return { }; + + Vector<UChar, 1024> string; + unsigned prefixLength = 0; + + if (requiresContextForWordBoundary(characterAfter(characterOffset))) { + auto backwardsScanRange = boundary->document().createRange(); + if (!setRangeStartOrEndWithCharacterOffset(backwardsScanRange, characterOffset, false)) + return { }; + prefixLength = prefixLengthForRange(backwardsScanRange, string); + } + + if (!setRangeStartOrEndWithCharacterOffset(*searchRange, characterOffset, true)) + return { }; + CharacterOffset end = startOrEndCharacterOffsetForRange(searchRange, false); + + TextIterator it(searchRange.get(), TextIteratorEmitsObjectReplacementCharacters); + unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction); + + if (it.atEnd() && next == string.size()) + return end; + + // We should consider the node boundary that splits words. + if (searchFunction == endWordBoundary && next - prefixLength == 1) + return nextCharacterOffset(characterOffset, false); + + // The endSentenceBoundary function will include a line break at the end of the sentence. + if (searchFunction == endSentenceBoundary && string[next - 1] == '\n') + next--; + + if (next > prefixLength) + return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset + next - prefixLength); + + return characterOffset; +} + +CharacterOffset AXObjectCache::previousBoundary(const CharacterOffset& characterOffset, BoundarySearchFunction searchFunction) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + Node* boundary = parentEditingBoundary(characterOffset.node); + if (!boundary) + return CharacterOffset(); + + RefPtr<Range> searchRange = rangeForNodeContents(boundary); + Vector<UChar, 1024> string; + unsigned suffixLength = 0; + + if (requiresContextForWordBoundary(characterBefore(characterOffset))) { + auto forwardsScanRange = boundary->document().createRange(); + if (forwardsScanRange->setEndAfter(*boundary).hasException()) + return { }; + if (!setRangeStartOrEndWithCharacterOffset(forwardsScanRange, characterOffset, true)) + return { }; + suffixLength = suffixLengthForRange(forwardsScanRange, string); + } + + if (!setRangeStartOrEndWithCharacterOffset(*searchRange, characterOffset, false)) + return { }; + CharacterOffset start = startOrEndCharacterOffsetForRange(searchRange, true); + + SimplifiedBackwardsTextIterator it(*searchRange); + unsigned next = backwardSearchForBoundaryWithTextIterator(it, string, suffixLength, searchFunction); + + if (!next) + return it.atEnd() ? start : characterOffset; + + Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer(); + + // SimplifiedBackwardsTextIterator ignores replaced elements. + if (AccessibilityObject::replacedNodeNeedsCharacter(characterOffset.node)) + return characterOffsetForNodeAndOffset(*characterOffset.node, 0); + Node* nextSibling = node.nextSibling(); + if (&node != characterOffset.node && AccessibilityObject::replacedNodeNeedsCharacter(nextSibling)) + return startOrEndCharacterOffsetForRange(rangeForNodeContents(nextSibling), false); + + if ((node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) { + // The next variable contains a usable index into a text node + if (node.isTextNode()) + return traverseToOffsetInRange(rangeForNodeContents(&node), next, TraverseOptionValidateOffset); + return characterOffsetForNodeAndOffset(node, next, TraverseOptionIncludeStart); + } + + int characterCount = characterOffset.offset - (string.size() - suffixLength - next); + // We don't want to go to the previous node if the node is at the start of a new line. + if (characterCount < 0 && (characterOffsetNodeIsBR(characterOffset) || string[string.size() - suffixLength - 1] == '\n')) + characterCount = 0; + return characterOffsetForNodeAndOffset(*characterOffset.node, characterCount, TraverseOptionIncludeStart); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfParagraph(const CharacterOffset& characterOffset, EditingBoundaryCrossingRule boundaryCrossingRule) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + auto* startNode = characterOffset.node; + + if (isRenderedAsNonInlineTableImageOrHR(startNode)) + return startOrEndCharacterOffsetForRange(rangeForNodeContents(startNode), true); + + auto* startBlock = enclosingBlock(startNode); + int offset = characterOffset.startIndex + characterOffset.offset; + auto* highestRoot = highestEditableRoot(firstPositionInOrBeforeNode(startNode)); + Position::AnchorType type = Position::PositionIsOffsetInAnchor; + + auto* node = findStartOfParagraph(startNode, highestRoot, startBlock, offset, type, boundaryCrossingRule); + + if (type == Position::PositionIsOffsetInAnchor) + return characterOffsetForNodeAndOffset(*node, offset, TraverseOptionIncludeStart); + + return startOrEndCharacterOffsetForRange(rangeForNodeContents(node), true); +} + +CharacterOffset AXObjectCache::endCharacterOffsetOfParagraph(const CharacterOffset& characterOffset, EditingBoundaryCrossingRule boundaryCrossingRule) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + Node* startNode = characterOffset.node; + if (isRenderedAsNonInlineTableImageOrHR(startNode)) + return startOrEndCharacterOffsetForRange(rangeForNodeContents(startNode), false); + + Node* stayInsideBlock = enclosingBlock(startNode); + int offset = characterOffset.startIndex + characterOffset.offset; + Node* highestRoot = highestEditableRoot(firstPositionInOrBeforeNode(startNode)); + Position::AnchorType type = Position::PositionIsOffsetInAnchor; + + Node* node = findEndOfParagraph(startNode, highestRoot, stayInsideBlock, offset, type, boundaryCrossingRule); + if (type == Position::PositionIsOffsetInAnchor) { + if (node->isTextNode()) { + CharacterOffset startOffset = startOrEndCharacterOffsetForRange(rangeForNodeContents(node), true); + offset -= startOffset.startIndex; + } + return characterOffsetForNodeAndOffset(*node, offset, TraverseOptionIncludeStart); + } + + return startOrEndCharacterOffsetForRange(rangeForNodeContents(node), false); +} + +RefPtr<Range> AXObjectCache::paragraphForCharacterOffset(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfParagraph(characterOffset); + CharacterOffset end = endCharacterOffsetOfParagraph(start); + + return rangeForUnorderedCharacterOffsets(start, end); +} + +CharacterOffset AXObjectCache::nextParagraphEndCharacterOffset(const CharacterOffset& characterOffset) +{ + // make sure we move off of a paragraph end + CharacterOffset next = nextCharacterOffset(characterOffset); + + // We should skip the following BR node. + if (characterOffsetNodeIsBR(next) && !characterOffsetNodeIsBR(characterOffset)) + next = nextCharacterOffset(next); + + return endCharacterOffsetOfParagraph(next); +} + +CharacterOffset AXObjectCache::previousParagraphStartCharacterOffset(const CharacterOffset& characterOffset) +{ + // make sure we move off of a paragraph start + CharacterOffset previous = previousCharacterOffset(characterOffset); + + // We should skip the preceding BR node. + if (characterOffsetNodeIsBR(previous) && !characterOffsetNodeIsBR(characterOffset)) + previous = previousCharacterOffset(previous); + + return startCharacterOffsetOfParagraph(previous); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfSentence(const CharacterOffset& characterOffset) +{ + return previousBoundary(characterOffset, startSentenceBoundary); +} + +CharacterOffset AXObjectCache::endCharacterOffsetOfSentence(const CharacterOffset& characterOffset) +{ + return nextBoundary(characterOffset, endSentenceBoundary); +} + +RefPtr<Range> AXObjectCache::sentenceForCharacterOffset(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfSentence(characterOffset); + CharacterOffset end = endCharacterOffsetOfSentence(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +CharacterOffset AXObjectCache::nextSentenceEndCharacterOffset(const CharacterOffset& characterOffset) +{ + // Make sure we move off of a sentence end. + return endCharacterOffsetOfSentence(nextCharacterOffset(characterOffset)); +} + +CharacterOffset AXObjectCache::previousSentenceStartCharacterOffset(const CharacterOffset& characterOffset) +{ + // Make sure we move off of a sentence start. + CharacterOffset previous = previousCharacterOffset(characterOffset); + + // We should skip the preceding BR node. + if (characterOffsetNodeIsBR(previous) && !characterOffsetNodeIsBR(characterOffset)) + previous = previousCharacterOffset(previous); + + return startCharacterOffsetOfSentence(previous); +} + +LayoutRect AXObjectCache::localCaretRectForCharacterOffset(RenderObject*& renderer, const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) { + renderer = nullptr; + return IntRect(); + } + + Node* node = characterOffset.node; + + renderer = node->renderer(); + if (!renderer) + return LayoutRect(); + + InlineBox* inlineBox = nullptr; + int caretOffset; + // Use a collapsed range to get the position. + RefPtr<Range> range = rangeForUnorderedCharacterOffsets(characterOffset, characterOffset); + if (!range) + return IntRect(); + + Position startPosition = range->startPosition(); + startPosition.getInlineBoxAndOffset(DOWNSTREAM, inlineBox, caretOffset); + + if (inlineBox) + renderer = &inlineBox->renderer(); + + if (is<RenderLineBreak>(renderer) && downcast<RenderLineBreak>(renderer)->inlineBoxWrapper() != inlineBox) + return IntRect(); + + return renderer->localCaretRect(inlineBox, caretOffset); +} + +IntRect AXObjectCache::absoluteCaretBoundsForCharacterOffset(const CharacterOffset& characterOffset) +{ + RenderBlock* caretPainter = nullptr; + + // First compute a rect local to the renderer at the selection start. + RenderObject* renderer = nullptr; + LayoutRect localRect = localCaretRectForCharacterOffset(renderer, characterOffset); + + localRect = localCaretRectInRendererForRect(localRect, characterOffset.node, renderer, caretPainter); + return absoluteBoundsForLocalCaretRect(caretPainter, localRect); +} + +CharacterOffset AXObjectCache::characterOffsetForPoint(const IntPoint &point, AccessibilityObject* obj) +{ + if (!obj) + return CharacterOffset(); + + VisiblePosition vp = obj->visiblePositionForPoint(point); + RefPtr<Range> range = makeRange(vp, vp); + return startOrEndCharacterOffsetForRange(range, true); +} + +CharacterOffset AXObjectCache::characterOffsetForPoint(const IntPoint &point) +{ + RefPtr<Range> caretRange = m_document.caretRangeFromPoint(LayoutPoint(point)); + return startOrEndCharacterOffsetForRange(caretRange, true); +} + +CharacterOffset AXObjectCache::characterOffsetForBounds(const IntRect& rect, bool first) +{ + if (rect.isEmpty()) + return CharacterOffset(); + + IntPoint corner = first ? rect.minXMinYCorner() : rect.maxXMaxYCorner(); + CharacterOffset characterOffset = characterOffsetForPoint(corner); + + if (rect.contains(absoluteCaretBoundsForCharacterOffset(characterOffset).center())) + return characterOffset; + + // If the initial position is located outside the bounds adjust it incrementally as needed. + CharacterOffset nextCharOffset = nextCharacterOffset(characterOffset, false); + CharacterOffset previousCharOffset = previousCharacterOffset(characterOffset, false); + while (!nextCharOffset.isNull() || !previousCharOffset.isNull()) { + if (rect.contains(absoluteCaretBoundsForCharacterOffset(nextCharOffset).center())) + return nextCharOffset; + if (rect.contains(absoluteCaretBoundsForCharacterOffset(previousCharOffset).center())) + return previousCharOffset; + + nextCharOffset = nextCharacterOffset(nextCharOffset, false); + previousCharOffset = previousCharacterOffset(previousCharOffset, false); + } + + return CharacterOffset(); +} + +// FIXME: Remove VisiblePosition code after implementing this using CharacterOffset. +CharacterOffset AXObjectCache::endCharacterOffsetOfLine(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + VisiblePosition vp = visiblePositionFromCharacterOffset(characterOffset); + VisiblePosition endLine = endOfLine(vp); + + return characterOffsetFromVisiblePosition(endLine); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfLine(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + VisiblePosition vp = visiblePositionFromCharacterOffset(characterOffset); + VisiblePosition startLine = startOfLine(vp); + + return characterOffsetFromVisiblePosition(startLine); +} + +CharacterOffset AXObjectCache::characterOffsetForIndex(int index, const AccessibilityObject* obj) +{ + if (!obj) + return CharacterOffset(); + + RefPtr<Range> range = obj->elementRange(); + CharacterOffset start = startOrEndCharacterOffsetForRange(range, true); + CharacterOffset end = startOrEndCharacterOffsetForRange(range, false); + CharacterOffset result = start; + for (int i = 0; i < index; i++) { + result = nextCharacterOffset(result, false); + if (result.isEqual(end)) + break; + } + return result; +} + +int AXObjectCache::indexForCharacterOffset(const CharacterOffset& characterOffset, AccessibilityObject* obj) +{ + // Create a collapsed range so that we can get the VisiblePosition from it. + RefPtr<Range> range = rangeForUnorderedCharacterOffsets(characterOffset, characterOffset); + if (!range) + return 0; + VisiblePosition vp = range->startPosition(); + return obj->indexForVisiblePosition(vp); +} + const Element* AXObjectCache::rootAXEditableElement(const Node* node) { const Element* result = node->rootEditableElement(); - const Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); + const Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); for (; element; element = element->parentElement()) { if (nodeIsTextControl(element)) @@ -999,20 +2652,18 @@ const Element* AXObjectCache::rootAXEditableElement(const Node* node) void AXObjectCache::clearTextMarkerNodesInUse(Document* document) { - HashSet<Node*>::iterator it = m_textMarkerNodes.begin(); - HashSet<Node*>::iterator end = m_textMarkerNodes.end(); - - // Check each node to see if it's inside the document being deleted. + if (!document) + return; + + // Check each node to see if it's inside the document being deleted, of if it no longer belongs to a document. HashSet<Node*> nodesToDelete; - for (; it != end; ++it) { - if (&(*it)->document() == document) - nodesToDelete.add(*it); + for (const auto& node : m_textMarkerNodes) { + if (!node->isConnected() || &(node)->document() == document) + nodesToDelete.add(node); } - it = nodesToDelete.begin(); - end = nodesToDelete.end(); - for (; it != end; ++it) - m_textMarkerNodes.remove(*it); + for (const auto& node : nodesToDelete) + m_textMarkerNodes.remove(node); } bool AXObjectCache::nodeIsTextControl(const Node* node) @@ -1024,24 +2675,59 @@ bool AXObjectCache::nodeIsTextControl(const Node* node) return axObject && axObject->isTextControl(); } +void AXObjectCache::performDeferredIsIgnoredChange() +{ + for (auto* renderer : m_deferredIsIgnoredChangeList) + recomputeIsIgnored(renderer); + m_deferredIsIgnoredChangeList.clear(); +} + +void AXObjectCache::recomputeDeferredIsIgnored(RenderBlock& renderer) +{ + if (renderer.beingDestroyed()) + return; + m_deferredIsIgnoredChangeList.add(&renderer); +} + bool isNodeAriaVisible(Node* node) { if (!node) return false; - - // To determine if a node is ARIA visible, we need to check the parent hierarchy to see if anyone specifies - // aria-hidden explicitly. + + // ARIA Node visibility is controlled by aria-hidden + // 1) if aria-hidden=true, the whole subtree is hidden + // 2) if aria-hidden=false, and the object is rendered, there's no effect + // 3) if aria-hidden=false, and the object is NOT rendered, then it must have + // aria-hidden=false on each parent until it gets to a rendered object + // 3b) a text node inherits a parents aria-hidden value + bool requiresAriaHiddenFalse = !node->renderer(); + bool ariaHiddenFalsePresent = false; for (Node* testNode = node; testNode; testNode = testNode->parentNode()) { - if (testNode->isElementNode()) { - const AtomicString& ariaHiddenValue = toElement(testNode)->fastGetAttribute(aria_hiddenAttr); - if (equalIgnoringCase(ariaHiddenValue, "false")) - return true; - if (equalIgnoringCase(ariaHiddenValue, "true")) + if (is<Element>(*testNode)) { + const AtomicString& ariaHiddenValue = downcast<Element>(*testNode).attributeWithoutSynchronization(aria_hiddenAttr); + if (equalLettersIgnoringASCIICase(ariaHiddenValue, "true")) + return false; + + bool ariaHiddenFalse = equalLettersIgnoringASCIICase(ariaHiddenValue, "false"); + if (!testNode->renderer() && !ariaHiddenFalse) return false; + if (!ariaHiddenFalsePresent && ariaHiddenFalse) + ariaHiddenFalsePresent = true; + // We should break early when it gets to a rendered object. + if (testNode->renderer()) + break; } } - return false; + return !requiresAriaHiddenFalse || ariaHiddenFalsePresent; +} + +AccessibilityObject* AXObjectCache::rootWebArea() +{ + AccessibilityObject* rootObject = this->rootObject(); + if (!rootObject || !rootObject->isAccessibilityScrollView()) + return nullptr; + return downcast<AccessibilityScrollView>(*rootObject).webAreaObject(); } AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache) @@ -1056,6 +2742,28 @@ AXAttributeCacheEnabler::~AXAttributeCacheEnabler() if (m_cache) m_cache->stopCachingComputedObjectAttributes(); } + +#if !PLATFORM(COCOA) +AXTextChange AXObjectCache::textChangeForEditType(AXTextEditType type) +{ + switch (type) { + case AXTextEditTypeCut: + case AXTextEditTypeDelete: + return AXTextDeleted; + case AXTextEditTypeInsert: + case AXTextEditTypeDictation: + case AXTextEditTypeTyping: + case AXTextEditTypePaste: + return AXTextInserted; + case AXTextEditTypeAttributesChange: + return AXTextAttributesChanged; + case AXTextEditTypeUnknown: + break; + } + ASSERT_NOT_REACHED(); + return AXTextInserted; +} +#endif } // namespace WebCore diff --git a/Source/WebCore/accessibility/AXObjectCache.h b/Source/WebCore/accessibility/AXObjectCache.h index cf5fd5795..4f2bc6d9c 100644 --- a/Source/WebCore/accessibility/AXObjectCache.h +++ b/Source/WebCore/accessibility/AXObjectCache.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. + * Copyright (C) 2003, 2006, 2007, 2008, 2009, 2010, 2011, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR @@ -23,15 +23,18 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AXObjectCache_h -#define AXObjectCache_h +#pragma once +#include "AXTextStateChangeIntent.h" #include "AccessibilityObject.h" +#include "Range.h" #include "Timer.h" +#include "VisibleUnits.h" #include <limits.h> #include <wtf/Forward.h> #include <wtf/HashMap.h> #include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> #include <wtf/RefPtr.h> namespace WebCore { @@ -40,6 +43,7 @@ class Document; class HTMLAreaElement; class Node; class Page; +class RenderBlock; class RenderObject; class ScrollView; class VisiblePosition; @@ -49,9 +53,35 @@ struct TextMarkerData { AXID axID; Node* node; int offset; + int characterStartIndex; + int characterOffset; + bool ignored; EAffinity affinity; }; +struct CharacterOffset { + Node* node; + int startIndex; + int offset; + int remainingOffset; + + CharacterOffset(Node* n = nullptr, int startIndex = 0, int offset = 0, int remaining = 0) + : node(n) + , startIndex(startIndex) + , offset(offset) + , remainingOffset(remaining) + { } + + int remaining() const { return remainingOffset; } + bool isNull() const { return !node; } + bool isEqual(CharacterOffset& other) const + { + if (isNull() || other.isNull()) + return false; + return node == other.node && startIndex == other.startIndex && offset == other.offset; + } +}; + class AXComputedObjectAttributeCache { public: AccessibilityObjectInclusion getIgnored(AXID) const; @@ -67,6 +97,32 @@ private: HashMap<AXID, CachedAXObjectAttributes> m_idMapping; }; +struct VisiblePositionIndex { + int value = -1; + RefPtr<ContainerNode> scope; +}; + +struct VisiblePositionIndexRange { + VisiblePositionIndex startIndex; + VisiblePositionIndex endIndex; + bool isNull() const { return startIndex.value == -1 || endIndex.value == -1; } +}; + +class AccessibilityReplacedText { +public: + AccessibilityReplacedText() { } + AccessibilityReplacedText(const VisibleSelection&); + void postTextStateChangeNotification(AXObjectCache*, AXTextEditType, const String&, const VisibleSelection&); + const VisiblePositionIndexRange& replacedRange() { return m_replacedRange; } +protected: + String m_replacedText; + VisiblePositionIndexRange m_replacedRange; +}; + +#if !PLATFORM(COCOA) +enum AXTextChange { AXTextInserted, AXTextDeleted, AXTextAttributesChanged }; +#endif + enum PostTarget { TargetElement, TargetObservableParent }; enum PostType { PostSynchronously, PostAsynchronously }; @@ -77,12 +133,12 @@ public: explicit AXObjectCache(Document&); ~AXObjectCache(); - static AccessibilityObject* focusedUIElementForPage(const Page*); + WEBCORE_EXPORT static AccessibilityObject* focusedUIElementForPage(const Page*); // Returns the root object for the entire document. - AccessibilityObject* rootObject(); + WEBCORE_EXPORT AccessibilityObject* rootObject(); // Returns the root object for a specific frame. - AccessibilityObject* rootObjectForFrame(Frame*); + WEBCORE_EXPORT AccessibilityObject* rootObjectForFrame(Frame*); // For AX objects with elements that back them. AccessibilityObject* getOrCreate(RenderObject*); @@ -104,7 +160,7 @@ public: void detachWrapper(AccessibilityObject*, AccessibilityDetachmentType); void attachWrapper(AccessibilityObject*); - void childrenChanged(Node*); + void childrenChanged(Node*, Node* newChild = nullptr); void childrenChanged(RenderObject*, RenderObject* newChild = nullptr); void childrenChanged(AccessibilityObject*); void checkedStateChanged(Node*); @@ -122,16 +178,19 @@ public: void handleScrolledToAnchor(const Node* anchorNode); void handleAriaExpandedChange(Node*); void handleScrollbarUpdate(ScrollView*); + + void handleAriaModalChange(Node*); + Node* ariaModalNode(); void handleAttributeChanged(const QualifiedName& attrName, Element*); void recomputeIsIgnored(RenderObject* renderer); #if HAVE(ACCESSIBILITY) - static void enableAccessibility(); - static void disableAccessibility(); + WEBCORE_EXPORT static void enableAccessibility(); + WEBCORE_EXPORT static void disableAccessibility(); // Enhanced user interface accessibility can be toggled by the assistive technology. - static void setEnhancedUserInterfaceAccessibility(bool flag) { gAccessibilityEnhancedUserInterfaceEnabled = flag; } + WEBCORE_EXPORT static void setEnhancedUserInterfaceAccessibility(bool flag); static bool accessibilityEnabled() { return gAccessibilityEnabled; } static bool accessibilityEnhancedUserInterfaceEnabled() { return gAccessibilityEnhancedUserInterfaceEnabled; } @@ -146,7 +205,6 @@ public: void removeAXID(AccessibilityObject*); bool isIDinUse(AXID id) const { return m_idsInUse.contains(id); } - Element* rootAXEditableElement(Node*); const Element* rootAXEditableElement(const Node*); bool nodeIsTextControl(const Node*); @@ -155,7 +213,49 @@ public: // Text marker utilities. void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&); + void textMarkerDataForCharacterOffset(TextMarkerData&, const CharacterOffset&); + void textMarkerDataForNextCharacterOffset(TextMarkerData&, const CharacterOffset&); + void textMarkerDataForPreviousCharacterOffset(TextMarkerData&, const CharacterOffset&); VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&); + CharacterOffset characterOffsetForTextMarkerData(TextMarkerData&); + // Use ignoreNextNodeStart/ignorePreviousNodeEnd to determine the behavior when we are at node boundary. + CharacterOffset nextCharacterOffset(const CharacterOffset&, bool ignoreNextNodeStart = true); + CharacterOffset previousCharacterOffset(const CharacterOffset&, bool ignorePreviousNodeEnd = true); + void startOrEndTextMarkerDataForRange(TextMarkerData&, RefPtr<Range>, bool); + CharacterOffset startOrEndCharacterOffsetForRange(RefPtr<Range>, bool); + AccessibilityObject* accessibilityObjectForTextMarkerData(TextMarkerData&); + RefPtr<Range> rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&); + static RefPtr<Range> rangeForNodeContents(Node*); + static int lengthForRange(Range*); + + // Word boundary + CharacterOffset nextWordEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousWordStartCharacterOffset(const CharacterOffset&); + RefPtr<Range> leftWordRange(const CharacterOffset&); + RefPtr<Range> rightWordRange(const CharacterOffset&); + + // Paragraph + RefPtr<Range> paragraphForCharacterOffset(const CharacterOffset&); + CharacterOffset nextParagraphEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousParagraphStartCharacterOffset(const CharacterOffset&); + + // Sentence + RefPtr<Range> sentenceForCharacterOffset(const CharacterOffset&); + CharacterOffset nextSentenceEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousSentenceStartCharacterOffset(const CharacterOffset&); + + // Bounds + CharacterOffset characterOffsetForPoint(const IntPoint&, AccessibilityObject*); + IntRect absoluteCaretBoundsForCharacterOffset(const CharacterOffset&); + CharacterOffset characterOffsetForBounds(const IntRect&, bool); + + // Lines + CharacterOffset endCharacterOffsetOfLine(const CharacterOffset&); + CharacterOffset startCharacterOffsetOfLine(const CharacterOffset&); + + // Index + CharacterOffset characterOffsetForIndex(int, const AccessibilityObject*); + int indexForCharacterOffset(const CharacterOffset&, AccessibilityObject*); enum AXNotification { AXActiveDescendantChanged, @@ -165,10 +265,12 @@ public: AXFocusedUIElementChanged, AXLayoutComplete, AXLoadComplete, + AXNewDocumentLoadComplete, AXSelectedChildrenChanged, AXSelectedTextChanged, AXValueChanged, AXScrolledToAnchor, + AXLiveRegionCreated, AXLiveRegionChanged, AXMenuListItemSelected, AXMenuListValueChanged, @@ -177,6 +279,7 @@ public: AXRowCountChanged, AXRowCollapsed, AXRowExpanded, + AXExpandedChanged, AXInvalidStatusChanged, AXTextChanged, AXAriaAttributeChanged, @@ -187,12 +290,19 @@ public: void postNotification(Node*, AXNotification, PostTarget = TargetElement, PostType = PostAsynchronously); void postNotification(AccessibilityObject*, Document*, AXNotification, PostTarget = TargetElement, PostType = PostAsynchronously); - enum AXTextChange { - AXTextInserted, - AXTextDeleted, - }; +#ifndef NDEBUG + void showIntent(const AXTextStateChangeIntent&); +#endif - void nodeTextChangeNotification(Node*, AXTextChange, unsigned offset, const String&); + void setTextSelectionIntent(const AXTextStateChangeIntent&); + void setIsSynchronizingSelection(bool); + + void postTextStateChangeNotification(Node*, AXTextEditType, const String&, const VisiblePosition&); + void postTextReplacementNotification(Node*, AXTextEditType deletionType, const String& deletedText, AXTextEditType insertionType, const String& insertedText, const VisiblePosition&); + void postTextStateChangeNotification(Node*, const AXTextStateChangeIntent&, const VisibleSelection&); + void postTextStateChangeNotification(const Position&, const AXTextStateChangeIntent&, const VisibleSelection&); + void postLiveRegionChangeNotification(AccessibilityObject*); + void focusAriaModalNode(); enum AXLoadingEvent { AXLoadingStarted, @@ -211,12 +321,26 @@ public: AXComputedObjectAttributeCache* computedObjectAttributeCache() { return m_computedObjectAttributeCache.get(); } Document& document() const { return m_document; } - + +#if PLATFORM(MAC) + static void setShouldRepostNotificationsForTests(bool value); +#endif + void recomputeDeferredIsIgnored(RenderBlock& renderer); + void performDeferredIsIgnoredChange(); + protected: void postPlatformNotification(AccessibilityObject*, AXNotification); void platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode); - void nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned offset, const String&); +#if PLATFORM(COCOA) + void postTextStateChangePlatformNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&); + void postTextStateChangePlatformNotification(AccessibilityObject*, AXTextEditType, const String&, const VisiblePosition&); + void postTextReplacementPlatformNotification(AccessibilityObject*, AXTextEditType, const String&, AXTextEditType, const String&, const VisiblePosition&); +#else + static AXTextChange textChangeForEditType(AXTextEditType); + void nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&); +#endif + void frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent); void textChanged(AccessibilityObject*); void labelChanged(Element*); @@ -225,8 +349,57 @@ protected: void setNodeInUse(Node* n) { m_textMarkerNodes.add(n); } void removeNodeForUse(Node* n) { m_textMarkerNodes.remove(n); } bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); } + + // CharacterOffset functions. + enum TraverseOption { TraverseOptionDefault = 1 << 0, TraverseOptionToNodeEnd = 1 << 1, TraverseOptionIncludeStart = 1 << 2, TraverseOptionValidateOffset = 1 << 3 }; + Node* nextNode(Node*) const; + Node* previousNode(Node*) const; + CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, TraverseOption = TraverseOptionDefault, bool stayWithinRange = false); + VisiblePosition visiblePositionFromCharacterOffset(const CharacterOffset&); + CharacterOffset characterOffsetFromVisiblePosition(const VisiblePosition&); + void setTextMarkerDataWithCharacterOffset(TextMarkerData&, const CharacterOffset&); + UChar32 characterAfter(const CharacterOffset&); + UChar32 characterBefore(const CharacterOffset&); + CharacterOffset characterOffsetForNodeAndOffset(Node&, int, TraverseOption = TraverseOptionDefault); + CharacterOffset previousBoundary(const CharacterOffset&, BoundarySearchFunction); + CharacterOffset nextBoundary(const CharacterOffset&, BoundarySearchFunction); + CharacterOffset startCharacterOffsetOfWord(const CharacterOffset&, EWordSide = RightWordIfOnBoundary); + CharacterOffset endCharacterOffsetOfWord(const CharacterOffset&, EWordSide = RightWordIfOnBoundary); + CharacterOffset startCharacterOffsetOfParagraph(const CharacterOffset&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); + CharacterOffset endCharacterOffsetOfParagraph(const CharacterOffset&, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); + CharacterOffset startCharacterOffsetOfSentence(const CharacterOffset&); + CharacterOffset endCharacterOffsetOfSentence(const CharacterOffset&); + CharacterOffset characterOffsetForPoint(const IntPoint&); + LayoutRect localCaretRectForCharacterOffset(RenderObject*&, const CharacterOffset&); + bool shouldSkipBoundary(const CharacterOffset&, const CharacterOffset&); private: + AccessibilityObject* rootWebArea(); + + static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + + AXID getAXID(AccessibilityObject*); + + void notificationPostTimerFired(); + + void liveRegionChangedNotificationPostTimerFired(); + + void focusAriaModalNodeTimerFired(); + + void postTextStateChangeNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&); + + bool enqueuePasswordValueChangeNotification(AccessibilityObject*); + void passwordNotificationPostTimerFired(); + + void handleMenuOpened(Node*); + void handleLiveRegionCreated(Node*); + void handleMenuItemSelected(Node*); + + // aria-modal related + void findAriaModalNodes(); + void updateCurrentAriaModalNode(); + bool isNodeVisible(Node*) const; + Document& m_document; HashMap<AXID, RefPtr<AccessibilityObject>> m_objects; HashMap<RenderObject*, AXID> m_renderObjectMapping; @@ -234,20 +407,28 @@ private: HashMap<Node*, AXID> m_nodeObjectMapping; HashSet<Node*> m_textMarkerNodes; std::unique_ptr<AXComputedObjectAttributeCache> m_computedObjectAttributeCache; - static bool gAccessibilityEnabled; - static bool gAccessibilityEnhancedUserInterfaceEnabled; - + WEBCORE_EXPORT static bool gAccessibilityEnabled; + WEBCORE_EXPORT static bool gAccessibilityEnhancedUserInterfaceEnabled; + HashSet<AXID> m_idsInUse; - - Timer<AXObjectCache> m_notificationPostTimer; + + Timer m_notificationPostTimer; Vector<std::pair<RefPtr<AccessibilityObject>, AXNotification>> m_notificationsToPost; - void notificationPostTimerFired(Timer<AXObjectCache>&); - void handleMenuOpened(Node*); - void handleMenuItemSelected(Node*); + + Timer m_passwordNotificationPostTimer; + + ListHashSet<RefPtr<AccessibilityObject>> m_passwordNotificationsToPost; - static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + Timer m_liveRegionChangedPostTimer; + ListHashSet<RefPtr<AccessibilityObject>> m_liveRegionObjectsSet; - AXID getAXID(AccessibilityObject*); + Timer m_focusAriaModalNodeTimer; + Node* m_currentAriaModalNode; + ListHashSet<Node*> m_ariaModalNodesSet; + + AXTextStateChangeIntent m_textSelectionIntent; + bool m_isSynchronizingSelection { false }; + ListHashSet<RenderBlock*> m_deferredIsIgnoredChangeList; }; class AXAttributeCacheEnabler @@ -255,40 +436,44 @@ class AXAttributeCacheEnabler public: explicit AXAttributeCacheEnabler(AXObjectCache *cache); ~AXAttributeCacheEnabler(); - + +#if HAVE(ACCESSIBILITY) private: AXObjectCache* m_cache; +#endif }; - + bool nodeHasRole(Node*, const String& role); // This will let you know if aria-hidden was explicitly set to false. bool isNodeAriaVisible(Node*); #if !HAVE(ACCESSIBILITY) inline AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID) const { return DefaultBehavior; } +inline AccessibilityReplacedText::AccessibilityReplacedText(const VisibleSelection&) { } +inline void AccessibilityReplacedText::postTextStateChangeNotification(AXObjectCache*, AXTextEditType, const String&, const VisibleSelection&) { } inline void AXComputedObjectAttributeCache::setIgnored(AXID, AccessibilityObjectInclusion) { } -inline AXObjectCache::AXObjectCache(Document& document) : m_document(document), m_notificationPostTimer(this, (Timer<AXObjectCache>::TimerFiredFunction) nullptr) { } +inline AXObjectCache::AXObjectCache(Document& document) : m_document(document), m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired), m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired), m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired), m_focusAriaModalNodeTimer(*this, &AXObjectCache::focusAriaModalNodeTimerFired) { } inline AXObjectCache::~AXObjectCache() { } -inline AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page*) { return 0; } -inline AccessibilityObject* AXObjectCache::get(RenderObject*) { return 0; } -inline AccessibilityObject* AXObjectCache::get(Node*) { return 0; } -inline AccessibilityObject* AXObjectCache::get(Widget*) { return 0; } -inline AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole) { return 0; } -inline AccessibilityObject* AXObjectCache::getOrCreate(RenderObject*) { return 0; } -inline AccessibilityObject* AXObjectCache::getOrCreate(Node*) { return 0; } -inline AccessibilityObject* AXObjectCache::getOrCreate(Widget*) { return 0; } -inline AccessibilityObject* AXObjectCache::rootObject() { return 0; } -inline AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame*) { return 0; } -inline Element* AXObjectCache::rootAXEditableElement(Node*) { return 0; } +inline AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::get(RenderObject*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::get(Node*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::get(Widget*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole) { return nullptr; } +inline AccessibilityObject* AXObjectCache::getOrCreate(RenderObject*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::getOrCreate(Node*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::getOrCreate(Widget*) { return nullptr; } +inline AccessibilityObject* AXObjectCache::rootObject() { return nullptr; } +inline AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame*) { return nullptr; } inline bool nodeHasRole(Node*, const String&) { return false; } inline void AXObjectCache::startCachingComputedObjectAttributesUntilTreeMutates() { } inline void AXObjectCache::stopCachingComputedObjectAttributes() { } inline bool isNodeAriaVisible(Node*) { return true; } -inline const Element* AXObjectCache::rootAXEditableElement(const Node*) { return 0; } +inline const Element* AXObjectCache::rootAXEditableElement(const Node*) { return nullptr; } +inline Node* AXObjectCache::ariaModalNode() { return nullptr; } inline void AXObjectCache::attachWrapper(AccessibilityObject*) { } inline void AXObjectCache::checkedStateChanged(Node*) { } inline void AXObjectCache::childrenChanged(RenderObject*, RenderObject*) { } -inline void AXObjectCache::childrenChanged(Node*) { } +inline void AXObjectCache::childrenChanged(Node*, Node*) { } inline void AXObjectCache::childrenChanged(AccessibilityObject*) { } inline void AXObjectCache::textChanged(RenderObject*) { } inline void AXObjectCache::textChanged(Node*) { } @@ -300,25 +485,51 @@ inline void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityOb inline void AXObjectCache::handleActiveDescendantChanged(Node*) { } inline void AXObjectCache::handleAriaExpandedChange(Node*) { } inline void AXObjectCache::handleAriaRoleChanged(Node*) { } +inline void AXObjectCache::handleAriaModalChange(Node*) { } inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) { } inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { } inline void AXObjectCache::handleAttributeChanged(const QualifiedName&, Element*) { } inline void AXObjectCache::recomputeIsIgnored(RenderObject*) { } +inline void AXObjectCache::recomputeDeferredIsIgnored(RenderBlock&) { } +inline void AXObjectCache::performDeferredIsIgnoredChange() { } inline void AXObjectCache::handleScrolledToAnchor(const Node*) { } -inline void AXObjectCache::nodeTextChangeNotification(Node*, AXTextChange, unsigned, const String&) { } -inline void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) { } +inline void AXObjectCache::postTextStateChangeNotification(Node*, const AXTextStateChangeIntent&, const VisibleSelection&) { } +inline void AXObjectCache::postTextStateChangeNotification(Node*, AXTextEditType, const String&, const VisiblePosition&) { } +inline void AXObjectCache::postTextReplacementNotification(Node*, AXTextEditType, const String&, AXTextEditType, const String&, const VisiblePosition&) { } inline void AXObjectCache::postNotification(AccessibilityObject*, Document*, AXNotification, PostTarget, PostType) { } inline void AXObjectCache::postNotification(RenderObject*, AXNotification, PostTarget, PostType) { } inline void AXObjectCache::postNotification(Node*, AXNotification, PostTarget, PostType) { } inline void AXObjectCache::postPlatformNotification(AccessibilityObject*, AXNotification) { } +inline void AXObjectCache::postLiveRegionChangeNotification(AccessibilityObject*) { } +inline void AXObjectCache::focusAriaModalNode() { } +inline RefPtr<Range> AXObjectCache::rangeForNodeContents(Node*) { return nullptr; } inline void AXObjectCache::remove(AXID) { } inline void AXObjectCache::remove(RenderObject*) { } inline void AXObjectCache::remove(Node*) { } inline void AXObjectCache::remove(Widget*) { } inline void AXObjectCache::selectedChildrenChanged(RenderObject*) { } inline void AXObjectCache::selectedChildrenChanged(Node*) { } +inline void AXObjectCache::setIsSynchronizingSelection(bool) { } +inline void AXObjectCache::setTextSelectionIntent(const AXTextStateChangeIntent&) { } +inline RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&) { return nullptr; } +inline IntRect AXObjectCache::absoluteCaretBoundsForCharacterOffset(const CharacterOffset&) { return IntRect(); } +inline CharacterOffset AXObjectCache::characterOffsetForIndex(int, const AccessibilityObject*) { return CharacterOffset(); } +inline CharacterOffset AXObjectCache::startOrEndCharacterOffsetForRange(RefPtr<Range>, bool) { return CharacterOffset(); } +inline CharacterOffset AXObjectCache::endCharacterOffsetOfLine(const CharacterOffset&) { return CharacterOffset(); } +inline CharacterOffset AXObjectCache::nextCharacterOffset(const CharacterOffset&, bool) { return CharacterOffset(); } +inline CharacterOffset AXObjectCache::previousCharacterOffset(const CharacterOffset&, bool) { return CharacterOffset(); } +#if PLATFORM(COCOA) +inline void AXObjectCache::postTextStateChangePlatformNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&) { } +inline void AXObjectCache::postTextStateChangePlatformNotification(AccessibilityObject*, AXTextEditType, const String&, const VisiblePosition&) { } +inline void AXObjectCache::postTextReplacementPlatformNotification(AccessibilityObject*, AXTextEditType, const String&, AXTextEditType, const String&, const VisiblePosition&) { } +#else +inline AXTextChange AXObjectCache::textChangeForEditType(AXTextEditType) { return AXTextInserted; } +inline void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) { } #endif -} +inline AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache*) { } +inline AXAttributeCacheEnabler::~AXAttributeCacheEnabler() { } #endif + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AXTextStateChangeIntent.h b/Source/WebCore/accessibility/AXTextStateChangeIntent.h new file mode 100644 index 000000000..1feccce2e --- /dev/null +++ b/Source/WebCore/accessibility/AXTextStateChangeIntent.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +namespace WebCore { + +enum AXTextStateChangeType { + AXTextStateChangeTypeUnknown, + AXTextStateChangeTypeEdit, + AXTextStateChangeTypeSelectionMove, + AXTextStateChangeTypeSelectionExtend, + AXTextStateChangeTypeSelectionBoundary +}; + +enum AXTextEditType { + AXTextEditTypeUnknown, + AXTextEditTypeDelete, // Generic text delete + AXTextEditTypeInsert, // Generic text insert + AXTextEditTypeTyping, // Insert via typing + AXTextEditTypeDictation, // Insert via dictation + AXTextEditTypeCut, // Delete via Cut + AXTextEditTypePaste, // Insert via Paste + AXTextEditTypeAttributesChange // Change font, style, alignment, color, etc. +}; + +enum AXTextSelectionDirection { + AXTextSelectionDirectionUnknown, + AXTextSelectionDirectionBeginning, + AXTextSelectionDirectionEnd, + AXTextSelectionDirectionPrevious, + AXTextSelectionDirectionNext, + AXTextSelectionDirectionDiscontiguous +}; + +enum AXTextSelectionGranularity { + AXTextSelectionGranularityUnknown, + AXTextSelectionGranularityCharacter, + AXTextSelectionGranularityWord, + AXTextSelectionGranularityLine, + AXTextSelectionGranularitySentence, + AXTextSelectionGranularityParagraph, + AXTextSelectionGranularityPage, + AXTextSelectionGranularityDocument, + AXTextSelectionGranularityAll // All granularity represents the action of selecting the whole document as a single action. Extending selection by some other granularity until it encompasses the whole document will not result in a all granularity notification. +}; + +struct AXTextSelection { + AXTextSelectionDirection direction; + AXTextSelectionGranularity granularity; + bool focusChange; +}; + +struct AXTextStateChangeIntent { + AXTextStateChangeType type; + union { + AXTextSelection selection; + AXTextEditType change; + }; + + AXTextStateChangeIntent(AXTextStateChangeType type = AXTextStateChangeTypeUnknown, AXTextSelection selection = AXTextSelection()) + : type(type) + , selection(selection) + { } + + AXTextStateChangeIntent(AXTextEditType change) + : type(AXTextStateChangeTypeEdit) + , change(change) + { } +}; + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp b/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp index acb837070..a0b95760c 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp +++ b/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -30,11 +30,13 @@ #include "AccessibilityARIAGrid.h" #include "AXObjectCache.h" +#include "AccessibilityARIAGridRow.h" #include "AccessibilityTableCell.h" #include "AccessibilityTableColumn.h" #include "AccessibilityTableHeaderContainer.h" #include "AccessibilityTableRow.h" #include "RenderObject.h" +#include "RenderTableSection.h" namespace WebCore { @@ -47,36 +49,36 @@ AccessibilityARIAGrid::~AccessibilityARIAGrid() { } -PassRefPtr<AccessibilityARIAGrid> AccessibilityARIAGrid::create(RenderObject* renderer) +Ref<AccessibilityARIAGrid> AccessibilityARIAGrid::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGrid(renderer)); + return adoptRef(*new AccessibilityARIAGrid(renderer)); } bool AccessibilityARIAGrid::addTableCellChild(AccessibilityObject* child, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount) { - if (!child || !child->isTableRow() || child->ariaRoleAttribute() != RowRole) + if (!child || (!is<AccessibilityTableRow>(*child) && !is<AccessibilityARIAGridRow>(*child))) return false; - AccessibilityTableRow* row = toAccessibilityTableRow(child); - if (appendedRows.contains(row)) + auto& row = downcast<AccessibilityTableRow>(*child); + if (appendedRows.contains(&row)) return false; // store the maximum number of columns - unsigned rowCellCount = row->children().size(); + unsigned rowCellCount = row.children().size(); if (rowCellCount > columnCount) columnCount = rowCellCount; - row->setRowIndex((int)m_rows.size()); - m_rows.append(row); + row.setRowIndex((int)m_rows.size()); + m_rows.append(&row); // Try adding the row if it's not ignoring accessibility, // otherwise add its children (the cells) as the grid's children. - if (!row->accessibilityIsIgnored()) - m_children.append(row); + if (!row.accessibilityIsIgnored()) + m_children.append(&row); else - m_children.appendVector(row->children()); + m_children.appendVector(row.children()); - appendedRows.add(row); + appendedRows.add(&row); return true; } @@ -85,9 +87,9 @@ void AccessibilityARIAGrid::addRowDescendant(AccessibilityObject* rowChild, Hash if (!rowChild) return; - if (!rowChild->isTableRow()) { + if (!rowChild->isTableRow() || !rowChild->node()) { // Although a "grid" should have rows as its direct descendants, if this is not a table row, - // dive deeper into the descendants to try to find a valid row. + // or this row is anonymous, dive deeper into the descendants to try to find a valid row. for (const auto& child : rowChild->children()) addRowDescendant(child.get(), appendedRows, columnCount); } else @@ -98,7 +100,7 @@ void AccessibilityARIAGrid::addChildren() { ASSERT(!m_haveChildren); - if (!isAccessibilityTable()) { + if (!isExposableThroughAccessibility()) { AccessibilityRenderObject::addChildren(); return; } @@ -109,20 +111,36 @@ void AccessibilityARIAGrid::addChildren() AXObjectCache* axCache = m_renderer->document().axObjectCache(); - // add only rows that are labeled as aria rows + // Add the children rows but be mindful in case there are footer sections in this table. HashSet<AccessibilityObject*> appendedRows; unsigned columnCount = 0; - for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) - addRowDescendant(child.get(), appendedRows, columnCount); + AccessibilityChildrenVector footerSections; + for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) { + bool footerSection = false; + if (RenderObject* childRenderer = child->renderer()) { + if (is<RenderTableSection>(*childRenderer)) { + RenderTableSection& childSection = downcast<RenderTableSection>(*childRenderer); + if (&childSection == childSection.table()->footer()) { + footerSections.append(child); + footerSection = true; + } + } + } + if (!footerSection) + addRowDescendant(child.get(), appendedRows, columnCount); + } + + for (const auto& footerSection : footerSections) + addRowDescendant(footerSection.get(), appendedRows, columnCount); // make the columns based on the number of columns in the first body for (unsigned i = 0; i < columnCount; ++i) { - AccessibilityTableColumn* column = toAccessibilityTableColumn(axCache->getOrCreate(ColumnRole)); - column->setColumnIndex((int)i); - column->setParent(this); - m_columns.append(column); - if (!column->accessibilityIsIgnored()) - m_children.append(column); + auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole)); + column.setColumnIndex(static_cast<int>(i)); + column.setParent(this); + m_columns.append(&column); + if (!column.accessibilityIsIgnored()) + m_children.append(&column); } AccessibilityObject* headerContainerObject = headerContainer(); diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.h b/Source/WebCore/accessibility/AccessibilityARIAGrid.h index 2a4fb9b26..33232d66a 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGrid.h +++ b/Source/WebCore/accessibility/AccessibilityARIAGrid.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityARIAGrid_h -#define AccessibilityARIAGrid_h +#pragma once #include "AccessibilityTable.h" #include <wtf/Forward.h> @@ -37,28 +36,24 @@ namespace WebCore { class AccessibilityTableCell; class AccessibilityTableHeaderContainer; -class AccessibilityARIAGrid : public AccessibilityTable { - -private: - explicit AccessibilityARIAGrid(RenderObject*); +class AccessibilityARIAGrid final : public AccessibilityTable { public: - static PassRefPtr<AccessibilityARIAGrid> create(RenderObject*); + static Ref<AccessibilityARIAGrid> create(RenderObject*); virtual ~AccessibilityARIAGrid(); - - virtual bool isAriaTable() const override { return true; } - virtual void addChildren() override; + void addChildren() override; private: + explicit AccessibilityARIAGrid(RenderObject*); + // ARIA treegrids and grids support selected rows. - virtual bool supportsSelectedRows() override { return true; } - virtual bool isMultiSelectable() const override { return true; } - virtual bool isTableExposableThroughAccessibility() const override { return true; } + bool supportsSelectedRows() override { return true; } + bool isMultiSelectable() const override { return true; } + bool computeIsTableExposableThroughAccessibility() const override { return true; } + bool isAriaTable() const override { return true; } void addRowDescendant(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); bool addTableCellChild(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); }; } // namespace WebCore - -#endif // AccessibilityARIAGrid_h diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp index 506e7f25e..36aac823d 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp +++ b/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -32,9 +32,12 @@ #include "AccessibilityObject.h" #include "AccessibilityTable.h" #include "AccessibilityTableRow.h" +#include "HTMLNames.h" namespace WebCore { +using namespace HTMLNames; + AccessibilityARIAGridCell::AccessibilityARIAGridCell(RenderObject* renderer) : AccessibilityTableCell(renderer) { @@ -44,43 +47,37 @@ AccessibilityARIAGridCell::~AccessibilityARIAGridCell() { } -PassRefPtr<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer) +Ref<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGridCell(renderer)); + return adoptRef(*new AccessibilityARIAGridCell(renderer)); } AccessibilityTable* AccessibilityARIAGridCell::parentTable() const { - AccessibilityObject* parent = parentObjectUnignored(); - if (!parent) - return nullptr; - - if (parent->isAccessibilityTable()) - return toAccessibilityTable(parent); - - // It could happen that we hadn't reached the parent table yet (in - // case objects for rows were not ignoring accessibility) so for - // that reason we need to run parentObjectUnignored once again. - parent = parent->parentObjectUnignored(); - if (!parent || !parent->isAccessibilityTable()) - return nullptr; - - return toAccessibilityTable(parent); + // ARIA gridcells may have multiple levels of unignored ancestors that are not the parent table, + // including rows and interactive rowgroups. In addition, poorly-formed grids may contain elements + // which pass the tests for inclusion. + for (AccessibilityObject* parent = parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) { + if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) + return downcast<AccessibilityTable>(parent); + } + + return nullptr; } -void AccessibilityARIAGridCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) +void AccessibilityARIAGridCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (parent->isTableRow()) { + if (is<AccessibilityTableRow>(*parent)) { // We already got a table row, use its API. - rowRange.first = toAccessibilityTableRow(parent)->rowIndex(); - } else if (parent->isAccessibilityTable()) { + rowRange.first = downcast<AccessibilityTableRow>(*parent).rowIndex(); + } else if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) { // We reached the parent table, so we need to inspect its // children to determine the row index for the cell in it. - unsigned columnCount = toAccessibilityTable(parent)->columnCount(); + unsigned columnCount = downcast<AccessibilityTable>(*parent).columnCount(); if (!columnCount) return; @@ -94,17 +91,51 @@ void AccessibilityARIAGridCell::rowIndexRange(std::pair<unsigned, unsigned>& row } } - // as far as I can tell, grid cells cannot span rows - rowRange.second = 1; + // ARIA 1.1, aria-rowspan attribute is intended for cells and gridcells which are not contained in a native table. + // So we should check for that attribute here. + rowRange.second = ariaRowSpanWithRowIndex(rowRange.first); +} + +unsigned AccessibilityARIAGridCell::ariaRowSpanWithRowIndex(unsigned rowIndex) const +{ + unsigned rowSpan = AccessibilityTableCell::ariaRowSpan(); + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent) + return 1; + + // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group. + if (!rowSpan) { + // rowSpan defaults to 1. + rowSpan = 1; + if (AccessibilityObject* parentRowGroup = this->parentRowGroup()) { + // If the row group is the parent table, we use total row count to calculate the span. + if (is<AccessibilityTable>(*parentRowGroup)) + rowSpan = downcast<AccessibilityTable>(*parentRowGroup).rowCount() - rowIndex; + // Otherwise, we have to get the index for the current row within the parent row group. + else if (is<AccessibilityTableRow>(*parent)) { + const auto& siblings = parentRowGroup->children(); + unsigned rowCount = siblings.size(); + for (unsigned k = 0; k < rowCount; ++k) { + if (siblings[k].get() == parent) { + rowSpan = rowCount - k; + break; + } + } + } + } + } + + return rowSpan; } -void AccessibilityARIAGridCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) +void AccessibilityARIAGridCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (!parent->isTableRow() && !parent->isAccessibilityTable()) + if (!is<AccessibilityTableRow>(*parent) + && !(is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility())) return; const AccessibilityChildrenVector& siblings = parent->children(); @@ -116,8 +147,20 @@ void AccessibilityARIAGridCell::columnIndexRange(std::pair<unsigned, unsigned>& } } - // as far as I can tell, grid cells cannot span columns - columnRange.second = 1; + // ARIA 1.1, aria-colspan attribute is intended for cells and gridcells which are not contained in a native table. + // So we should check for that attribute here. + columnRange.second = ariaColumnSpan(); +} + +AccessibilityObject* AccessibilityARIAGridCell::parentRowGroup() const +{ + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (parent->hasTagName(theadTag) || parent->hasTagName(tbodyTag) || parent->hasTagName(tfootTag) || parent->roleValue() == RowGroupRole) + return parent; + } + + // If there's no row group found, we use the parent table as the row group. + return parentTable(); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridCell.h b/Source/WebCore/accessibility/AccessibilityARIAGridCell.h index 1df3e3f02..ef226007a 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGridCell.h +++ b/Source/WebCore/accessibility/AccessibilityARIAGridCell.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,30 +26,28 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityARIAGridCell_h -#define AccessibilityARIAGridCell_h +#pragma once #include "AccessibilityTableCell.h" namespace WebCore { -class AccessibilityARIAGridCell : public AccessibilityTableCell { - -private: - explicit AccessibilityARIAGridCell(RenderObject*); +class AccessibilityARIAGridCell final : public AccessibilityTableCell { public: - static PassRefPtr<AccessibilityARIAGridCell> create(RenderObject*); + static Ref<AccessibilityARIAGridCell> create(RenderObject*); virtual ~AccessibilityARIAGridCell(); // fills in the start location and row span of cell - virtual void rowIndexRange(std::pair<unsigned, unsigned>& rowRange) override; + void rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const override; // fills in the start location and column span of cell - virtual void columnIndexRange(std::pair<unsigned, unsigned>& columnRange) override; + void columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const override; -protected: - virtual AccessibilityTable* parentTable() const override; +private: + explicit AccessibilityARIAGridCell(RenderObject*); + + AccessibilityTable* parentTable() const override; + AccessibilityObject* parentRowGroup() const; + unsigned ariaRowSpanWithRowIndex(unsigned index) const; }; } // namespace WebCore - -#endif // AccessibilityARIAGridCell_h diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp index f85b62719..dd4fda567 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp +++ b/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -44,9 +44,9 @@ AccessibilityARIAGridRow::~AccessibilityARIAGridRow() { } -PassRefPtr<AccessibilityARIAGridRow> AccessibilityARIAGridRow::create(RenderObject* renderer) +Ref<AccessibilityARIAGridRow> AccessibilityARIAGridRow::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGridRow(renderer)); + return adoptRef(*new AccessibilityARIAGridRow(renderer)); } bool AccessibilityARIAGridRow::isARIATreeGridRow() const @@ -63,7 +63,7 @@ void AccessibilityARIAGridRow::disclosedRows(AccessibilityChildrenVector& disclo // The contiguous disclosed rows will be the rows in the table that // have an aria-level of plus 1 from this row. AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(*parent) || !downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) return; // Search for rows that match the correct level. @@ -73,7 +73,7 @@ void AccessibilityARIAGridRow::disclosedRows(AccessibilityChildrenVector& disclo return; unsigned level = hierarchicalLevel(); - auto& allRows = toAccessibilityTable(parent)->rows(); + auto& allRows = downcast<AccessibilityTable>(*parent).rows(); int rowCount = allRows.size(); for (int k = index + 1; k < rowCount; ++k) { AccessibilityObject* row = allRows[k].get(); @@ -90,20 +90,20 @@ AccessibilityObject* AccessibilityARIAGridRow::disclosedByRow() const // The row that discloses this one is the row in the table // that is aria-level subtract 1 from this row. AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isAccessibilityTable()) - return 0; + if (!is<AccessibilityTable>(*parent) || !downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) + return nullptr; // If the level is 1 or less, than nothing discloses this row. unsigned level = hierarchicalLevel(); if (level <= 1) - return 0; + return nullptr; // Search for the previous row that matches the correct level. int index = rowIndex(); - auto& allRows = toAccessibilityTable(parent)->rows(); + auto& allRows = downcast<AccessibilityTable>(parent)->rows(); int rowCount = allRows.size(); if (index >= rowCount) - return 0; + return nullptr; for (int k = index - 1; k >= 0; --k) { AccessibilityObject* row = allRows[k].get(); @@ -114,14 +114,22 @@ AccessibilityObject* AccessibilityARIAGridRow::disclosedByRow() const return nullptr; } +AccessibilityObject* AccessibilityARIAGridRow::parentObjectUnignored() const +{ + return parentTable(); +} + AccessibilityTable* AccessibilityARIAGridRow::parentTable() const { // The parent table might not be the direct ancestor of the row unfortunately. ARIA states that role="grid" should // only have "row" elements, but if not, we still should handle it gracefully by finding the right table. for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { // The parent table for an ARIA grid row should be an ARIA table. - if (parent->isTable() && parent->isAccessibilityTable() && toAccessibilityTable(parent)->isAriaTable()) - return toAccessibilityTable(parent); + if (is<AccessibilityTable>(*parent)) { + AccessibilityTable& tableParent = downcast<AccessibilityTable>(*parent); + if (tableParent.isExposableThroughAccessibility() && tableParent.isAriaTable()) + return &tableParent; + } } return nullptr; diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.h b/Source/WebCore/accessibility/AccessibilityARIAGridRow.h index c97db29b4..7ba74f4dd 100644 --- a/Source/WebCore/accessibility/AccessibilityARIAGridRow.h +++ b/Source/WebCore/accessibility/AccessibilityARIAGridRow.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityARIAGridRow_h -#define AccessibilityARIAGridRow_h +#pragma once #include "AccessibilityTableRow.h" @@ -35,26 +34,24 @@ namespace WebCore { class AccessibilityTable; -class AccessibilityARIAGridRow : public AccessibilityTableRow { - -private: - explicit AccessibilityARIAGridRow(RenderObject*); +class AccessibilityARIAGridRow final : public AccessibilityTableRow { public: - static PassRefPtr<AccessibilityARIAGridRow> create(RenderObject*); + static Ref<AccessibilityARIAGridRow> create(RenderObject*); virtual ~AccessibilityARIAGridRow(); void disclosedRows(AccessibilityChildrenVector&); AccessibilityObject* disclosedByRow() const; - virtual AccessibilityObject* headerObject() override; + AccessibilityObject* headerObject() override; private: - virtual bool isARIATreeGridRow() const override; - virtual AccessibilityTable* parentTable() const override; -}; + explicit AccessibilityARIAGridRow(RenderObject*); -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityARIAGridRow, isARIATreeGridRow()) + bool isARIATreeGridRow() const override; + AccessibilityTable* parentTable() const override; + AccessibilityObject* parentObjectUnignored() const override; +}; } // namespace WebCore -#endif // AccessibilityARIAGridRow_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityARIAGridRow, isARIATreeGridRow()) diff --git a/Source/WebCore/accessibility/AccessibilityAllInOne.cpp b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp new file mode 100644 index 000000000..2cbe4505c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. + +#include "AXObjectCache.cpp" +#include "AccessibilityARIAGrid.cpp" +#include "AccessibilityARIAGridCell.cpp" +#include "AccessibilityARIAGridRow.cpp" +#include "AccessibilityAttachment.cpp" +#include "AccessibilityImageMapLink.cpp" +#include "AccessibilityLabel.cpp" +#include "AccessibilityList.cpp" +#include "AccessibilityListBox.cpp" +#include "AccessibilityListBoxOption.cpp" +#include "AccessibilityMathMLElement.cpp" +#include "AccessibilityMediaControls.cpp" +#include "AccessibilityMenuList.cpp" +#include "AccessibilityMenuListOption.cpp" +#include "AccessibilityMenuListPopup.cpp" +#include "AccessibilityMockObject.cpp" +#include "AccessibilityNodeObject.cpp" +#include "AccessibilityObject.cpp" +#include "AccessibilityProgressIndicator.cpp" +#include "AccessibilityRenderObject.cpp" +#include "AccessibilitySVGElement.cpp" +#include "AccessibilitySVGRoot.cpp" +#include "AccessibilityScrollView.cpp" +#include "AccessibilityScrollbar.cpp" +#include "AccessibilitySlider.cpp" +#include "AccessibilitySpinButton.cpp" +#include "AccessibilityTable.cpp" +#include "AccessibilityTableCell.cpp" +#include "AccessibilityTableColumn.cpp" +#include "AccessibilityTableHeaderContainer.cpp" +#include "AccessibilityTableRow.cpp" +#include "AccessibilityTree.cpp" +#include "AccessibilityTreeItem.cpp" diff --git a/Source/WebCore/accessibility/AccessibilityAttachment.cpp b/Source/WebCore/accessibility/AccessibilityAttachment.cpp new file mode 100644 index 000000000..a7d3c37ea --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityAttachment.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityAttachment.h" + +#include "HTMLAttachmentElement.h" +#include "HTMLNames.h" +#include "LocalizedStrings.h" +#include "RenderAttachment.h" + +#if ENABLE(ATTACHMENT_ELEMENT) + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityAttachment::AccessibilityAttachment(RenderAttachment* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +Ref<AccessibilityAttachment> AccessibilityAttachment::create(RenderAttachment* renderer) +{ + return adoptRef(*new AccessibilityAttachment(renderer)); +} + +bool AccessibilityAttachment::hasProgress(float* progress) const +{ + auto& progressString = getAttribute(progressAttr); + bool validProgress; + float result = std::max<float>(std::min<float>(progressString.toFloat(&validProgress), 1), 0); + if (progress) + *progress = result; + return validProgress; +} + +float AccessibilityAttachment::valueForRange() const +{ + float progress = 0; + hasProgress(&progress); + return progress; +} + +HTMLAttachmentElement* AccessibilityAttachment::attachmentElement() const +{ + ASSERT(is<HTMLAttachmentElement>(node())); + if (!is<HTMLAttachmentElement>(node())) + return nullptr; + + return downcast<HTMLAttachmentElement>(node()); +} + +String AccessibilityAttachment::roleDescription() const +{ + return AXAttachmentRoleText(); +} + +bool AccessibilityAttachment::computeAccessibilityIsIgnored() const +{ + return false; +} + +void AccessibilityAttachment::accessibilityText(Vector<AccessibilityText>& textOrder) +{ + HTMLAttachmentElement* attachmentElement = this->attachmentElement(); + if (!attachmentElement) + return; + + auto title = attachmentElement->attachmentTitle(); + auto& subtitle = getAttribute(subtitleAttr); + auto& action = getAttribute(actionAttr); + + if (action.length()) + textOrder.append(AccessibilityText(action, ActionText)); + + if (title.length()) + textOrder.append(AccessibilityText(title, TitleText)); + + if (subtitle.length()) + textOrder.append(AccessibilityText(subtitle, SubtitleText)); +} + +} // namespace WebCore + +#endif // ENABLE(ATTACHMENT_ELEMENT) + diff --git a/Source/WebCore/accessibility/AccessibilityAttachment.h b/Source/WebCore/accessibility/AccessibilityAttachment.h new file mode 100644 index 000000000..2b657737c --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityAttachment.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(ATTACHMENT_ELEMENT) + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class HTMLAttachmentElement; +class RenderAttachment; + +class AccessibilityAttachment final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityAttachment> create(RenderAttachment*); + HTMLAttachmentElement* attachmentElement() const; + bool hasProgress(float* progress = nullptr) const; + +private: + AccessibilityRole roleValue() const override { return ButtonRole; } + bool isAttachmentElement() const override { return true; } + + String roleDescription() const override; + float valueForRange() const override; + bool computeAccessibilityIsIgnored() const override; + void accessibilityText(Vector<AccessibilityText>&) override; + explicit AccessibilityAttachment(RenderAttachment*); +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityAttachment, isAttachmentElement()) + +#endif // ENABLE(ATTACHMENT_ELEMENT) diff --git a/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp index 5f3f2fab0..96bc1392b 100644 --- a/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp +++ b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -40,8 +40,8 @@ namespace WebCore { using namespace HTMLNames; AccessibilityImageMapLink::AccessibilityImageMapLink() - : m_areaElement(0) - , m_mapElement(0) + : m_areaElement(nullptr) + , m_mapElement(nullptr) { } @@ -49,9 +49,9 @@ AccessibilityImageMapLink::~AccessibilityImageMapLink() { } -PassRefPtr<AccessibilityImageMapLink> AccessibilityImageMapLink::create() +Ref<AccessibilityImageMapLink> AccessibilityImageMapLink::create() { - return adoptRef(new AccessibilityImageMapLink()); + return adoptRef(*new AccessibilityImageMapLink()); } AccessibilityObject* AccessibilityImageMapLink::parentObject() const @@ -60,7 +60,7 @@ AccessibilityObject* AccessibilityImageMapLink::parentObject() const return m_parent; if (!m_mapElement.get() || !m_mapElement->renderer()) - return 0; + return nullptr; return m_mapElement->document().axObjectCache()->getOrCreate(m_mapElement->renderer()); } @@ -140,8 +140,8 @@ RenderElement* AccessibilityImageMapLink::imageMapLinkRenderer() const return nullptr; RenderElement* renderer = nullptr; - if (m_parent && m_parent->isAccessibilityRenderObject()) - renderer = toRenderElement(toAccessibilityRenderObject(m_parent)->renderer()); + if (is<AccessibilityRenderObject>(m_parent)) + renderer = downcast<RenderElement>(downcast<AccessibilityRenderObject>(*m_parent).renderer()); else renderer = m_mapElement->renderer(); diff --git a/Source/WebCore/accessibility/AccessibilityImageMapLink.h b/Source/WebCore/accessibility/AccessibilityImageMapLink.h index d28ff6bfb..d74e30cff 100644 --- a/Source/WebCore/accessibility/AccessibilityImageMapLink.h +++ b/Source/WebCore/accessibility/AccessibilityImageMapLink.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityImageMapLink_h -#define AccessibilityImageMapLink_h +#pragma once #include "AccessibilityMockObject.h" #include "HTMLAreaElement.h" @@ -35,12 +34,9 @@ namespace WebCore { -class AccessibilityImageMapLink : public AccessibilityMockObject { - -private: - AccessibilityImageMapLink(); +class AccessibilityImageMapLink final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityImageMapLink> create(); + static Ref<AccessibilityImageMapLink> create(); virtual ~AccessibilityImageMapLink(); void setHTMLAreaElement(HTMLAreaElement* element) { m_areaElement = element; } @@ -49,40 +45,39 @@ public: void setHTMLMapElement(HTMLMapElement* element) { m_mapElement = element; } HTMLMapElement* mapElement() const { return m_mapElement.get(); } - virtual Node* node() const override { return m_areaElement.get(); } + Node* node() const override { return m_areaElement.get(); } - virtual AccessibilityRole roleValue() const override; - virtual bool isEnabled() const override { return true; } + AccessibilityRole roleValue() const override; + bool isEnabled() const override { return true; } - virtual Element* anchorElement() const override; - virtual Element* actionElement() const override; - virtual URL url() const override; - virtual bool isLink() const override { return true; } - virtual bool isLinked() const override { return true; } - virtual String title() const override; - virtual String accessibilityDescription() const override; - virtual AccessibilityObject* parentObject() const override; + Element* anchorElement() const override; + Element* actionElement() const override; + URL url() const override; + bool isLink() const override { return true; } + bool isLinked() const override { return true; } + String title() const override; + String accessibilityDescription() const override; + AccessibilityObject* parentObject() const override; - virtual String stringValueForMSAA() const override; - virtual String nameForMSAA() const override; + String stringValueForMSAA() const override; + String nameForMSAA() const override; - virtual LayoutRect elementRect() const override; - -private: - RefPtr<HTMLAreaElement> m_areaElement; - RefPtr<HTMLMapElement> m_mapElement; + LayoutRect elementRect() const override; - virtual void detachFromParent() override; +private: + AccessibilityImageMapLink(); - virtual Path elementPath() const override; + void detachFromParent() override; + Path elementPath() const override; RenderElement* imageMapLinkRenderer() const; - virtual void accessibilityText(Vector<AccessibilityText>&) override; - virtual bool isImageMapLink() const override { return true; } - virtual bool supportsPath() const override { return true; } -}; + void accessibilityText(Vector<AccessibilityText>&) override; + bool isImageMapLink() const override { return true; } + bool supportsPath() const override { return true; } -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityImageMapLink, isImageMapLink()) + RefPtr<HTMLAreaElement> m_areaElement; + RefPtr<HTMLMapElement> m_mapElement; +}; } // namespace WebCore -#endif // AccessibilityImageMapLink_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityImageMapLink, isImageMapLink()) diff --git a/Source/WebCore/accessibility/AccessibilityLabel.cpp b/Source/WebCore/accessibility/AccessibilityLabel.cpp new file mode 100644 index 000000000..c2d503884 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityLabel.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AccessibilityLabel.h" + +#include "AXObjectCache.h" +#include "HTMLNames.h" + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityLabel::AccessibilityLabel(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityLabel::~AccessibilityLabel() +{ +} + +Ref<AccessibilityLabel> AccessibilityLabel::create(RenderObject* renderer) +{ + return adoptRef(*new AccessibilityLabel(renderer)); +} + +bool AccessibilityLabel::computeAccessibilityIsIgnored() const +{ + return accessibilityIsIgnoredByDefault(); +} + +String AccessibilityLabel::stringValue() const +{ + if (containsOnlyStaticText()) + return textUnderElement(); + return AccessibilityNodeObject::stringValue(); +} + +static bool childrenContainOnlyStaticText(const AccessibilityObject::AccessibilityChildrenVector& children) +{ + if (!children.size()) + return false; + for (const auto& child : children) { + if (child->roleValue() == StaticTextRole) + continue; + if (child->roleValue() == GroupRole) { + if (!childrenContainOnlyStaticText(child->children())) + return false; + } else + return false; + } + return true; +} + +bool AccessibilityLabel::containsOnlyStaticText() const +{ + if (m_containsOnlyStaticTextDirty) + return childrenContainOnlyStaticText(m_children); + return m_containsOnlyStaticText; +} + +void AccessibilityLabel::updateChildrenIfNecessary() +{ + AccessibilityRenderObject::updateChildrenIfNecessary(); + if (m_containsOnlyStaticTextDirty) + m_containsOnlyStaticText = childrenContainOnlyStaticText(m_children); + m_containsOnlyStaticTextDirty = false; +} + +void AccessibilityLabel::clearChildren() +{ + AccessibilityRenderObject::clearChildren(); + m_containsOnlyStaticText = false; + m_containsOnlyStaticTextDirty = false; +} + +void AccessibilityLabel::insertChild(AccessibilityObject* object, unsigned index) +{ + AccessibilityRenderObject::insertChild(object, index); + m_containsOnlyStaticTextDirty = true; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityLabel.h b/Source/WebCore/accessibility/AccessibilityLabel.h new file mode 100644 index 000000000..ed75f66c4 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityLabel.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#pragma once + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityLabel final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityLabel> create(RenderObject*); + virtual ~AccessibilityLabel(); + + bool containsOnlyStaticText() const; + +private: + explicit AccessibilityLabel(RenderObject*); + bool computeAccessibilityIsIgnored() const final; + AccessibilityRole roleValue() const final { return LabelRole; } + bool isLabel() const final { return true; } + String stringValue() const final; + void updateChildrenIfNecessary() final; + void clearChildren() final; + void insertChild(AccessibilityObject*, unsigned) final; + bool m_containsOnlyStaticTextDirty : 1; + bool m_containsOnlyStaticText : 1; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityLabel, isLabel()) diff --git a/Source/WebCore/accessibility/AccessibilityList.cpp b/Source/WebCore/accessibility/AccessibilityList.cpp index 69aa71f1e..da130a2b4 100644 --- a/Source/WebCore/accessibility/AccessibilityList.cpp +++ b/Source/WebCore/accessibility/AccessibilityList.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -30,7 +30,10 @@ #include "AccessibilityList.h" #include "AXObjectCache.h" +#include "HTMLElement.h" #include "HTMLNames.h" +#include "PseudoElement.h" +#include "RenderListItem.h" #include "RenderObject.h" namespace WebCore { @@ -46,9 +49,9 @@ AccessibilityList::~AccessibilityList() { } -PassRefPtr<AccessibilityList> AccessibilityList::create(RenderObject* renderer) +Ref<AccessibilityList> AccessibilityList::create(RenderObject* renderer) { - return adoptRef(new AccessibilityList(renderer)); + return adoptRef(*new AccessibilityList(renderer)); } bool AccessibilityList::computeAccessibilityIsIgnored() const @@ -91,15 +94,108 @@ bool AccessibilityList::isDescriptionList() const return false; Node* node = m_renderer->node(); - return node && node->hasTagName(dlTag); + return node && node->hasTagName(dlTag); } -AccessibilityRole AccessibilityList::roleValue() const +bool AccessibilityList::childHasPseudoVisibleListItemMarkers(RenderObject* listItem) { - if (isDescriptionList()) + // Check if the list item has a pseudo-element that should be accessible (e.g. an image or text) + Element* listItemElement = downcast<Element>(listItem->node()); + if (!listItemElement || !listItemElement->beforePseudoElement()) + return false; + + AccessibilityObject* axObj = axObjectCache()->getOrCreate(listItemElement->beforePseudoElement()->renderer()); + if (!axObj) + return false; + + if (!axObj->accessibilityIsIgnored()) + return true; + + for (const auto& child : axObj->children()) { + if (!child->accessibilityIsIgnored()) + return true; + } + + // Platforms which expose rendered text content through the parent element will treat + // those renderers as "ignored" objects. +#if PLATFORM(GTK) + String text = axObj->textUnderElement(); + return !text.isEmpty() && !text.containsOnlyWhitespace(); +#else + return false; +#endif +} + +AccessibilityRole AccessibilityList::determineAccessibilityRole() +{ + m_ariaRole = determineAriaRoleAttribute(); + + // Directory is mapped to list for now, but does not adhere to the same heuristics. + if (ariaRoleAttribute() == DirectoryRole) + return ListRole; + + // Heuristic to determine if this list is being used for layout or for content. + // 1. If it's a named list, like ol or aria=list, then it's a list. + // 1a. Unless the list has no children, then it's not a list. + // 2. If it displays visible list markers, it's a list. + // 3. If it does not display list markers and has only one child, it's not a list. + // 4. If it does not have any listitem children, it's not a list. + // 5. Otherwise it's a list (for now). + + AccessibilityRole role = ListRole; + + // Temporarily set role so that we can query children (otherwise canHaveChildren returns false). + m_role = role; + + unsigned listItemCount = 0; + bool hasVisibleMarkers = false; + + const auto& children = this->children(); + // DescriptionLists are always semantically a description list, so do not apply heuristics. + if (isDescriptionList() && children.size()) return DescriptionListRole; + + for (const auto& child : children) { + if (child->ariaRoleAttribute() == ListItemRole) + listItemCount++; + else if (child->roleValue() == ListItemRole) { + RenderObject* listItem = child->renderer(); + if (!listItem) + continue; + + // Rendered list items always count. + if (listItem->isListItem()) { + if (!hasVisibleMarkers && (listItem->style().listStyleType() != NoneListStyle || listItem->style().listStyleImage() || childHasPseudoVisibleListItemMarkers(listItem))) + hasVisibleMarkers = true; + listItemCount++; + } else if (listItem->node() && listItem->node()->hasTagName(liTag)) { + // Inline elements that are in a list with an explicit role should also count. + if (m_ariaRole == ListRole) + listItemCount++; + + if (childHasPseudoVisibleListItemMarkers(listItem)) { + hasVisibleMarkers = true; + listItemCount++; + } + } + } + } + + // Non <ul> lists and ARIA lists only need to have one child. + // <ul>, <ol> lists need to have visible markers. + if (ariaRoleAttribute() != UnknownRole) { + if (!listItemCount) + role = GroupRole; + } else if (!hasVisibleMarkers) + role = GroupRole; + + return role; +} - return ListRole; +AccessibilityRole AccessibilityList::roleValue() const +{ + ASSERT(m_role != UnknownRole); + return m_role; } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityList.h b/Source/WebCore/accessibility/AccessibilityList.h index 5e844ff0c..908979f4a 100644 --- a/Source/WebCore/accessibility/AccessibilityList.h +++ b/Source/WebCore/accessibility/AccessibilityList.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,33 +26,31 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityList_h -#define AccessibilityList_h +#pragma once #include "AccessibilityRenderObject.h" namespace WebCore { -class AccessibilityList : public AccessibilityRenderObject { - -private: - explicit AccessibilityList(RenderObject*); +class AccessibilityList final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityList> create(RenderObject*); + static Ref<AccessibilityList> create(RenderObject*); virtual ~AccessibilityList(); - virtual bool isList() const override { return true; } bool isUnorderedList() const; bool isOrderedList() const; bool isDescriptionList() const; - virtual AccessibilityRole roleValue() const override final; + AccessibilityRole roleValue() const override; + private: - virtual bool computeAccessibilityIsIgnored() const override; + explicit AccessibilityList(RenderObject*); + bool isList() const override { return true; } + bool computeAccessibilityIsIgnored() const override; + AccessibilityRole determineAccessibilityRole() override; + bool childHasPseudoVisibleListItemMarkers(RenderObject*); }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityList, isList()) - } // namespace WebCore -#endif // AccessibilityList_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityList, isList()) diff --git a/Source/WebCore/accessibility/AccessibilityListBox.cpp b/Source/WebCore/accessibility/AccessibilityListBox.cpp index f9bb388ef..7fc68a9b9 100644 --- a/Source/WebCore/accessibility/AccessibilityListBox.cpp +++ b/Source/WebCore/accessibility/AccessibilityListBox.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -49,10 +49,10 @@ AccessibilityListBox::AccessibilityListBox(RenderObject* renderer) AccessibilityListBox::~AccessibilityListBox() { } - -PassRefPtr<AccessibilityListBox> AccessibilityListBox::create(RenderObject* renderer) + +Ref<AccessibilityListBox> AccessibilityListBox::create(RenderObject* renderer) { - return adoptRef(new AccessibilityListBox(renderer)); + return adoptRef(*new AccessibilityListBox(renderer)); } bool AccessibilityListBox::canSetSelectedChildrenAttribute() const @@ -61,7 +61,7 @@ bool AccessibilityListBox::canSetSelectedChildrenAttribute() const if (!selectNode) return false; - return !toHTMLSelectElement(selectNode)->isDisabledFormControl(); + return !downcast<HTMLSelectElement>(*selectNode).isDisabledFormControl(); } void AccessibilityListBox::addChildren() @@ -72,7 +72,7 @@ void AccessibilityListBox::addChildren() m_haveChildren = true; - for (const auto& listItem : toHTMLSelectElement(selectNode)->listItems()) { + for (const auto& listItem : downcast<HTMLSelectElement>(*selectNode).listItems()) { // The cast to HTMLElement below is safe because the only other possible listItem type // would be a WMLElement, but WML builds don't use accessibility features at all. AccessibilityObject* listOption = listBoxOptionAccessibilityObject(listItem); @@ -92,16 +92,16 @@ void AccessibilityListBox::setSelectedChildren(const AccessibilityChildrenVector // disable any selected options for (const auto& child : m_children) { - AccessibilityListBoxOption* listBoxOption = toAccessibilityListBoxOption(child.get()); - if (listBoxOption->isSelected()) - listBoxOption->setSelected(false); + auto& listBoxOption = downcast<AccessibilityListBoxOption>(*child); + if (listBoxOption.isSelected()) + listBoxOption.setSelected(false); } for (const auto& obj : children) { if (obj->roleValue() != ListBoxOptionRole) continue; - toAccessibilityListBoxOption(obj.get())->setSelected(true); + downcast<AccessibilityListBoxOption>(*obj).setSelected(true); } } @@ -113,7 +113,7 @@ void AccessibilityListBox::selectedChildren(AccessibilityChildrenVector& result) addChildren(); for (const auto& child : m_children) { - if (toAccessibilityListBoxOption(child.get())->isSelected()) + if (downcast<AccessibilityListBoxOption>(*child).isSelected()) result.append(child.get()); } } @@ -127,7 +127,7 @@ void AccessibilityListBox::visibleChildren(AccessibilityChildrenVector& result) unsigned length = m_children.size(); for (unsigned i = 0; i < length; i++) { - if (toRenderListBox(m_renderer)->listIndexIsVisible(i)) + if (downcast<RenderListBox>(*m_renderer).listIndexIsVisible(i)) result.append(m_children[i]); } } @@ -136,12 +136,12 @@ AccessibilityObject* AccessibilityListBox::listBoxOptionAccessibilityObject(HTML { // skip hr elements if (!element || element->hasTagName(hrTag)) - return 0; + return nullptr; - AccessibilityObject* listBoxObject = m_renderer->document().axObjectCache()->getOrCreate(ListBoxOptionRole); - toAccessibilityListBoxOption(listBoxObject)->setHTMLElement(element); + AccessibilityObject& listBoxObject = *m_renderer->document().axObjectCache()->getOrCreate(ListBoxOptionRole); + downcast<AccessibilityListBoxOption>(listBoxObject).setHTMLElement(element); - return listBoxObject; + return &listBoxObject; } AccessibilityObject* AccessibilityListBox::elementAccessibilityHitTest(const IntPoint& point) const @@ -149,18 +149,18 @@ AccessibilityObject* AccessibilityListBox::elementAccessibilityHitTest(const Int // the internal HTMLSelectElement methods for returning a listbox option at a point // ignore optgroup elements. if (!m_renderer) - return 0; + return nullptr; Node* node = m_renderer->node(); if (!node) - return 0; + return nullptr; LayoutRect parentRect = boundingBoxRect(); - AccessibilityObject* listBoxOption = 0; + AccessibilityObject* listBoxOption = nullptr; unsigned length = m_children.size(); - for (unsigned i = 0; i < length; i++) { - LayoutRect rect = toRenderListBox(m_renderer)->itemBoundingBoxRect(parentRect.location(), i); + for (unsigned i = 0; i < length; ++i) { + LayoutRect rect = downcast<RenderListBox>(*m_renderer).itemBoundingBoxRect(parentRect.location(), i); // The cast to HTMLElement below is safe because the only other possible listItem type // would be a WMLElement, but WML builds don't use accessibility features at all. if (rect.contains(point)) { diff --git a/Source/WebCore/accessibility/AccessibilityListBox.h b/Source/WebCore/accessibility/AccessibilityListBox.h index 2f56bf60d..ab91d9376 100644 --- a/Source/WebCore/accessibility/AccessibilityListBox.h +++ b/Source/WebCore/accessibility/AccessibilityListBox.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,39 +26,34 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityListBox_h -#define AccessibilityListBox_h +#pragma once #include "AccessibilityRenderObject.h" namespace WebCore { -class AccessibilityListBox : public AccessibilityRenderObject { - -private: - explicit AccessibilityListBox(RenderObject*); +class AccessibilityListBox final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityListBox> create(RenderObject*); + static Ref<AccessibilityListBox> create(RenderObject*); virtual ~AccessibilityListBox(); - virtual bool isListBox() const override { return true; } - - virtual bool canSetSelectedChildrenAttribute() const override; + bool canSetSelectedChildrenAttribute() const override; void setSelectedChildren(const AccessibilityChildrenVector&); - virtual AccessibilityRole roleValue() const override { return ListBoxRole; } + AccessibilityRole roleValue() const override { return ListBoxRole; } - virtual void selectedChildren(AccessibilityChildrenVector&) override; - virtual void visibleChildren(AccessibilityChildrenVector&) override; + void selectedChildren(AccessibilityChildrenVector&) override; + void visibleChildren(AccessibilityChildrenVector&) override; - virtual void addChildren() override; + void addChildren() override; -private: +private: + explicit AccessibilityListBox(RenderObject*); + + bool isNativeListBox() const override { return true; } AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; + AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; }; - -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityListBox, isListBox()) } // namespace WebCore -#endif // AccessibilityListBox_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityListBox, isNativeListBox()) diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp b/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp index e403e6c3d..6a63894ef 100644 --- a/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp +++ b/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -46,7 +46,7 @@ namespace WebCore { using namespace HTMLNames; AccessibilityListBoxOption::AccessibilityListBoxOption() - : m_optionElement(0) + : m_optionElement(nullptr) { } @@ -54,23 +54,20 @@ AccessibilityListBoxOption::~AccessibilityListBoxOption() { } -PassRefPtr<AccessibilityListBoxOption> AccessibilityListBoxOption::create() +Ref<AccessibilityListBoxOption> AccessibilityListBoxOption::create() { - return adoptRef(new AccessibilityListBoxOption()); + return adoptRef(*new AccessibilityListBoxOption()); } bool AccessibilityListBoxOption::isEnabled() const { - if (!m_optionElement) - return false; - - if (isHTMLOptGroupElement(m_optionElement)) + if (is<HTMLOptGroupElement>(m_optionElement)) return false; - if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_disabledAttr), "true")) return false; - if (m_optionElement->hasAttribute(disabledAttr)) + if (m_optionElement->hasAttributeWithoutSynchronization(disabledAttr)) return false; return true; @@ -78,13 +75,10 @@ bool AccessibilityListBoxOption::isEnabled() const bool AccessibilityListBoxOption::isSelected() const { - if (!m_optionElement) + if (!is<HTMLOptionElement>(m_optionElement)) return false; - if (!isHTMLOptionElement(m_optionElement)) - return false; - - return toHTMLOptionElement(m_optionElement)->selected(); + return downcast<HTMLOptionElement>(*m_optionElement).selected(); } bool AccessibilityListBoxOption::isSelectedOptionActive() const @@ -106,14 +100,14 @@ LayoutRect AccessibilityListBoxOption::elementRect() const if (!listBoxParentNode) return rect; - RenderObject* listBoxRenderer = listBoxParentNode->renderer(); + RenderElement* listBoxRenderer = listBoxParentNode->renderer(); if (!listBoxRenderer) return rect; LayoutRect parentRect = listBoxRenderer->document().axObjectCache()->getOrCreate(listBoxRenderer)->boundingBoxRect(); int index = listBoxOptionIndex(); if (index != -1) - rect = toRenderListBox(listBoxRenderer)->itemBoundingBoxRect(parentRect.location(), index); + rect = downcast<RenderListBox>(*listBoxRenderer).itemBoundingBoxRect(parentRect.location(), index); return rect; } @@ -131,10 +125,7 @@ bool AccessibilityListBoxOption::computeAccessibilityIsIgnored() const bool AccessibilityListBoxOption::canSetSelectedAttribute() const { - if (!m_optionElement) - return false; - - if (!isHTMLOptionElement(m_optionElement)) + if (!is<HTMLOptionElement>(m_optionElement)) return false; if (m_optionElement->isDisabledFormControl()) @@ -156,11 +147,11 @@ String AccessibilityListBoxOption::stringValue() const if (!ariaLabel.isNull()) return ariaLabel; - if (isHTMLOptionElement(m_optionElement)) - return toHTMLOptionElement(m_optionElement)->text(); + if (is<HTMLOptionElement>(*m_optionElement)) + return downcast<HTMLOptionElement>(*m_optionElement).label(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->groupLabelText(); + if (is<HTMLOptGroupElement>(*m_optionElement)) + return downcast<HTMLOptGroupElement>(*m_optionElement).groupLabelText(); return String(); } @@ -174,7 +165,7 @@ AccessibilityObject* AccessibilityListBoxOption::parentObject() const { HTMLSelectElement* parentNode = listBoxOptionParentNode(); if (!parentNode) - return 0; + return nullptr; return m_optionElement->document().axObjectCache()->getOrCreate(parentNode); } @@ -200,15 +191,15 @@ void AccessibilityListBoxOption::setSelected(bool selected) HTMLSelectElement* AccessibilityListBoxOption::listBoxOptionParentNode() const { if (!m_optionElement) - return 0; + return nullptr; - if (isHTMLOptionElement(m_optionElement)) - return toHTMLOptionElement(m_optionElement)->ownerSelectElement(); + if (is<HTMLOptionElement>(*m_optionElement)) + return downcast<HTMLOptionElement>(*m_optionElement).ownerSelectElement(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->ownerSelectElement(); + if (is<HTMLOptGroupElement>(*m_optionElement)) + return downcast<HTMLOptGroupElement>(*m_optionElement).ownerSelectElement(); - return 0; + return nullptr; } int AccessibilityListBoxOption::listBoxOptionIndex() const diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.h b/Source/WebCore/accessibility/AccessibilityListBoxOption.h index ad1aecfaa..9d3ecd2e5 100644 --- a/Source/WebCore/accessibility/AccessibilityListBoxOption.h +++ b/Source/WebCore/accessibility/AccessibilityListBoxOption.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityListBoxOption_h -#define AccessibilityListBoxOption_h +#pragma once #include "AccessibilityObject.h" #include "HTMLElement.h" @@ -37,46 +36,42 @@ namespace WebCore { class AccessibilityListBox; class Element; -class HTMLElement; class HTMLSelectElement; -class AccessibilityListBoxOption : public AccessibilityObject { - -private: - AccessibilityListBoxOption(); +class AccessibilityListBoxOption final : public AccessibilityObject { public: - static PassRefPtr<AccessibilityListBoxOption> create(); + static Ref<AccessibilityListBoxOption> create(); virtual ~AccessibilityListBoxOption(); void setHTMLElement(HTMLElement* element) { m_optionElement = element; } - virtual AccessibilityRole roleValue() const override { return ListBoxOptionRole; } - virtual bool isSelected() const override; - virtual bool isEnabled() const override; - virtual bool isSelectedOptionActive() const override; - virtual String stringValue() const override; - virtual Element* actionElement() const override; - virtual Node* node() const override { return m_optionElement; } - virtual void setSelected(bool) override; - virtual bool canSetSelectedAttribute() const override; + AccessibilityRole roleValue() const override { return ListBoxOptionRole; } + bool isSelected() const override; + bool isEnabled() const override; + bool isSelectedOptionActive() const override; + String stringValue() const override; + Element* actionElement() const override; + Node* node() const override { return m_optionElement; } + void setSelected(bool) override; + bool canSetSelectedAttribute() const override; - virtual LayoutRect elementRect() const override; - virtual AccessibilityObject* parentObject() const override; - virtual bool isListBoxOption() const override final { return true; } + LayoutRect elementRect() const override; + AccessibilityObject* parentObject() const override; private: - HTMLElement* m_optionElement; - - virtual bool canHaveChildren() const override { return false; } + AccessibilityListBoxOption(); + + bool isListBoxOption() const override { return true; } + bool canHaveChildren() const override { return false; } HTMLSelectElement* listBoxOptionParentNode() const; int listBoxOptionIndex() const; IntRect listBoxOptionRect() const; AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; - virtual bool computeAccessibilityIsIgnored() const override; -}; + bool computeAccessibilityIsIgnored() const override; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityListBoxOption, isListBoxOption()) + HTMLElement* m_optionElement; +}; } // namespace WebCore -#endif // AccessibilityListBoxOption_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityListBoxOption, isListBoxOption()) diff --git a/Source/WebCore/accessibility/AccessibilityMathMLElement.cpp b/Source/WebCore/accessibility/AccessibilityMathMLElement.cpp new file mode 100644 index 000000000..e31beafae --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMathMLElement.cpp @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2016 Igalia, S.L. + * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(MATHML) +#include "AccessibilityMathMLElement.h" + +#include "AXObjectCache.h" +#include "MathMLNames.h" + +namespace WebCore { + +AccessibilityMathMLElement::AccessibilityMathMLElement(RenderObject* renderer, bool isAnonymousOperator) + : AccessibilityRenderObject(renderer) + , m_isAnonymousOperator(isAnonymousOperator) +{ +} + +AccessibilityMathMLElement::~AccessibilityMathMLElement() +{ +} + +Ref<AccessibilityMathMLElement> AccessibilityMathMLElement::create(RenderObject* renderer, bool isAnonymousOperator) +{ + return adoptRef(*new AccessibilityMathMLElement(renderer, isAnonymousOperator)); +} + +AccessibilityRole AccessibilityMathMLElement::determineAccessibilityRole() +{ + if (!m_renderer) + return UnknownRole; + + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; + + Node* node = m_renderer->node(); + if (node && node->hasTagName(MathMLNames::mathTag)) + return DocumentMathRole; + + // It's not clear which role a platform should choose for a math element. + // Declaring a math element role should give flexibility to platforms to choose. + return MathElementRole; +} + +String AccessibilityMathMLElement::textUnderElement(AccessibilityTextUnderElementMode mode) const +{ + if (m_isAnonymousOperator) { + UChar operatorChar = downcast<RenderMathMLOperator>(*m_renderer).textContent(); + return operatorChar ? String(&operatorChar, 1) : String(); + } + + return AccessibilityRenderObject::textUnderElement(mode); +} + +String AccessibilityMathMLElement::stringValue() const +{ + if (m_isAnonymousOperator) + return textUnderElement(); + + return AccessibilityRenderObject::stringValue(); +} + +bool AccessibilityMathMLElement::isIgnoredElementWithinMathTree() const +{ + if (m_isAnonymousOperator) + return false; + + // Only math elements that we explicitly recognize should be included + // We don't want things like <mstyle> to appear in the tree. + if (isMathFraction() || isMathFenced() || isMathSubscriptSuperscript() || isMathRow() + || isMathUnderOver() || isMathRoot() || isMathText() || isMathNumber() + || isMathOperator() || isMathFenceOperator() || isMathSeparatorOperator() + || isMathIdentifier() || isMathTable() || isMathTableRow() || isMathTableCell() || isMathMultiscript()) + return false; + + return true; +} + +bool AccessibilityMathMLElement::isMathFraction() const +{ + return m_renderer && m_renderer->isRenderMathMLFraction(); +} + +bool AccessibilityMathMLElement::isMathFenced() const +{ + return m_renderer && m_renderer->isRenderMathMLFenced(); +} + +bool AccessibilityMathMLElement::isMathSubscriptSuperscript() const +{ + return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript(); +} + +bool AccessibilityMathMLElement::isMathRow() const +{ + return m_renderer && m_renderer->isRenderMathMLRow() && !isMathRoot(); +} + +bool AccessibilityMathMLElement::isMathUnderOver() const +{ + return m_renderer && m_renderer->isRenderMathMLUnderOver(); +} + +bool AccessibilityMathMLElement::isMathSquareRoot() const +{ + return m_renderer && m_renderer->isRenderMathMLSquareRoot(); +} + +bool AccessibilityMathMLElement::isMathToken() const +{ + return m_renderer && m_renderer->isRenderMathMLToken(); +} + +bool AccessibilityMathMLElement::isMathRoot() const +{ + return m_renderer && m_renderer->isRenderMathMLRoot(); +} + +bool AccessibilityMathMLElement::isMathOperator() const +{ + return m_renderer && m_renderer->isRenderMathMLOperator(); +} + +bool AccessibilityMathMLElement::isAnonymousMathOperator() const +{ + return m_isAnonymousOperator; +} + +bool AccessibilityMathMLElement::isMathFenceOperator() const +{ + if (!is<RenderMathMLOperator>(m_renderer)) + return false; + + return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Fence); +} + +bool AccessibilityMathMLElement::isMathSeparatorOperator() const +{ + if (!is<RenderMathMLOperator>(m_renderer)) + return false; + + return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Separator); +} + +bool AccessibilityMathMLElement::isMathText() const +{ + return node() && (node()->hasTagName(MathMLNames::mtextTag) || hasTagName(MathMLNames::msTag)); +} + +bool AccessibilityMathMLElement::isMathNumber() const +{ + return node() && node()->hasTagName(MathMLNames::mnTag); +} + +bool AccessibilityMathMLElement::isMathIdentifier() const +{ + return node() && node()->hasTagName(MathMLNames::miTag); +} + +bool AccessibilityMathMLElement::isMathMultiscript() const +{ + return node() && node()->hasTagName(MathMLNames::mmultiscriptsTag); +} + +bool AccessibilityMathMLElement::isMathTable() const +{ + return node() && node()->hasTagName(MathMLNames::mtableTag); +} + +bool AccessibilityMathMLElement::isMathTableRow() const +{ + return node() && (node()->hasTagName(MathMLNames::mtrTag) || hasTagName(MathMLNames::mlabeledtrTag)); +} + +bool AccessibilityMathMLElement::isMathTableCell() const +{ + return node() && node()->hasTagName(MathMLNames::mtdTag); +} + +bool AccessibilityMathMLElement::isMathScriptObject(AccessibilityMathScriptObjectType type) const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent) + return false; + + return type == Subscript ? this == parent->mathSubscriptObject() : this == parent->mathSuperscriptObject(); +} + +bool AccessibilityMathMLElement::isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType type) const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isMathMultiscript()) + return false; + + // The scripts in a MathML <mmultiscripts> element consist of one or more + // subscript, superscript pairs. In order to determine if this object is + // a scripted token, we need to examine each set of pairs to see if the + // this token is present and in the position corresponding with the type. + + AccessibilityMathMultiscriptPairs pairs; + if (type == PreSubscript || type == PreSuperscript) + parent->mathPrescripts(pairs); + else + parent->mathPostscripts(pairs); + + for (const auto& pair : pairs) { + if (this == pair.first) + return (type == PreSubscript || type == PostSubscript); + if (this == pair.second) + return (type == PreSuperscript || type == PostSuperscript); + } + + return false; +} + +AccessibilityObject* AccessibilityMathMLElement::mathRadicandObject() +{ + if (!isMathRoot()) + return nullptr; + + // For MathSquareRoot, we actually return the first child of the base. + // See also https://webkit.org/b/146452 + const auto& children = this->children(); + if (children.size() < 1) + return nullptr; + + return children[0].get(); +} + +AccessibilityObject* AccessibilityMathMLElement::mathRootIndexObject() +{ + if (!isMathRoot() || isMathSquareRoot()) + return nullptr; + + const auto& children = this->children(); + if (children.size() < 2) + return nullptr; + + return children[1].get(); +} + +AccessibilityObject* AccessibilityMathMLElement::mathNumeratorObject() +{ + if (!isMathFraction()) + return nullptr; + + const auto& children = this->children(); + if (children.size() != 2) + return nullptr; + + return children[0].get(); +} + +AccessibilityObject* AccessibilityMathMLElement::mathDenominatorObject() +{ + if (!isMathFraction()) + return nullptr; + + const auto& children = this->children(); + if (children.size() != 2) + return nullptr; + + return children[1].get(); +} + +AccessibilityObject* AccessibilityMathMLElement::mathUnderObject() +{ + if (!isMathUnderOver() || !node()) + return nullptr; + + const auto& children = this->children(); + if (children.size() < 2) + return nullptr; + + if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag)) + return children[1].get(); + + return nullptr; +} + +AccessibilityObject* AccessibilityMathMLElement::mathOverObject() +{ + if (!isMathUnderOver() || !node()) + return nullptr; + + const auto& children = this->children(); + if (children.size() < 2) + return nullptr; + + if (node()->hasTagName(MathMLNames::moverTag)) + return children[1].get(); + if (node()->hasTagName(MathMLNames::munderoverTag)) + return children[2].get(); + + return nullptr; +} + +AccessibilityObject* AccessibilityMathMLElement::mathBaseObject() +{ + if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript()) + return nullptr; + + const auto& children = this->children(); + // The base object in question is always the first child. + if (children.size() > 0) + return children[0].get(); + + return nullptr; +} + +AccessibilityObject* AccessibilityMathMLElement::mathSubscriptObject() +{ + if (!isMathSubscriptSuperscript() || !node()) + return nullptr; + + const auto& children = this->children(); + if (children.size() < 2) + return nullptr; + + if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag)) + return children[1].get(); + + return nullptr; +} + +AccessibilityObject* AccessibilityMathMLElement::mathSuperscriptObject() +{ + if (!isMathSubscriptSuperscript() || !node()) + return nullptr; + + const auto& children = this->children(); + unsigned count = children.size(); + + if (count >= 2 && node()->hasTagName(MathMLNames::msupTag)) + return children[1].get(); + + if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag)) + return children[2].get(); + + return nullptr; +} + +String AccessibilityMathMLElement::mathFencedOpenString() const +{ + if (!isMathFenced()) + return String(); + + return getAttribute(MathMLNames::openAttr); +} + +String AccessibilityMathMLElement::mathFencedCloseString() const +{ + if (!isMathFenced()) + return String(); + + return getAttribute(MathMLNames::closeAttr); +} + +void AccessibilityMathMLElement::mathPrescripts(AccessibilityMathMultiscriptPairs& prescripts) +{ + if (!isMathMultiscript() || !node()) + return; + + bool foundPrescript = false; + std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair; + for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { + if (foundPrescript) { + AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); + if (axChild && axChild->isMathElement()) { + if (!prescriptPair.first) + prescriptPair.first = axChild; + else { + prescriptPair.second = axChild; + prescripts.append(prescriptPair); + prescriptPair.first = nullptr; + prescriptPair.second = nullptr; + } + } + } else if (child->hasTagName(MathMLNames::mprescriptsTag)) + foundPrescript = true; + } + + // Handle the odd number of pre scripts case. + if (prescriptPair.first) + prescripts.append(prescriptPair); +} + +void AccessibilityMathMLElement::mathPostscripts(AccessibilityMathMultiscriptPairs& postscripts) +{ + if (!isMathMultiscript() || !node()) + return; + + // In Multiscripts, the post-script elements start after the first element (which is the base) + // and continue until a <mprescripts> tag is found + std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair; + bool foundBaseElement = false; + for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { + if (child->hasTagName(MathMLNames::mprescriptsTag)) + break; + + AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); + if (axChild && axChild->isMathElement()) { + if (!foundBaseElement) + foundBaseElement = true; + else if (!postscriptPair.first) + postscriptPair.first = axChild; + else { + postscriptPair.second = axChild; + postscripts.append(postscriptPair); + postscriptPair.first = nullptr; + postscriptPair.second = nullptr; + } + } + } + + // Handle the odd number of post scripts case. + if (postscriptPair.first) + postscripts.append(postscriptPair); +} + +int AccessibilityMathMLElement::mathLineThickness() const +{ + if (!is<RenderMathMLFraction>(m_renderer)) + return -1; + + return downcast<RenderMathMLFraction>(*m_renderer).relativeLineThickness(); +} + +} // namespace WebCore + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/accessibility/AccessibilityMathMLElement.h b/Source/WebCore/accessibility/AccessibilityMathMLElement.h new file mode 100644 index 000000000..580b859cb --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityMathMLElement.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 Igalia, S.L. + * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#if ENABLE(MATHML) + +#include "AccessibilityRenderObject.h" +#include "RenderMathMLBlock.h" +#include "RenderMathMLFraction.h" +#include "RenderMathMLMath.h" +#include "RenderMathMLOperator.h" +#include "RenderMathMLRoot.h" + +namespace WebCore { + +class AccessibilityMathMLElement : public AccessibilityRenderObject { + +public: + static Ref<AccessibilityMathMLElement> create(RenderObject*, bool isAnonymousOperator); + virtual ~AccessibilityMathMLElement(); + +protected: + explicit AccessibilityMathMLElement(RenderObject*, bool isAnonymousOperator); + +private: + AccessibilityRole determineAccessibilityRole() final; + String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; + String stringValue() const override; + bool isIgnoredElementWithinMathTree() const final; + + bool isMathElement() const final { return true; } + + bool isMathFraction() const override; + bool isMathFenced() const override; + bool isMathSubscriptSuperscript() const override; + bool isMathRow() const override; + bool isMathUnderOver() const override; + bool isMathRoot() const override; + bool isMathSquareRoot() const override; + bool isMathText() const override; + bool isMathNumber() const override; + bool isMathOperator() const override; + bool isMathFenceOperator() const override; + bool isMathSeparatorOperator() const override; + bool isMathIdentifier() const override; + bool isMathTable() const override; + bool isMathTableRow() const override; + bool isMathTableCell() const override; + bool isMathMultiscript() const override; + bool isMathToken() const override; + bool isMathScriptObject(AccessibilityMathScriptObjectType) const override; + bool isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType) const override; + + // Generic components. + AccessibilityObject* mathBaseObject() override; + + // Root components. + AccessibilityObject* mathRadicandObject() override; + AccessibilityObject* mathRootIndexObject() override; + + // Fraction components. + AccessibilityObject* mathNumeratorObject() override; + AccessibilityObject* mathDenominatorObject() override; + + // Under over components. + AccessibilityObject* mathUnderObject() override; + AccessibilityObject* mathOverObject() override; + + // Subscript/superscript components. + AccessibilityObject* mathSubscriptObject() override; + AccessibilityObject* mathSuperscriptObject() override; + + // Fenced components. + String mathFencedOpenString() const override; + String mathFencedCloseString() const override; + int mathLineThickness() const override; + bool isAnonymousMathOperator() const override; + + // Multiscripts components. + void mathPrescripts(AccessibilityMathMultiscriptPairs&) override; + void mathPostscripts(AccessibilityMathMultiscriptPairs&) override; + + bool m_isAnonymousOperator; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMathMLElement, isMathElement()) + +#endif // ENABLE(MATHML) diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.cpp b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp index 92fe22104..a59d1682b 100644 --- a/Source/WebCore/accessibility/AccessibilityMediaControls.cpp +++ b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -41,6 +41,7 @@ #include "MediaControlElements.h" #include "RenderObject.h" #include "RenderSlider.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { @@ -52,7 +53,7 @@ AccessibilityMediaControl::AccessibilityMediaControl(RenderObject* renderer) { } -PassRefPtr<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* renderer) { ASSERT(renderer->node()); @@ -68,7 +69,7 @@ PassRefPtr<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* return AccessibilityMediaControlsContainer::create(renderer); default: - return adoptRef(new AccessibilityMediaControl(renderer)); + return adoptRef(*new AccessibilityMediaControl(renderer)); } } @@ -80,23 +81,23 @@ MediaControlElementType AccessibilityMediaControl::controlType() const return mediaControlElementType(renderer()->node()); } -String AccessibilityMediaControl::controlTypeName() const +const String& AccessibilityMediaControl::controlTypeName() const { - DEFINE_STATIC_LOCAL(const String, mediaEnterFullscreenButtonName, (ASCIILiteral("EnterFullscreenButton"))); - DEFINE_STATIC_LOCAL(const String, mediaExitFullscreenButtonName, (ASCIILiteral("ExitFullscreenButton"))); - DEFINE_STATIC_LOCAL(const String, mediaMuteButtonName, (ASCIILiteral("MuteButton"))); - DEFINE_STATIC_LOCAL(const String, mediaPlayButtonName, (ASCIILiteral("PlayButton"))); - DEFINE_STATIC_LOCAL(const String, mediaSeekBackButtonName, (ASCIILiteral("SeekBackButton"))); - DEFINE_STATIC_LOCAL(const String, mediaSeekForwardButtonName, (ASCIILiteral("SeekForwardButton"))); - DEFINE_STATIC_LOCAL(const String, mediaRewindButtonName, (ASCIILiteral("RewindButton"))); - DEFINE_STATIC_LOCAL(const String, mediaReturnToRealtimeButtonName, (ASCIILiteral("ReturnToRealtimeButton"))); - DEFINE_STATIC_LOCAL(const String, mediaUnMuteButtonName, (ASCIILiteral("UnMuteButton"))); - DEFINE_STATIC_LOCAL(const String, mediaPauseButtonName, (ASCIILiteral("PauseButton"))); - DEFINE_STATIC_LOCAL(const String, mediaStatusDisplayName, (ASCIILiteral("StatusDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaCurrentTimeDisplay, (ASCIILiteral("CurrentTimeDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaTimeRemainingDisplay, (ASCIILiteral("TimeRemainingDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaShowClosedCaptionsButtonName, (ASCIILiteral("ShowClosedCaptionsButton"))); - DEFINE_STATIC_LOCAL(const String, mediaHideClosedCaptionsButtonName, (ASCIILiteral("HideClosedCaptionsButton"))); + static NeverDestroyed<const String> mediaEnterFullscreenButtonName(ASCIILiteral("EnterFullscreenButton")); + static NeverDestroyed<const String> mediaExitFullscreenButtonName(ASCIILiteral("ExitFullscreenButton")); + static NeverDestroyed<const String> mediaMuteButtonName(ASCIILiteral("MuteButton")); + static NeverDestroyed<const String> mediaPlayButtonName(ASCIILiteral("PlayButton")); + static NeverDestroyed<const String> mediaSeekBackButtonName(ASCIILiteral("SeekBackButton")); + static NeverDestroyed<const String> mediaSeekForwardButtonName(ASCIILiteral("SeekForwardButton")); + static NeverDestroyed<const String> mediaRewindButtonName(ASCIILiteral("RewindButton")); + static NeverDestroyed<const String> mediaReturnToRealtimeButtonName(ASCIILiteral("ReturnToRealtimeButton")); + static NeverDestroyed<const String> mediaUnMuteButtonName(ASCIILiteral("UnMuteButton")); + static NeverDestroyed<const String> mediaPauseButtonName(ASCIILiteral("PauseButton")); + static NeverDestroyed<const String> mediaStatusDisplayName(ASCIILiteral("StatusDisplay")); + static NeverDestroyed<const String> mediaCurrentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed<const String> mediaTimeRemainingDisplay(ASCIILiteral("TimeRemainingDisplay")); + static NeverDestroyed<const String> mediaShowClosedCaptionsButtonName(ASCIILiteral("ShowClosedCaptionsButton")); + static NeverDestroyed<const String> mediaHideClosedCaptionsButtonName(ASCIILiteral("HideClosedCaptionsButton")); switch (controlType()) { case MediaEnterFullscreenButton: @@ -134,7 +135,7 @@ String AccessibilityMediaControl::controlTypeName() const break; } - return String(); + return nullAtom; } void AccessibilityMediaControl::accessibilityText(Vector<AccessibilityText>& textOrder) @@ -155,7 +156,7 @@ void AccessibilityMediaControl::accessibilityText(Vector<AccessibilityText>& tex String AccessibilityMediaControl::title() const { - DEFINE_STATIC_LOCAL(const String, controlsPanel, (ASCIILiteral("ControlsPanel"))); + static NeverDestroyed<const String> controlsPanel(ASCIILiteral("ControlsPanel")); if (controlType() == MediaControlsPanel) return localizedMediaControlElementString(controlsPanel); @@ -221,9 +222,9 @@ AccessibilityMediaControlsContainer::AccessibilityMediaControlsContainer(RenderO { } -PassRefPtr<AccessibilityObject> AccessibilityMediaControlsContainer::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaControlsContainer::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaControlsContainer(renderer)); + return adoptRef(*new AccessibilityMediaControlsContainer(renderer)); } String AccessibilityMediaControlsContainer::accessibilityDescription() const @@ -242,10 +243,10 @@ bool AccessibilityMediaControlsContainer::controllingVideoElement() const return !element || element->isVideo(); } -const String AccessibilityMediaControlsContainer::elementTypeName() const +const String& AccessibilityMediaControlsContainer::elementTypeName() const { - DEFINE_STATIC_LOCAL(const String, videoElement, (ASCIILiteral("VideoElement"))); - DEFINE_STATIC_LOCAL(const String, audioElement, (ASCIILiteral("AudioElement"))); + static NeverDestroyed<const String> videoElement(ASCIILiteral("VideoElement")); + static NeverDestroyed<const String> audioElement(ASCIILiteral("AudioElement")); if (controllingVideoElement()) return videoElement; @@ -265,24 +266,24 @@ AccessibilityMediaTimeline::AccessibilityMediaTimeline(RenderObject* renderer) { } -PassRefPtr<AccessibilityObject> AccessibilityMediaTimeline::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaTimeline::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaTimeline(renderer)); + return adoptRef(*new AccessibilityMediaTimeline(renderer)); } String AccessibilityMediaTimeline::valueDescription() const { Node* node = m_renderer->node(); - if (!isHTMLInputElement(node)) + if (!is<HTMLInputElement>(*node)) return String(); - float time = toHTMLInputElement(node)->value().toFloat(); + float time = downcast<HTMLInputElement>(*node).value().toFloat(); return localizedMediaTimeDescription(time); } String AccessibilityMediaTimeline::helpText() const { - DEFINE_STATIC_LOCAL(const String, slider, (ASCIILiteral("Slider"))); + static NeverDestroyed<const String> slider(ASCIILiteral("Slider")); return localizedMediaControlElementHelpText(slider); } @@ -295,9 +296,9 @@ AccessibilityMediaTimeDisplay::AccessibilityMediaTimeDisplay(RenderObject* rende { } -PassRefPtr<AccessibilityObject> AccessibilityMediaTimeDisplay::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaTimeDisplay::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaTimeDisplay(renderer)); + return adoptRef(*new AccessibilityMediaTimeDisplay(renderer)); } bool AccessibilityMediaTimeDisplay::computeAccessibilityIsIgnored() const @@ -313,8 +314,8 @@ bool AccessibilityMediaTimeDisplay::computeAccessibilityIsIgnored() const String AccessibilityMediaTimeDisplay::accessibilityDescription() const { - DEFINE_STATIC_LOCAL(const String, currentTimeDisplay, (ASCIILiteral("CurrentTimeDisplay"))); - DEFINE_STATIC_LOCAL(const String, timeRemainingDisplay, (ASCIILiteral("TimeRemainingDisplay"))); + static NeverDestroyed<const String> currentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed<const String> timeRemainingDisplay(ASCIILiteral("TimeRemainingDisplay")); if (controlType() == MediaCurrentTimeDisplay) return localizedMediaControlElementString(currentTimeDisplay); diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.h b/Source/WebCore/accessibility/AccessibilityMediaControls.h index c729609a0..07ff9cb33 100644 --- a/Source/WebCore/accessibility/AccessibilityMediaControls.h +++ b/Source/WebCore/accessibility/AccessibilityMediaControls.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,9 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -#ifndef AccessibilityMediaControls_h -#define AccessibilityMediaControls_h +#pragma once #if ENABLE(VIDEO) @@ -38,84 +36,77 @@ namespace WebCore { class AccessibilityMediaControl : public AccessibilityRenderObject { - public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaControl() { } - virtual AccessibilityRole roleValue() const override; + AccessibilityRole roleValue() const override; - virtual String title() const override; - virtual String accessibilityDescription() const override; - virtual String helpText() const override; + String title() const override; + String accessibilityDescription() const override; + String helpText() const override; protected: explicit AccessibilityMediaControl(RenderObject*); MediaControlElementType controlType() const; - String controlTypeName() const; - virtual bool computeAccessibilityIsIgnored() const override; + const String& controlTypeName() const; + bool computeAccessibilityIsIgnored() const override; private: - virtual void accessibilityText(Vector<AccessibilityText>&) override; + void accessibilityText(Vector<AccessibilityText>&) override; }; -class AccessibilityMediaTimeline : public AccessibilitySlider { - +class AccessibilityMediaTimeline final : public AccessibilitySlider { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaTimeline() { } - virtual bool isMediaTimeline() const override { return true; } - - virtual String helpText() const override; - virtual String valueDescription() const override; + String helpText() const override; + String valueDescription() const override; const AtomicString& getAttribute(const QualifiedName& attribute) const; private: explicit AccessibilityMediaTimeline(RenderObject*); -}; + bool isMediaTimeline() const override { return true; } +}; -class AccessibilityMediaControlsContainer : public AccessibilityMediaControl { +class AccessibilityMediaControlsContainer final : public AccessibilityMediaControl { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaControlsContainer() { } - virtual AccessibilityRole roleValue() const override { return ToolbarRole; } + AccessibilityRole roleValue() const override { return ToolbarRole; } - virtual String helpText() const override; - virtual String accessibilityDescription() const override; + String helpText() const override; + String accessibilityDescription() const override; private: explicit AccessibilityMediaControlsContainer(RenderObject*); bool controllingVideoElement() const; - const String elementTypeName() const; - virtual bool computeAccessibilityIsIgnored() const override; + const String& elementTypeName() const; + bool computeAccessibilityIsIgnored() const override; }; -class AccessibilityMediaTimeDisplay : public AccessibilityMediaControl { - +class AccessibilityMediaTimeDisplay final : public AccessibilityMediaControl { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaTimeDisplay() { } - virtual AccessibilityRole roleValue() const override { return ApplicationTimerRole; } + AccessibilityRole roleValue() const override { return ApplicationTimerRole; } - virtual String stringValue() const override; - virtual String accessibilityDescription() const override; + String stringValue() const override; + String accessibilityDescription() const override; private: explicit AccessibilityMediaTimeDisplay(RenderObject*); - virtual bool isMediaControlLabel() const override { return true; } - virtual bool computeAccessibilityIsIgnored() const override; + bool isMediaControlLabel() const override { return true; } + bool computeAccessibilityIsIgnored() const override; }; - } // namespace WebCore #endif // ENABLE(VIDEO) - -#endif // AccessibilityMediaControls_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.cpp b/Source/WebCore/accessibility/AccessibilityMenuList.cpp index bc5364c20..c0e5cf00e 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuList.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuList.cpp @@ -37,12 +37,12 @@ AccessibilityMenuList::AccessibilityMenuList(RenderMenuList* renderer) { } -PassRefPtr<AccessibilityMenuList> AccessibilityMenuList::create(RenderMenuList* renderer) +Ref<AccessibilityMenuList> AccessibilityMenuList::create(RenderMenuList* renderer) { - return adoptRef(new AccessibilityMenuList(renderer)); + return adoptRef(*new AccessibilityMenuList(renderer)); } -bool AccessibilityMenuList::press() const +bool AccessibilityMenuList::press() { #if !PLATFORM(IOS) RenderMenuList* menuList = static_cast<RenderMenuList*>(m_renderer); @@ -58,20 +58,24 @@ bool AccessibilityMenuList::press() const void AccessibilityMenuList::addChildren() { - m_haveChildren = true; - - AXObjectCache* cache = m_renderer->document().axObjectCache(); - + if (!m_renderer) + return; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + AccessibilityObject* list = cache->getOrCreate(MenuListPopupRole); if (!list) return; - toAccessibilityMockObject(list)->setParent(this); + downcast<AccessibilityMockObject>(*list).setParent(this); if (list->accessibilityIsIgnored()) { cache->remove(list->axObjectID()); return; } + m_haveChildren = true; m_children.append(list); list->addChildren(); @@ -100,7 +104,7 @@ bool AccessibilityMenuList::canSetFocusAttribute() const if (!node()) return false; - return !toElement(node())->isDisabledFormControl(); + return !downcast<Element>(*node()).isDisabledFormControl(); } void AccessibilityMenuList::didUpdateActiveOption(int optionIndex) @@ -111,15 +115,21 @@ void AccessibilityMenuList::didUpdateActiveOption(int optionIndex) const auto& childObjects = children(); if (!childObjects.isEmpty()) { ASSERT(childObjects.size() == 1); - ASSERT(childObjects[0]->isMenuListPopup()); - - if (childObjects[0]->isMenuListPopup()) { - if (AccessibilityMenuListPopup* popup = toAccessibilityMenuListPopup(childObjects[0].get())) - popup->didUpdateActiveOption(optionIndex); - } + ASSERT(is<AccessibilityMenuListPopup>(*childObjects[0])); + + // We might be calling this method in situations where the renderers for list items + // associated to the menu list have not been created (e.g. they might be rendered + // in the UI process, as it's the case in the GTK+ port, which uses GtkMenuItem). + // So, we need to make sure that the accessibility popup object has some children + // before asking it to update its active option, or it will read invalid memory. + // You can reproduce the issue in the GTK+ port by removing this check and running + // accessibility/insert-selected-option-into-select-causes-crash.html (will crash). + int popupChildrenSize = static_cast<int>(childObjects[0]->children().size()); + if (is<AccessibilityMenuListPopup>(*childObjects[0]) && optionIndex >= 0 && optionIndex < popupChildrenSize) + downcast<AccessibilityMenuListPopup>(*childObjects[0]).didUpdateActiveOption(optionIndex); } - cache->postNotification(this, &document.get(), AXObjectCache::AXMenuListValueChanged, TargetElement, PostSynchronously); + cache->postNotification(this, document.ptr(), AXObjectCache::AXMenuListValueChanged, TargetElement, PostSynchronously); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.h b/Source/WebCore/accessibility/AccessibilityMenuList.h index 01d5885d3..8f1123d34 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuList.h +++ b/Source/WebCore/accessibility/AccessibilityMenuList.h @@ -23,40 +23,34 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityMenuList_h -#define AccessibilityMenuList_h +#pragma once #include "AccessibilityRenderObject.h" namespace WebCore { -class AccessibilityMenuList; -class AccessibilityMenuListPopup; -class HTMLOptionElement; class RenderMenuList; -class AccessibilityMenuList : public AccessibilityRenderObject { +class AccessibilityMenuList final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityMenuList> create(RenderMenuList* renderer); + static Ref<AccessibilityMenuList> create(RenderMenuList* renderer); - virtual bool isCollapsed() const override; - virtual bool press() const override; + bool isCollapsed() const override; + bool press() override; void didUpdateActiveOption(int optionIndex); private: explicit AccessibilityMenuList(RenderMenuList*); - virtual bool isMenuList() const override { return true; } - virtual AccessibilityRole roleValue() const override { return PopUpButtonRole; } - virtual bool canSetFocusAttribute() const override; + bool isMenuList() const override { return true; } + AccessibilityRole roleValue() const override { return PopUpButtonRole; } + bool canSetFocusAttribute() const override; - virtual void addChildren() override; - virtual void childrenChanged() override; + void addChildren() override; + void childrenChanged() override; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityMenuList, isMenuList()) - } // namespace WebCore -#endif // AccessibilityMenuList_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuList, isMenuList()) diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp index 296fc71bc..ddb26464a 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp @@ -41,7 +41,7 @@ AccessibilityMenuListOption::AccessibilityMenuListOption() void AccessibilityMenuListOption::setElement(HTMLElement* element) { - ASSERT_ARG(element, isHTMLOptionElement(element)); + ASSERT_ARG(element, is<HTMLOptionElement>(element)); m_element = element; } @@ -54,7 +54,7 @@ bool AccessibilityMenuListOption::isEnabled() const { // isDisabledFormControl() returns true if the parent <select> element is disabled, // which we don't want. - return !toHTMLOptionElement(m_element.get())->ownElementDisabled(); + return !downcast<HTMLOptionElement>(*m_element).ownElementDisabled(); } bool AccessibilityMenuListOption::isVisible() const @@ -75,15 +75,15 @@ bool AccessibilityMenuListOption::isOffScreen() const bool AccessibilityMenuListOption::isSelected() const { - return toHTMLOptionElement(m_element.get())->selected(); + return downcast<HTMLOptionElement>(*m_element).selected(); } -void AccessibilityMenuListOption::setSelected(bool b) +void AccessibilityMenuListOption::setSelected(bool selected) { if (!canSetSelectedAttribute()) return; - toHTMLOptionElement(m_element.get())->setSelected(b); + downcast<HTMLOptionElement>(*m_element).setSelected(selected); } String AccessibilityMenuListOption::nameForMSAA() const @@ -118,7 +118,7 @@ LayoutRect AccessibilityMenuListOption::elementRect() const String AccessibilityMenuListOption::stringValue() const { - return toHTMLOptionElement(m_element.get())->text(); + return downcast<HTMLOptionElement>(*m_element).label(); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.h b/Source/WebCore/accessibility/AccessibilityMenuListOption.h index 6bee58b18..4111e07b9 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListOption.h +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.h @@ -23,47 +23,43 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityMenuListOption_h -#define AccessibilityMenuListOption_h +#pragma once #include "AccessibilityMockObject.h" namespace WebCore { -class AccessibilityMenuListPopup; class HTMLElement; -class AccessibilityMenuListOption : public AccessibilityMockObject { +class AccessibilityMenuListOption final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityMenuListOption> create() { return adoptRef(new AccessibilityMenuListOption); } + static Ref<AccessibilityMenuListOption> create() { return adoptRef(*new AccessibilityMenuListOption); } void setElement(HTMLElement*); private: AccessibilityMenuListOption(); - virtual bool isMenuListOption() const override { return true; } + bool isMenuListOption() const override { return true; } - virtual AccessibilityRole roleValue() const override { return MenuListOptionRole; } - virtual bool canHaveChildren() const override { return false; } + AccessibilityRole roleValue() const override { return MenuListOptionRole; } + bool canHaveChildren() const override { return false; } - virtual Element* actionElement() const override; - virtual bool isEnabled() const override; - virtual bool isVisible() const override; - virtual bool isOffScreen() const override; - virtual bool isSelected() const override; - virtual String nameForMSAA() const override; - virtual void setSelected(bool) override; - virtual bool canSetSelectedAttribute() const override; - virtual LayoutRect elementRect() const override; - virtual String stringValue() const override; - virtual bool computeAccessibilityIsIgnored() const override; + Element* actionElement() const override; + bool isEnabled() const override; + bool isVisible() const override; + bool isOffScreen() const override; + bool isSelected() const override; + String nameForMSAA() const override; + void setSelected(bool) override; + bool canSetSelectedAttribute() const override; + LayoutRect elementRect() const override; + String stringValue() const override; + bool computeAccessibilityIsIgnored() const override; RefPtr<HTMLElement> m_element; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityMenuListOption, isMenuListOption()) - } // namespace WebCore -#endif // AccessibilityMenuListOption_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuListOption, isMenuListOption()) diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp index 8890752a9..fe1bd34b8 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp @@ -70,19 +70,16 @@ bool AccessibilityMenuListPopup::computeAccessibilityIsIgnored() const AccessibilityMenuListOption* AccessibilityMenuListPopup::menuListOptionAccessibilityObject(HTMLElement* element) const { - if (!element || !isHTMLOptionElement(element) || !element->inRenderedDocument()) - return 0; + if (!is<HTMLOptionElement>(element) || !element->inRenderedDocument()) + return nullptr; - AccessibilityObject* object = document()->axObjectCache()->getOrCreate(MenuListOptionRole); - ASSERT_WITH_SECURITY_IMPLICATION(object->isMenuListOption()); + auto& option = downcast<AccessibilityMenuListOption>(*document()->axObjectCache()->getOrCreate(MenuListOptionRole)); + option.setElement(element); - AccessibilityMenuListOption* option = toAccessibilityMenuListOption(object); - option->setElement(element); - - return option; + return &option; } -bool AccessibilityMenuListPopup::press() const +bool AccessibilityMenuListPopup::press() { if (!m_parent) return false; @@ -102,7 +99,7 @@ void AccessibilityMenuListPopup::addChildren() m_haveChildren = true; - for (const auto& listItem : toHTMLSelectElement(selectNode)->listItems()) { + for (const auto& listItem : downcast<HTMLSelectElement>(*selectNode).listItems()) { AccessibilityMenuListOption* option = menuListOptionAccessibilityObject(listItem); if (option) { option->setParent(this); diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.h b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h index 9e20933e3..c1987cd3a 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListPopup.h +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h @@ -23,8 +23,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityMenuListPopup_h -#define AccessibilityMenuListPopup_h +#pragma once #include "AccessibilityMockObject.h" @@ -33,37 +32,33 @@ namespace WebCore { class AccessibilityMenuList; class AccessibilityMenuListOption; class HTMLElement; -class HTMLSelectElement; -class AccessibilityMenuListPopup : public AccessibilityMockObject { +class AccessibilityMenuListPopup final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityMenuListPopup> create() { return adoptRef(new AccessibilityMenuListPopup); } + static Ref<AccessibilityMenuListPopup> create() { return adoptRef(*new AccessibilityMenuListPopup); } - virtual bool isEnabled() const override; - virtual bool isOffScreen() const override; + bool isEnabled() const override; + bool isOffScreen() const override; void didUpdateActiveOption(int optionIndex); - private: AccessibilityMenuListPopup(); - virtual bool isMenuListPopup() const override { return true; } + bool isMenuListPopup() const override { return true; } - virtual LayoutRect elementRect() const override { return LayoutRect(); } - virtual AccessibilityRole roleValue() const override { return MenuListPopupRole; } + LayoutRect elementRect() const override { return LayoutRect(); } + AccessibilityRole roleValue() const override { return MenuListPopupRole; } - virtual bool isVisible() const override; - virtual bool press() const override; - virtual void addChildren() override; - virtual void childrenChanged() override; - virtual bool computeAccessibilityIsIgnored() const override; + bool isVisible() const override; + bool press() override; + void addChildren() override; + void childrenChanged() override; + bool computeAccessibilityIsIgnored() const override; AccessibilityMenuListOption* menuListOptionAccessibilityObject(HTMLElement*) const; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityMenuListPopup, isMenuListPopup()) - } // namespace WebCore -#endif // AccessibilityMenuListPopup_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuListPopup, isMenuListPopup()) diff --git a/Source/WebCore/accessibility/AccessibilityMockObject.cpp b/Source/WebCore/accessibility/AccessibilityMockObject.cpp index f48156125..0d804cecb 100644 --- a/Source/WebCore/accessibility/AccessibilityMockObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityMockObject.cpp @@ -29,7 +29,7 @@ namespace WebCore { AccessibilityMockObject::AccessibilityMockObject() - : m_parent(0) + : m_parent(nullptr) { } diff --git a/Source/WebCore/accessibility/AccessibilityMockObject.h b/Source/WebCore/accessibility/AccessibilityMockObject.h index 985a21982..668f889d4 100644 --- a/Source/WebCore/accessibility/AccessibilityMockObject.h +++ b/Source/WebCore/accessibility/AccessibilityMockObject.h @@ -23,8 +23,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityMockObject_h -#define AccessibilityMockObject_h +#pragma once #include "AccessibilityObject.h" @@ -37,24 +36,23 @@ protected: public: virtual ~AccessibilityMockObject(); - virtual AccessibilityObject* parentObject() const override { return m_parent; } - virtual void setParent(AccessibilityObject* parent) { m_parent = parent; }; - virtual bool isEnabled() const override { return true; } + AccessibilityObject* parentObject() const override { return m_parent; } + virtual void setParent(AccessibilityObject* parent) { m_parent = parent; } + bool isEnabled() const override { return true; } protected: AccessibilityObject* m_parent; // Must be called when the parent object clears its children. - virtual void detachFromParent() override { m_parent = 0; } + void detachFromParent() override { m_parent = nullptr; } private: - virtual bool isMockObject() const override { return true; } + bool isMockObject() const final { return true; } + bool isDetachedFromParent() override { return !m_parent; } - virtual bool computeAccessibilityIsIgnored() const override; -}; + bool computeAccessibilityIsIgnored() const override; +}; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityMockObject, isMockObject()) - } // namespace WebCore -#endif // AccessibilityMockObject_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMockObject, isMockObject()) diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp index 8969dba6f..4bce0b09c 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp @@ -10,7 +10,7 @@ * 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. -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,6 +31,7 @@ #include "AXObjectCache.h" #include "AccessibilityImageMapLink.h" +#include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" @@ -41,40 +42,31 @@ #include "FrameLoader.h" #include "FrameSelection.h" #include "FrameView.h" -#include "HTMLAreaElement.h" +#include "HTMLCanvasElement.h" +#include "HTMLDetailsElement.h" #include "HTMLFieldSetElement.h" #include "HTMLFormElement.h" -#include "HTMLFrameElementBase.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLLegendElement.h" -#include "HTMLMapElement.h" #include "HTMLNames.h" -#include "HTMLOptGroupElement.h" -#include "HTMLOptionElement.h" -#include "HTMLOptionsCollection.h" #include "HTMLParserIdioms.h" -#include "HTMLPlugInImageElement.h" #include "HTMLSelectElement.h" #include "HTMLTextAreaElement.h" #include "HTMLTextFormControlElement.h" -#include "HitTestRequest.h" -#include "HitTestResult.h" #include "LabelableElement.h" #include "LocalizedStrings.h" +#include "MathMLElement.h" #include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" -#include "Page.h" #include "ProgressTracker.h" #include "RenderImage.h" #include "RenderView.h" #include "SVGElement.h" -#include "SVGNames.h" #include "Text.h" #include "TextControlInnerElements.h" -#include "TextIterator.h" #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "Widget.h" @@ -87,7 +79,7 @@ namespace WebCore { using namespace HTMLNames; -static String accessibleNameForNode(Node*); +static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr); AccessibilityNodeObject::AccessibilityNodeObject(Node* node) : AccessibilityObject() @@ -115,16 +107,16 @@ void AccessibilityNodeObject::init() m_role = determineAccessibilityRole(); } -PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node) +Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node) { - return adoptRef(new AccessibilityNodeObject(node)); + return adoptRef(*new AccessibilityNodeObject(node)); } void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { // AccessibilityObject calls clearChildren. AccessibilityObject::detach(detachmentType, cache); - m_node = 0; + m_node = nullptr; } void AccessibilityNodeObject::childrenChanged() @@ -149,11 +141,14 @@ void AccessibilityNodeObject::childrenChanged() // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update. // If this element supports ARIA live regions, then notify the AT of changes. + // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged + // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined + // time interval. if (parent->supportsARIALiveRegion()) - cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged); + cache->postLiveRegionChangeNotification(parent); // If this element is an ARIA text control, notify the AT of changes. - if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->hasEditableStyle()) + if (parent->isNonNativeTextControl()) cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged); } } @@ -171,12 +166,12 @@ void AccessibilityNodeObject::updateAccessibilityRole() AccessibilityObject* AccessibilityNodeObject::firstChild() const { if (!node()) - return 0; + return nullptr; Node* firstChild = node()->firstChild(); if (!firstChild) - return 0; + return nullptr; return axObjectCache()->getOrCreate(firstChild); } @@ -184,11 +179,11 @@ AccessibilityObject* AccessibilityNodeObject::firstChild() const AccessibilityObject* AccessibilityNodeObject::lastChild() const { if (!node()) - return 0; + return nullptr; Node* lastChild = node()->lastChild(); if (!lastChild) - return 0; + return nullptr; return axObjectCache()->getOrCreate(lastChild); } @@ -196,11 +191,11 @@ AccessibilityObject* AccessibilityNodeObject::lastChild() const AccessibilityObject* AccessibilityNodeObject::previousSibling() const { if (!node()) - return 0; + return nullptr; Node* previousSibling = node()->previousSibling(); if (!previousSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(previousSibling); } @@ -208,11 +203,11 @@ AccessibilityObject* AccessibilityNodeObject::previousSibling() const AccessibilityObject* AccessibilityNodeObject::nextSibling() const { if (!node()) - return 0; + return nullptr; Node* nextSibling = node()->nextSibling(); if (!nextSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(nextSibling); } @@ -225,20 +220,23 @@ AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const AccessibilityObject* AccessibilityNodeObject::parentObject() const { if (!node()) - return 0; + return nullptr; Node* parentObj = node()->parentNode(); - if (parentObj) - return axObjectCache()->getOrCreate(parentObj); + if (!parentObj) + return nullptr; - return 0; + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(parentObj); + + return nullptr; } LayoutRect AccessibilityNodeObject::elementRect() const { return boundingBoxRect(); } - + LayoutRect AccessibilityNodeObject::boundingBoxRect() const { // AccessibilityNodeObjects have no mechanism yet to return a size or position. @@ -267,7 +265,7 @@ void AccessibilityNodeObject::setNode(Node* node) Document* AccessibilityNodeObject::document() const { if (!node()) - return 0; + return nullptr; return &node()->document(); } @@ -276,52 +274,52 @@ AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() if (!node()) return UnknownRole; - m_ariaRole = determineAriaRoleAttribute(); + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; - if (node()->isLink()) return WebCoreLinkRole; if (node()->isTextNode()) return StaticTextRole; if (node()->hasTagName(buttonTag)) return buttonRoleType(); - if (isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isCheckbox()) + if (is<HTMLInputElement>(*node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isCheckbox()) return CheckBoxRole; - if (input->isRadioButton()) + if (input.isRadioButton()) return RadioButtonRole; - if (input->isTextButton()) + if (input.isTextButton()) return buttonRoleType(); - if (input->isRangeControl()) + if (input.isRangeControl()) return SliderRole; - + if (input.isInputTypeHidden()) + return IgnoredRole; + if (input.isSearchField()) + return SearchFieldRole; #if ENABLE(INPUT_TYPE_COLOR) - const AtomicString& type = input->getAttribute(typeAttr); - if (equalIgnoringCase(type, "color")) + if (input.isColorControl()) return ColorWellRole; #endif - return TextFieldRole; } if (node()->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(node()); - return selectElement->multiple() ? ListBoxRole : PopUpButtonRole; + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node()); + return selectElement.multiple() ? ListBoxRole : PopUpButtonRole; } - if (isHTMLTextAreaElement(node())) + if (is<HTMLTextAreaElement>(*node())) return TextAreaRole; if (headingLevel()) return HeadingRole; + if (node()->hasTagName(blockquoteTag)) + return BlockquoteRole; if (node()->hasTagName(divTag)) return DivRole; if (node()->hasTagName(pTag)) return ParagraphRole; - if (isHTMLLabelElement(node())) + if (is<HTMLLabelElement>(*node())) return LabelRole; - if (node()->isElementNode() && toElement(node())->isFocusable()) + if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable()) return GroupRole; return UnknownRole; @@ -397,11 +395,8 @@ bool AccessibilityNodeObject::canHaveChildren() const case ListBoxOptionRole: case ScrollBarRole: case ProgressIndicatorRole: + case SwitchRole: return false; - case LegendRole: - if (Element* element = this->element()) - return !ancestorsOfType<HTMLFieldSetElement>(*element).first(); - FALLTHROUGH; default: return true; } @@ -426,20 +421,28 @@ bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const if (!string.length()) return true; } - + + AccessibilityObjectInclusion decision = defaultObjectInclusion(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; // If this element is within a parent that cannot have children, it should not be exposed. if (isDescendantOfBarrenParent()) return true; + if (roleValue() == IgnoredRole) + return true; + return m_role == UnknownRole; } bool AccessibilityNodeObject::canvasHasFallbackContent() const { Node* node = this->node(); - if (!node || !node->hasTagName(canvasTag)) + if (!is<HTMLCanvasElement>(node)) return false; - Element& canvasElement = toElement(*node); + HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node); // If it has any children that are elements, we'll assume it might be fallback // content. If it has no children or its only children are not elements // (e.g. just text nodes), it doesn't have fallback content. @@ -451,23 +454,18 @@ bool AccessibilityNodeObject::isImageButton() const return isNativeImage() && isButton(); } -bool AccessibilityNodeObject::isAnchor() const -{ - return !isNativeImage() && isLink(); -} - bool AccessibilityNodeObject::isNativeTextControl() const { Node* node = this->node(); if (!node) return false; - if (isHTMLTextAreaElement(node)) + if (is<HTMLTextAreaElement>(*node)) return true; - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isText() || input->isNumberField(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isText() || input.isNumberField(); } return false; @@ -479,12 +477,13 @@ bool AccessibilityNodeObject::isSearchField() const if (!node) return false; - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) + if (roleValue() == SearchFieldRole) + return true; + + if (!is<HTMLInputElement>(*node)) return false; - if (inputElement->isSearchField()) - return true; + auto& inputElement = downcast<HTMLInputElement>(*node); // Some websites don't label their search fields as such. However, they will // use the word "search" in either the form or input type. This won't catch every case, @@ -496,7 +495,7 @@ bool AccessibilityNodeObject::isSearchField() const return true; // Check the form action and the name, which will sometimes be "search". - HTMLFormElement* form = inputElement->form(); + auto* form = inputElement.form(); if (form && (form->name().contains("search", false) || form->action().contains("search", false))) return true; @@ -509,15 +508,15 @@ bool AccessibilityNodeObject::isNativeImage() const if (!node) return false; - if (isHTMLImageElement(node)) + if (is<HTMLImageElement>(*node)) return true; if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag)) return true; - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isImageButton(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isImageButton(); } return false; @@ -530,29 +529,41 @@ bool AccessibilityNodeObject::isImage() const bool AccessibilityNodeObject::isPasswordField() const { - Node* node = this->node(); - if (!node || !node->isHTMLElement()) + auto* node = this->node(); + if (!is<HTMLInputElement>(node)) return false; if (ariaRoleAttribute() != UnknownRole) return false; - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) - return false; + return downcast<HTMLInputElement>(*node).isPasswordField(); +} - return inputElement->isPasswordField(); +AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField() +{ + Node* node = this->node(); + if (!node) + return nullptr; + + if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField()) + return this; + + auto* element = node->shadowHost(); + if (!is<HTMLInputElement>(element)) + return nullptr; + + if (auto* cache = axObjectCache()) + return cache->getOrCreate(element); + + return nullptr; } bool AccessibilityNodeObject::isInputImage() const { Node* node = this->node(); - if (!node) - return false; - - if (roleValue() == ButtonRole && isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isImageButton(); + if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isImageButton(); } return false; @@ -613,14 +624,11 @@ bool AccessibilityNodeObject::isMenuItem() const bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const { Node* node = this->node(); - if (!node) + if (!is<HTMLInputElement>(node)) return false; - HTMLInputElement* input = node->toInputElement(); - if (input) - return input->isCheckbox() || input->isRadioButton(); - - return false; + auto& input = downcast<HTMLInputElement>(*node); + return input.isCheckbox() || input.isRadioButton(); } bool AccessibilityNodeObject::isEnabled() const @@ -628,30 +636,25 @@ bool AccessibilityNodeObject::isEnabled() const // ARIA says that the disabled status applies to the current element and all descendant elements. for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) { const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr); - if (equalIgnoringCase(disabledStatus, "true")) + if (equalLettersIgnoringASCIICase(disabledStatus, "true")) return false; - if (equalIgnoringCase(disabledStatus, "false")) + if (equalLettersIgnoringASCIICase(disabledStatus, "false")) break; } - + + if (roleValue() == HorizontalRuleRole) + return false; + Node* node = this->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return true; - return !toElement(node)->isDisabledFormControl(); + return !downcast<Element>(*node).isDisabledFormControl(); } bool AccessibilityNodeObject::isIndeterminate() const { - Node* node = this->node(); - if (!node) - return false; - - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) - return false; - - return inputElement->shouldAppearIndeterminate(); + return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true"); } bool AccessibilityNodeObject::isPressed() const @@ -663,16 +666,13 @@ bool AccessibilityNodeObject::isPressed() const if (!node) return false; - // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() - if (ariaRoleAttribute() == ButtonRole) { - if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) - return true; - return false; - } + // If this is an toggle button, check the aria-pressed attribute rather than node()->active() + if (isToggleButton()) + return equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true"); - if (!node->isElementNode()) + if (!is<Element>(*node)) return false; - return toElement(node)->active(); + return downcast<Element>(*node).active(); } bool AccessibilityNodeObject::isChecked() const @@ -682,9 +682,8 @@ bool AccessibilityNodeObject::isChecked() const return false; // First test for native checkedness semantics - HTMLInputElement* inputElement = node->toInputElement(); - if (inputElement) - return inputElement->shouldAppearChecked(); + if (is<HTMLInputElement>(*node)) + return downcast<HTMLInputElement>(*node).shouldAppearChecked(); // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute bool validRole = false; @@ -694,13 +693,14 @@ bool AccessibilityNodeObject::isChecked() const case MenuItemRole: case MenuItemCheckboxRole: case MenuItemRadioRole: + case SwitchRole: validRole = true; break; default: break; } - if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) + if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true")) return true; return false; @@ -709,53 +709,32 @@ bool AccessibilityNodeObject::isChecked() const bool AccessibilityNodeObject::isHovered() const { Node* node = this->node(); - if (!node) - return false; - - return node->isElementNode() && toElement(node)->hovered(); + return is<Element>(node) && downcast<Element>(*node).hovered(); } bool AccessibilityNodeObject::isMultiSelectable() const { const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); - if (equalIgnoringCase(ariaMultiSelectable, "true")) + if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true")) return true; - if (equalIgnoringCase(ariaMultiSelectable, "false")) + if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "false")) return false; - return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple(); -} - -bool AccessibilityNodeObject::isReadOnly() const -{ - Node* node = this->node(); - if (!node) - return true; - - if (isHTMLTextAreaElement(node)) - return toHTMLFormControlElement(node)->isReadOnly(); - - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextField()) - return input->isReadOnly(); - } - - return !node->hasEditableStyle(); + return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple(); } bool AccessibilityNodeObject::isRequired() const { // Explicit aria-required values should trump native required attributes. const AtomicString& requiredValue = getAttribute(aria_requiredAttr); - if (equalIgnoringCase(requiredValue, "true")) + if (equalLettersIgnoringASCIICase(requiredValue, "true")) return true; - if (equalIgnoringCase(requiredValue, "false")) + if (equalLettersIgnoringASCIICase(requiredValue, "false")) return false; Node* n = this->node(); - if (n && (n->isElementNode() && toElement(n)->isFormControlElement())) - return toHTMLFormControlElement(n)->isRequired(); + if (is<HTMLFormControlElement>(n)) + return downcast<HTMLFormControlElement>(*n).isRequired(); return false; } @@ -766,9 +745,11 @@ bool AccessibilityNodeObject::supportsRequiredAttribute() const case ButtonRole: return isFileUploadButton(); case CellRole: + case ColumnHeaderRole: case CheckBoxRole: case ComboBoxRole: case GridRole: + case GridCellRole: case IncrementorRole: case ListBoxRole: case PopUpButtonRole: @@ -818,6 +799,11 @@ int AccessibilityNodeObject::headingLevel() const if (node->hasTagName(h6Tag)) return 6; + // The implicit value of aria-level is 2 for the heading role. + // https://www.w3.org/TR/wai-aria-1.1/#heading + if (ariaRoleAttribute() == HeadingRole) + return 2; + return 0; } @@ -831,44 +817,62 @@ String AccessibilityNodeObject::valueDescription() const float AccessibilityNodeObject::valueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->valueAsNumber(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.valueAsNumber(); } if (!isRangeControl()) return 0.0f; - return getAttribute(aria_valuenowAttr).toFloat(); + // In ARIA 1.1, the implicit value for aria-valuenow on a spin button is 0. + // For other roles, it is half way between aria-valuemin and aria-valuemax. + auto value = getAttribute(aria_valuenowAttr); + if (!value.isEmpty()) + return value.toFloat(); + + return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2; } float AccessibilityNodeObject::maxValueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->maximum(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.maximum(); } if (!isRangeControl()) return 0.0f; - return getAttribute(aria_valuemaxAttr).toFloat(); + auto value = getAttribute(aria_valuemaxAttr); + if (!value.isEmpty()) + return value.toFloat(); + + // In ARIA 1.1, the implicit value for aria-valuemax on a spin button + // is that there is no maximum value. For other roles, it is 100. + return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f; } float AccessibilityNodeObject::minValueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->minimum(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.minimum(); } if (!isRangeControl()) return 0.0f; - return getAttribute(aria_valueminAttr).toFloat(); + auto value = getAttribute(aria_valueminAttr); + if (!value.isEmpty()) + return value.toFloat(); + + // In ARIA 1.1, the implicit value for aria-valuemin on a spin button + // is that there is no minimum value. For other roles, it is 0. + return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f; } float AccessibilityNodeObject::stepValueForRange() const @@ -892,8 +896,7 @@ bool AccessibilityNodeObject::isControl() const if (!node) return false; - return ((node->isElementNode() && toElement(node)->isFormControlElement()) - || AccessibilityObject::isARIAControl(ariaRoleAttribute())); + return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute()); } bool AccessibilityNodeObject::isFieldset() const @@ -928,12 +931,13 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem() if (!isTabList()) return nullptr; + // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked. // Find the child tab item that is selected (ie. the intValue == 1). AccessibilityObject::AccessibilityChildrenVector tabs; tabChildren(tabs); for (const auto& child : children()) { - if (child->isTabItem() && child->isChecked()) + if (child->isTabItem() && (child->isChecked() || child->isSelected())) return child.get(); } return nullptr; @@ -942,7 +946,7 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem() AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const { if (isNativeCheckboxOrRadio()) - return isChecked() ? ButtonStateOn : ButtonStateOff; + return isIndeterminate() ? ButtonStateMixed : isChecked() ? ButtonStateOn : ButtonStateOff; return AccessibilityObject::checkboxOrRadioValue(); } @@ -951,25 +955,25 @@ Element* AccessibilityNodeObject::anchorElement() const { Node* node = this->node(); if (!node) - return 0; + return nullptr; AXObjectCache* cache = axObjectCache(); // search up the DOM tree for an anchor element // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement for ( ; node; node = node->parentNode()) { - if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) - return toElement(node); + if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink())) + return downcast<Element>(node); } - return 0; + return nullptr; } static bool isNodeActionElement(Node* node) { - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (!input->isDisabledFormControl() && (input->isRadioButton() || input->isCheckbox() || input->isTextButton() || input->isFileUpload() || input->isImageButton())) + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton())) return true; } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag)) return true; @@ -980,7 +984,7 @@ static bool isNodeActionElement(Node* node) static Element* nativeActionElement(Node* start) { if (!start) - return 0; + return nullptr; // Do a deep-dive to see if any nodes should be used as the action element. // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons). @@ -988,25 +992,25 @@ static Element* nativeActionElement(Node* start) for (Node* child = start->firstChild(); child; child = child->nextSibling()) { if (isNodeActionElement(child)) - return toElement(child); + return downcast<Element>(child); if (Element* subChild = nativeActionElement(child)) return subChild; } - return 0; + return nullptr; } Element* AccessibilityNodeObject::actionElement() const { Node* node = this->node(); if (!node) - return 0; + return nullptr; if (isNodeActionElement(node)) - return toElement(node); + return downcast<Element>(node); if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) - return toElement(node); + return downcast<Element>(node); switch (roleValue()) { case ButtonRole: @@ -1020,7 +1024,7 @@ Element* AccessibilityNodeObject::actionElement() const // Check if the author is hiding the real control element inside the ARIA element. if (Element* nativeElement = nativeActionElement(node)) return nativeElement; - return toElement(node); + return downcast<Element>(node); default: break; } @@ -1031,25 +1035,25 @@ Element* AccessibilityNodeObject::actionElement() const return elt; } -Element* AccessibilityNodeObject::mouseButtonListener() const +Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const { Node* node = this->node(); if (!node) - return 0; + return nullptr; // check if our parent is a mouse button listener // FIXME: Do the continuation search like anchorElement does - for (auto& element : elementLineage(node->isElementNode() ? toElement(node) : node->parentElement())) { - // If we've reached the body and this is not a control element, do not expose press action for this element. - // It can cause false positives, where every piece of text is labeled as accepting press actions. - if (element.hasTagName(bodyTag) && isStaticText()) + for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) { + // If we've reached the body and this is not a control element, do not expose press action for this element unless filter is IncludeBodyElement. + // It can cause false positives, where every piece of text is labeled as accepting press actions. + if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement) break; if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent)) return &element; } - return 0; + return nullptr; } bool AccessibilityNodeObject::isDescendantOfBarrenParent() const @@ -1075,13 +1079,13 @@ void AccessibilityNodeObject::alterSliderValue(bool increase) void AccessibilityNodeObject::increment() { - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); + UserGestureIndicator gestureIndicator(ProcessingUserGesture, document()); alterSliderValue(true); } void AccessibilityNodeObject::decrement() { - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); + UserGestureIndicator gestureIndicator(ProcessingUserGesture, document()); alterSliderValue(false); } @@ -1154,8 +1158,8 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const { - if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable()) - return 0; + if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable()) + return nullptr; const AtomicString& id = element->getIdAttribute(); if (!id.isEmpty()) { @@ -1179,15 +1183,16 @@ String AccessibilityNodeObject::ariaAccessibilityDescription() const return String(); } -static Element* siblingWithAriaRole(String role, Node* node) +static Element* siblingWithAriaRole(Node* node, const char* role) { + // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer. ContainerNode* parent = node->parentNode(); if (!parent) return nullptr; for (auto& sibling : childrenOfType<Element>(*parent)) { - const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr); - if (equalIgnoringCase(siblingAriaRole, role)) + // FIXME: Should skip sibling that is the same as the node. + if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role)) return &sibling; } @@ -1199,39 +1204,89 @@ Element* AccessibilityNodeObject::menuElementForMenuButton() const if (ariaRoleAttribute() != MenuButtonRole) return nullptr; - return siblingWithAriaRole("menu", node()); + return siblingWithAriaRole(node(), "menu"); } AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const { - return axObjectCache()->getOrCreate(menuElementForMenuButton()); + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(menuElementForMenuButton()); + return nullptr; } Element* AccessibilityNodeObject::menuItemElementForMenu() const { if (ariaRoleAttribute() != MenuRole) - return 0; + return nullptr; - return siblingWithAriaRole("menuitem", node()); + return siblingWithAriaRole(node(), "menuitem"); } AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + Element* menuItem = menuItemElementForMenu(); if (menuItem) { // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem - AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem); + AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem); if (menuItemAX && menuItemAX->isMenuButton()) return menuItemAX; } - return 0; + return nullptr; +} + +AccessibilityObject* AccessibilityNodeObject::captionForFigure() const +{ + if (!isFigure()) + return nullptr; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + Node* node = this->node(); + for (Node* child = node->firstChild(); child; child = child->nextSibling()) { + if (child->hasTagName(figcaptionTag)) + return cache->getOrCreate(child); + } + return nullptr; } bool AccessibilityNodeObject::usesAltTagForTextComputation() const { return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag)); } + +bool AccessibilityNodeObject::isLabelable() const +{ + Node* node = this->node(); + if (!node) + return false; + + return is<HTMLInputElement>(*node) || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl() || isProgressIndicator() || isMeter(); +} + +String AccessibilityNodeObject::textForLabelElement(Element* element) const +{ + String result = String(); + if (!is<HTMLLabelElement>(*element)) + return result; + + HTMLLabelElement* label = downcast<HTMLLabelElement>(element); + // Check to see if there's aria-labelledby attribute on the label element. + if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label)) + result = labelObject->ariaLabeledByAttribute(); + + // Then check for aria-label attribute. + if (result.isEmpty()) + result = label->attributeWithoutSynchronization(aria_labelAttr); + + return !result.isEmpty() ? result : label->innerText(); +} void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const { @@ -1239,15 +1294,14 @@ void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOr if (!node) return; - bool isInputTag = isHTMLInputElement(node); - if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label) { + if (isLabelable()) { + if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) { AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label); - String innerText = label->innerText(); + String innerText = textForLabelElement(label); + // Only use the <label> text if there's no ARIA override. if (!innerText.isEmpty() && !ariaAccessibilityDescription()) - textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject)); + textOrder.append(AccessibilityText(innerText, isMeter() ? AlternativeText : LabelByElementText, labelObject)); return; } } @@ -1273,8 +1327,8 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd textOrder.append(AccessibilityText(ariaLabel, AlternativeText)); if (usesAltTagForTextComputation()) { - if (renderer() && renderer()->isRenderImage()) { - String renderAltText = toRenderImage(renderer())->altText(); + if (is<RenderImage>(renderer())) { + String renderAltText = downcast<RenderImage>(*renderer()).altText(); // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText. if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) { @@ -1294,17 +1348,22 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd return; // The fieldset element derives its alternative text from the first associated legend element if one is available. - if (isHTMLFieldSetElement(node)) { - AccessibilityObject* object = axObjectCache()->getOrCreate(toHTMLFieldSetElement(node)->legend()); + if (is<HTMLFieldSetElement>(*node)) { + AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend()); if (object && !object->isHidden()) textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText)); } -#if ENABLE(SVG) - // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. - if (node->isSVGElement()) - textOrder.append(AccessibilityText(toSVGElement(node)->title(), AlternativeText)); -#endif + // The figure element derives its alternative text from the first associated figcaption element if one is available. + if (isFigure()) { + AccessibilityObject* captionForFigure = this->captionForFigure(); + if (captionForFigure && !captionForFigure->isHidden()) + textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AlternativeText)); + } + + // Tree items missing a label are labeled by all child elements. + if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty()) + textOrder.append(AccessibilityText(accessibleNameForNode(node), AlternativeText)); #if ENABLE(MATHML) if (node->isMathMLElement()) @@ -1318,11 +1377,11 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) if (!node) return; - bool isInputTag = isHTMLInputElement(node); + bool isInputTag = is<HTMLInputElement>(*node); if (isInputTag) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextButton()) { - textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText)); + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextButton()) { + textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText)); return; } } @@ -1344,7 +1403,7 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) case CheckBoxRole: case ListBoxOptionRole: // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that. -#if !PLATFORM(MAC) +#if !PLATFORM(COCOA) case ListItemRole: #endif case MenuButtonRole: @@ -1352,8 +1411,8 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) case MenuItemCheckboxRole: case MenuItemRadioRole: case RadioButtonRole: + case SwitchRole: case TabRole: - case ProgressIndicatorRole: useTextUnderElement = true; break; default: @@ -1365,6 +1424,9 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) if (isHeading() || isLink()) useTextUnderElement = true; + if (isOutput()) + useTextUnderElement = true; + if (useTextUnderElement) { AccessibilityTextUnderElementMode mode; @@ -1387,27 +1449,23 @@ void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) con String describedBy = ariaDescribedByAttribute(); if (!describedBy.isEmpty()) textOrder.append(AccessibilityText(describedBy, SummaryText)); - - // Add help type text that is derived from ancestors. - for (Node* curr = node(); curr; curr = curr->parentNode()) { - const AtomicString& summary = getAttribute(summaryAttr); - if (!summary.isEmpty()) - textOrder.append(AccessibilityText(summary, SummaryText)); - - // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = getAttribute(titleAttr); - if (!title.isEmpty()) + + // Summary attribute used as help text on tables. + const AtomicString& summary = getAttribute(summaryAttr); + if (!summary.isEmpty()) + textOrder.append(AccessibilityText(summary, SummaryText)); + + // The title attribute should be used as help text unless it is already being used as descriptive text. + // However, when the title attribute is the only text alternative provided, it may be exposed as the + // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors + // can expose units through this attribute. Therefore, if the element is a meter, change its source + // type to HelpText. + const AtomicString& title = getAttribute(titleAttr); + if (!title.isEmpty()) { + if (!isMeter()) textOrder.append(AccessibilityText(title, TitleTagText)); - - // Only take help text from an ancestor element if its a group or an unknown role. If help was - // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); - if (!axObj) - return; - - AccessibilityRole role = axObj->roleValue(); - if (role != GroupRole && role != UnknownRole) - break; + else + textOrder.append(AccessibilityText(title, HelpText)); } } @@ -1436,7 +1494,7 @@ void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textO axElements.append(axElement); } - textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements)); + textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTFMove(axElements))); } } @@ -1458,30 +1516,26 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const // Check if the HTML element has an aria-label for the webpage. if (Element* documentElement = document->documentElement()) { - const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr); + const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr); if (!ariaLabel.isEmpty()) return ariaLabel; } - Node* owner = document->ownerElement(); - if (owner) { + if (auto* owner = document->ownerElement()) { if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { - const AtomicString& title = toElement(owner)->getAttribute(titleAttr); + const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr); if (!title.isEmpty()) return title; - return toElement(owner)->getNameAttribute(); } - if (owner->isHTMLElement()) - return toHTMLElement(owner)->getNameAttribute(); + return owner->getNameAttribute(); } String documentTitle = document->title(); if (!documentTitle.isEmpty()) return documentTitle; - owner = document->body(); - if (owner && owner->isHTMLElement()) - return toHTMLElement(owner)->getNameAttribute(); + if (auto* body = document->bodyOrFrameset()) + return body->getNameAttribute(); return String(); } @@ -1503,15 +1557,9 @@ String AccessibilityNodeObject::accessibilityDescription() const if (!alt.isNull()) return alt; } - -#if ENABLE(SVG) - // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. - if (m_node && m_node->isSVGElement()) - return toSVGElement(m_node)->title(); -#endif #if ENABLE(MATHML) - if (m_node && m_node->isMathMLElement()) + if (is<MathMLElement>(m_node)) return getAttribute(MathMLNames::alttextAttr); #endif @@ -1540,21 +1588,22 @@ String AccessibilityNodeObject::helpText() const return describedBy; String description = accessibilityDescription(); - for (Node* curr = node; curr; curr = curr->parentNode()) { - if (curr->isHTMLElement()) { - const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr); + for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { + if (is<HTMLElement>(*ancestor)) { + HTMLElement& element = downcast<HTMLElement>(*ancestor); + const AtomicString& summary = element.getAttribute(summaryAttr); if (!summary.isEmpty()) return summary; // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = toElement(curr)->getAttribute(titleAttr); + const AtomicString& title = element.getAttribute(titleAttr); if (!title.isEmpty() && description != title) return title; } // Only take help text from an ancestor element if its a group or an unknown role. If help was // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor); if (axObj) { AccessibilityRole role = axObj->roleValue(); if (role != GroupRole && role != UnknownRole) @@ -1568,10 +1617,10 @@ String AccessibilityNodeObject::helpText() const unsigned AccessibilityNodeObject::hierarchicalLevel() const { Node* node = this->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return 0; - Element* element = toElement(node); - String ariaLevel = element->getAttribute(aria_levelAttr); + Element& element = downcast<Element>(*node); + const AtomicString& ariaLevel = element.attributeWithoutSynchronization(aria_levelAttr); if (!ariaLevel.isEmpty()) return ariaLevel.toInt(); @@ -1583,7 +1632,7 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const // We measure tree hierarchy by the number of groups that the item is within. unsigned level = 1; for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - AccessibilityRole parentRole = parent->roleValue(); + AccessibilityRole parentRole = parent->ariaRoleAttribute(); if (parentRole == GroupRole) level++; else if (parentRole == TreeRole) @@ -1593,9 +1642,18 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const return level; } +void AccessibilityNodeObject::setIsExpanded(bool expand) +{ + if (is<HTMLDetailsElement>(node())) { + auto& details = downcast<HTMLDetailsElement>(*node()); + if (expand != details.isOpen()) + details.toggleOpen(); + } +} + // When building the textUnderElement for an object, determine whether or not // we should include the inner text of this given descendant object or skip it. -static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode) +static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode) { // Do not use any heuristic if we are explicitly asking to include all the children. if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) @@ -1618,7 +1676,12 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces // quite long. As a heuristic, skip links, controls, and elements that are usually // containers with lots of children. - if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true")) + // ARIA states that certain elements are not allowed to expose their children content for name calculation. + if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren + && !obj->accessibleNameDerivesFromContent()) + return false; + + if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true")) return false; // If something doesn't expose any children, then we can always take the inner text content. @@ -1631,13 +1694,19 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces return false; // Skip big container elements like lists, tables, etc. - if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas()) + if (is<AccessibilityList>(*obj)) + return false; + + if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility()) + return false; + + if (obj->isTree() || obj->isCanvas()) return false; return true; } -static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, String& childText) +static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText) { if (!builder.length() || !childText.length()) return false; @@ -1645,41 +1714,46 @@ static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, Str // We don't need to add an additional space before or after a line break. return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1])); } + +static void appendNameToStringBuilder(StringBuilder& builder, const String& text) +{ + if (shouldAddSpaceBeforeAppendingNextElement(builder, text)) + builder.append(' '); + builder.append(text); +} String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const { Node* node = this->node(); - if (node && node->isTextNode()) - return toText(node)->wholeText(); - - // The render tree should be stable before going ahead. Otherwise, further uses of the - // TextIterator will force a layout update, potentially altering the accessibility tree - // and leading to crashes in the loop that computes the result text from the children. - ASSERT(!document()->renderView()->layoutState()); - ASSERT(!document()->childNeedsStyleRecalc()); + if (is<Text>(node)) + return downcast<Text>(*node).wholeText(); StringBuilder builder; for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) { - if (!shouldUseAccessiblityObjectInnerText(child, mode)) + if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode) + continue; + + bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent()); + if (shouldDeriveNameFromAuthor) { + appendNameToStringBuilder(builder, accessibleNameForNode(child->node())); + continue; + } + + if (!shouldUseAccessibilityObjectInnerText(child, mode)) continue; - if (child->isAccessibilityNodeObject()) { + if (is<AccessibilityNodeObject>(*child)) { Vector<AccessibilityText> textOrder; - toAccessibilityNodeObject(child)->alternativeText(textOrder); + downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder); if (textOrder.size() > 0 && textOrder[0].text.length()) { - if (shouldAddSpaceBeforeAppendingNextElement(builder, textOrder[0].text)) - builder.append(' '); - builder.append(textOrder[0].text); + appendNameToStringBuilder(builder, textOrder[0].text); continue; } } - + String childText = child->textUnderElement(mode); - if (childText.length()) { - if (shouldAddSpaceBeforeAppendingNextElement(builder, childText)) - builder.append(' '); - builder.append(childText); - } + if (childText.length()) + appendNameToStringBuilder(builder, childText); } return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak); @@ -1691,18 +1765,18 @@ String AccessibilityNodeObject::title() const if (!node) return String(); - bool isInputTag = isHTMLInputElement(node); + bool isInputTag = is<HTMLInputElement>(*node); if (isInputTag) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextButton()) - return input->valueWithDefault(); + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextButton()) + return input.valueWithDefault(); } - if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { - HTMLLabelElement* label = labelForElement(toElement(node)); + if (isLabelable()) { + HTMLLabelElement* label = labelForElement(downcast<Element>(node)); // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override. if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length()) - return label->innerText(); + return textForLabelElement(label); } // If this node isn't rendered, there's no inner text we can extract from a select element. @@ -1725,6 +1799,7 @@ String AccessibilityNodeObject::title() const case MenuItemCheckboxRole: case MenuItemRadioRole: case RadioButtonRole: + case SwitchRole: case TabRole: return textUnderElement(); // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>. @@ -1759,13 +1834,13 @@ String AccessibilityNodeObject::text() const if (!node) return String(); - if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node))) - return toHTMLTextFormControlElement(node)->value(); + if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node)) + return downcast<HTMLTextFormControlElement>(*node).value(); if (!node->isElementNode()) return String(); - return toElement(node)->innerText(); + return downcast<Element>(node)->innerText(); } String AccessibilityNodeObject::stringValue() const @@ -1785,16 +1860,16 @@ String AccessibilityNodeObject::stringValue() const return textUnderElement(); if (node->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(node); - int selectedIndex = selectElement->selectedIndex(); - const Vector<HTMLElement*> listItems = selectElement->listItems(); + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node); + int selectedIndex = selectElement.selectedIndex(); + const Vector<HTMLElement*>& listItems = selectElement.listItems(); if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { - const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); + const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr); if (!overriddenDescription.isNull()) return overriddenDescription; } - if (!selectElement->multiple()) - return selectElement->value(); + if (!selectElement.multiple()) + return selectElement.value(); return String(); } @@ -1814,73 +1889,103 @@ void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const g = 0; b = 0; +#if ENABLE(INPUT_TYPE_COLOR) if (!isColorWell()) return; - if (!node() || !isHTMLInputElement(node())) + if (!is<HTMLInputElement>(node())) return; - HTMLInputElement* input = toHTMLInputElement(node()); - const AtomicString& type = input->getAttribute(typeAttr); - if (!equalIgnoringCase(type, "color")) + auto& input = downcast<HTMLInputElement>(*node()); + if (!input.isColorControl()) return; // HTMLInputElement::value always returns a string parseable by Color(). - Color color(input->value()); + Color color(input.value()); r = color.red(); g = color.green(); b = color.blue(); +#endif } // This function implements the ARIA accessible name as described by the Mozilla // ARIA Implementer's Guide. -static String accessibleNameForNode(Node* node) +static String accessibleNameForNode(Node* node, Node* labelledbyNode) { - if (!node->isHTMLElement()) + ASSERT(node); + if (!is<Element>(node)) return String(); - HTMLElement* element = toHTMLElement(node); - - const AtomicString& ariaLabel = element->fastGetAttribute(aria_labelAttr); + Element& element = downcast<Element>(*node); + const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr); if (!ariaLabel.isEmpty()) return ariaLabel; - const AtomicString& alt = element->fastGetAttribute(altAttr); + const AtomicString& alt = element.attributeWithoutSynchronization(altAttr); if (!alt.isEmpty()) return alt; - - if (isHTMLInputElement(node)) - return toHTMLInputElement(node)->value(); - + // If the node can be turned into an AX object, we can use standard name computation rules. // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath. AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node); - String text; - if (axObject) - text = axObject->textUnderElement(); - else if (node->isElementNode()) - text = toElement(node)->innerText(); + if (axObject) { + String valueDescription = axObject->valueDescription(); + if (!valueDescription.isEmpty()) + return valueDescription; + } + + if (is<HTMLInputElement>(*node)) + return downcast<HTMLInputElement>(*node).value(); + String text; + if (axObject) { + if (axObject->accessibleNameDerivesFromContent()) + text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode)); + } else + text = element.innerText(); + if (!text.isEmpty()) return text; - const AtomicString& title = element->fastGetAttribute(titleAttr); + const AtomicString& title = element.attributeWithoutSynchronization(titleAttr); if (!title.isEmpty()) return title; return String(); } +String AccessibilityNodeObject::accessibilityDescriptionForChildren() const +{ + Node* node = this->node(); + if (!node) + return String(); + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return String(); + + StringBuilder builder; + for (Node* child = node->firstChild(); child; child = child->nextSibling()) { + if (!is<Element>(child)) + continue; + + if (AccessibilityObject* axObject = cache->getOrCreate(child)) { + String description = axObject->ariaLabeledByAttribute(); + if (description.isEmpty()) + description = accessibleNameForNode(child); + appendNameToStringBuilder(builder, description); + } + } + + return builder.toString(); +} + String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const { StringBuilder builder; unsigned size = elements.size(); - for (unsigned i = 0; i < size; ++i) { - if (i) - builder.append(' '); - - builder.append(accessibleNameForNode(elements[i])); - } + for (unsigned i = 0; i < size; ++i) + appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node())); return builder.toString(); } @@ -1931,18 +2036,66 @@ bool AccessibilityNodeObject::canSetFocusAttribute() const // NOTE: It would be more accurate to ask the document whether setFocusedElement() would // do anything. For example, setFocusedElement() will do nothing if the current focused // node will not relinquish the focus. - if (!node) + if (!is<Element>(node)) return false; - if (!node->isElementNode()) + Element& element = downcast<Element>(*node); + + if (element.isDisabledFormControl()) return false; - Element* element = toElement(node); + return element.supportsFocus(); +} - if (element->isDisabledFormControl()) +bool AccessibilityNodeObject::canSetValueAttribute() const +{ + Node* node = this->node(); + if (!node) return false; - return element->supportsFocus(); + // The host-language readonly attribute trumps aria-readonly. + if (is<HTMLTextAreaElement>(*node)) + return !downcast<HTMLTextAreaElement>(*node).isReadOnly(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextField()) + return !input.isReadOnly(); + } + + String readOnly = ariaReadOnlyValue(); + if (!readOnly.isEmpty()) + return readOnly == "true" ? false : true; + + if (isNonNativeTextControl()) + return true; + + if (isMeter()) + return false; + + if (isProgressIndicator() || isSlider()) + return true; + +#if PLATFORM(GTK) + // In ATK, input types which support aria-readonly are treated as having a + // settable value if the user can modify the widget's value or its state. + if (supportsARIAReadOnly() || isRadioButton()) + return true; +#endif + + if (isWebArea()) { + Document* document = this->document(); + if (!document) + return false; + + if (HTMLElement* body = document->bodyOrFrameset()) { + if (body->hasEditableStyle()) + return true; + } + + return document->hasEditableStyle(); + } + + return node->hasEditableStyle(); } AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const @@ -2008,22 +2161,12 @@ AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(Accessibilit return role; } -// If you call node->hasEditableStyle() since that will return true if an ancestor is editable. -// This only returns true if this is the element that actually has the contentEditable attribute set. -bool AccessibilityNodeObject::hasContentEditableAttributeSet() const -{ - if (!hasAttribute(contenteditableAttr)) - return false; - const AtomicString& contentEditableValue = getAttribute(contenteditableAttr); - // Both "true" (case-insensitive) and the empty string count as true. - return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true"); -} - bool AccessibilityNodeObject::canSetSelectedAttribute() const { // Elements that can be selected switch (roleValue()) { case CellRole: + case GridCellRole: case RadioButtonRole: case RowHeaderRole: case RowRole: @@ -2032,6 +2175,9 @@ bool AccessibilityNodeObject::canSetSelectedAttribute() const case TreeGridRole: case TreeItemRole: case TreeRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: + case MenuItemRole: return isEnabled(); default: return false; diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.h b/Source/WebCore/accessibility/AccessibilityNodeObject.h index 28fad801b..37d6eb01f 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.h +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityNodeObject_h -#define AccessibilityNodeObject_h +#pragma once #include "AccessibilityObject.h" #include "LayoutRect.h" @@ -37,123 +36,114 @@ namespace WebCore { class AXObjectCache; class Element; -class Frame; -class FrameView; -class HitTestResult; -class HTMLAnchorElement; -class HTMLAreaElement; -class HTMLElement; class HTMLLabelElement; -class HTMLMapElement; -class HTMLSelectElement; -class IntPoint; -class IntSize; class Node; -class RenderListBox; -class RenderTextControl; -class RenderView; -class VisibleSelection; -class Widget; - + +enum MouseButtonListenerResultFilter { + ExcludeBodyElement = 1, + IncludeBodyElement, +}; + class AccessibilityNodeObject : public AccessibilityObject { -protected: - explicit AccessibilityNodeObject(Node*); public: - static PassRefPtr<AccessibilityNodeObject> create(Node*); + static Ref<AccessibilityNodeObject> create(Node*); virtual ~AccessibilityNodeObject(); - virtual void init() override; - - virtual bool isAccessibilityNodeObject() const override { return true; } - - virtual bool canvasHasFallbackContent() const override; - - virtual bool isAnchor() const override; - virtual bool isControl() const override; - virtual bool isFieldset() const override; - virtual bool isGroup() const override; - virtual bool isHeading() const override; - virtual bool isHovered() const override; - virtual bool isImage() const override; - virtual bool isImageButton() const override; - virtual bool isInputImage() const override; - virtual bool isLink() const override; - virtual bool isMenu() const override; - virtual bool isMenuBar() const override; - virtual bool isMenuButton() const override; - virtual bool isMenuItem() const override; - virtual bool isMenuRelated() const override; - virtual bool isMultiSelectable() const override; + void init() override; + + bool canvasHasFallbackContent() const override; + + bool isControl() const override; + bool isFieldset() const override; + bool isGroup() const override; + bool isHeading() const override; + bool isHovered() const override; + bool isImage() const override; + bool isImageButton() const override; + bool isInputImage() const override; + bool isLink() const override; + bool isMenu() const override; + bool isMenuBar() const override; + bool isMenuButton() const override; + bool isMenuItem() const override; + bool isMenuRelated() const override; + bool isMultiSelectable() const override; virtual bool isNativeCheckboxOrRadio() const; - virtual bool isNativeImage() const override; - virtual bool isNativeTextControl() const override; - virtual bool isPasswordField() const override; - virtual bool isProgressIndicator() const override; - virtual bool isSearchField() const override; - virtual bool isSlider() const override; - - virtual bool isChecked() const override; - virtual bool isEnabled() const override; - virtual bool isIndeterminate() const override; - virtual bool isPressed() const override; - virtual bool isReadOnly() const override; - virtual bool isRequired() const override; - virtual bool supportsRequiredAttribute() const override; - - virtual bool canSetSelectedAttribute() const override; + bool isNativeImage() const override; + bool isNativeTextControl() const override; + bool isPasswordField() const override; + AccessibilityObject* passwordFieldOrContainingPasswordField() override; + bool isProgressIndicator() const override; + bool isSearchField() const override; + bool isSlider() const override; + + bool isChecked() const override; + bool isEnabled() const override; + bool isIndeterminate() const override; + bool isPressed() const override; + bool isRequired() const override; + bool supportsRequiredAttribute() const override; + + bool canSetSelectedAttribute() const override; void setNode(Node*); - virtual Node* node() const override { return m_node; } - virtual Document* document() const override; - - virtual bool canSetFocusAttribute() const override; - virtual int headingLevel() const override; - - virtual String valueDescription() const override; - virtual float valueForRange() const override; - virtual float maxValueForRange() const override; - virtual float minValueForRange() const override; - virtual float stepValueForRange() const override; - - virtual AccessibilityObject* selectedRadioButton() override; - virtual AccessibilityObject* selectedTabItem() override; - virtual AccessibilityButtonState checkboxOrRadioValue() const override; - - virtual unsigned hierarchicalLevel() const override; - virtual String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; - virtual String accessibilityDescription() const override; - virtual String helpText() const override; - virtual String title() const override; - virtual String text() const override; - virtual String stringValue() const override; - virtual void colorValue(int& r, int& g, int& b) const override; - virtual String ariaLabeledByAttribute() const override; - virtual bool hasAttributesRequiredForInclusion() const override final; - - virtual Element* actionElement() const override; - Element* mouseButtonListener() const; - virtual Element* anchorElement() const override; + Node* node() const override { return m_node; } + Document* document() const override; + + bool canSetFocusAttribute() const override; + int headingLevel() const override; + + bool canSetValueAttribute() const override; + + String valueDescription() const override; + float valueForRange() const override; + float maxValueForRange() const override; + float minValueForRange() const override; + float stepValueForRange() const override; + + AccessibilityObject* selectedRadioButton() override; + AccessibilityObject* selectedTabItem() override; + AccessibilityButtonState checkboxOrRadioValue() const override; + + unsigned hierarchicalLevel() const override; + String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; + String accessibilityDescriptionForChildren() const; + String accessibilityDescription() const override; + String helpText() const override; + String title() const override; + String text() const override; + String stringValue() const override; + void colorValue(int& r, int& g, int& b) const override; + String ariaLabeledByAttribute() const override; + bool hasAttributesRequiredForInclusion() const final; + void setIsExpanded(bool) override; + + Element* actionElement() const override; + Element* mouseButtonListener(MouseButtonListenerResultFilter = ExcludeBodyElement) const; + Element* anchorElement() const override; AccessibilityObject* menuForMenuButton() const; virtual void changeValueByPercent(float percentChange); - virtual AccessibilityObject* firstChild() const override; - virtual AccessibilityObject* lastChild() const override; - virtual AccessibilityObject* previousSibling() const override; - virtual AccessibilityObject* nextSibling() const override; - virtual AccessibilityObject* parentObject() const override; - virtual AccessibilityObject* parentObjectIfExists() const override; + AccessibilityObject* firstChild() const override; + AccessibilityObject* lastChild() const override; + AccessibilityObject* previousSibling() const override; + AccessibilityObject* nextSibling() const override; + AccessibilityObject* parentObject() const override; + AccessibilityObject* parentObjectIfExists() const override; - virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; - virtual void childrenChanged() override; - virtual void updateAccessibilityRole() override; + void detach(AccessibilityDetachmentType, AXObjectCache*) override; + void childrenChanged() override; + void updateAccessibilityRole() override; - virtual void increment() override; - virtual void decrement() override; + void increment() override; + void decrement() override; - virtual LayoutRect elementRect() const override; + LayoutRect elementRect() const override; protected: + explicit AccessibilityNodeObject(Node*); + AccessibilityRole m_ariaRole; bool m_childrenDirty; mutable AccessibilityRole m_roleForMSAA; @@ -161,50 +151,51 @@ protected: bool m_initialized; #endif - virtual bool isDetached() const override { return !m_node; } + bool isDetached() const override { return !m_node; } virtual AccessibilityRole determineAccessibilityRole(); - virtual void addChildren() override; - virtual void addChild(AccessibilityObject*) override; - virtual void insertChild(AccessibilityObject*, unsigned index) override; + void addChildren() override; + void addChild(AccessibilityObject*) override; + void insertChild(AccessibilityObject*, unsigned index) override; - virtual bool canHaveChildren() const override; - virtual AccessibilityRole ariaRoleAttribute() const override; - AccessibilityRole determineAriaRoleAttribute() const; + bool canHaveChildren() const override; + AccessibilityRole ariaRoleAttribute() const override; + virtual AccessibilityRole determineAriaRoleAttribute() const; AccessibilityRole remapAriaRoleDueToParent(AccessibilityRole) const; - bool hasContentEditableAttributeSet() const; - virtual bool isDescendantOfBarrenParent() const override; + bool isDescendantOfBarrenParent() const override; void alterSliderValue(bool increase); void changeValueByStep(bool increase); // This returns true if it's focusable but it's not content editable and it's not a control or ARIA control. bool isGenericFocusableElement() const; + bool isLabelable() const; HTMLLabelElement* labelForElement(Element*) const; + String textForLabelElement(Element*) const; String ariaAccessibilityDescription() const; void ariaLabeledByElements(Vector<Element*>& elements) const; String accessibilityDescriptionForElements(Vector<Element*> &elements) const; - virtual LayoutRect boundingBoxRect() const override; - virtual String ariaDescribedByAttribute() const override; + LayoutRect boundingBoxRect() const override; + String ariaDescribedByAttribute() const override; Element* menuElementForMenuButton() const; Element* menuItemElementForMenu() const; AccessibilityObject* menuButtonForMenu() const; + AccessibilityObject* captionForFigure() const; private: - Node* m_node; - - virtual void accessibilityText(Vector<AccessibilityText>&) override; + bool isAccessibilityNodeObject() const final { return true; } + void accessibilityText(Vector<AccessibilityText>&) override; virtual void titleElementText(Vector<AccessibilityText>&) const; void alternativeText(Vector<AccessibilityText>&) const; void visibleText(Vector<AccessibilityText>&) const; void helpText(Vector<AccessibilityText>&) const; String alternativeTextForWebArea() const; void ariaLabeledByText(Vector<AccessibilityText>&) const; - virtual bool computeAccessibilityIsIgnored() const override; + bool computeAccessibilityIsIgnored() const override; bool usesAltTagForTextComputation() const; -}; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityNodeObject, isAccessibilityNodeObject()) + Node* m_node; +}; } // namespace WebCore -#endif // AccessibilityNodeObject_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityNodeObject, isAccessibilityNodeObject()) diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index 2294167e3..0f6319e37 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,26 +31,34 @@ #include "AXObjectCache.h" #include "AccessibilityRenderObject.h" +#include "AccessibilityScrollView.h" #include "AccessibilityTable.h" #include "DOMTokenList.h" #include "Editor.h" +#include "ElementIterator.h" +#include "EventHandler.h" #include "FloatRect.h" #include "FocusController.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" +#include "HTMLDetailsElement.h" +#include "HTMLInputElement.h" #include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "HitTestResult.h" #include "LocalizedStrings.h" #include "MainFrame.h" #include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" -#include "NotImplemented.h" #include "Page.h" #include "RenderImage.h" +#include "RenderLayer.h" #include "RenderListItem.h" #include "RenderListMarker.h" #include "RenderMenuList.h" +#include "RenderText.h" #include "RenderTextControl.h" #include "RenderTheme.h" #include "RenderView.h" @@ -63,6 +71,7 @@ #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> #include <wtf/text/StringBuilder.h> #include <wtf/text/WTFString.h> @@ -77,8 +86,8 @@ AccessibilityObject::AccessibilityObject() , m_haveChildren(false) , m_role(UnknownRole) , m_lastKnownIsIgnoredValue(DefaultBehavior) -#if PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY)) - , m_wrapper(0) +#if PLATFORM(GTK) + , m_wrapper(nullptr) #endif { } @@ -99,7 +108,7 @@ void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXO clearChildren(); #if HAVE(ACCESSIBILITY) - setWrapper(0); + setWrapper(nullptr); #endif } @@ -204,6 +213,9 @@ bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityO case MisspelledWordSearchKey: return axObject->hasMisspelling(); + case OutlineSearchKey: + return axObject->isTree(); + case PlainTextSearchKey: return axObject->hasPlainText(); @@ -223,11 +235,11 @@ bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityO case TableSameLevelSearchKey: return criteria->startObject - && axObject->isAccessibilityTable() - && axObject->tableLevel() == criteria->startObject->tableLevel(); + && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility() + && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel(); case TableSearchKey: - return axObject->isAccessibilityTable(); + return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility(); case TextFieldSearchKey: return axObject->isTextControl(); @@ -280,17 +292,95 @@ bool AccessibilityObject::accessibilityObjectContainsText(String* text) const || stringValue().contains(*text, false); } +// ARIA marks elements as having their accessible name derive from either their contents, or their author provide name. +bool AccessibilityObject::accessibleNameDerivesFromContent() const +{ + // First check for objects specifically identified by ARIA. + switch (ariaRoleAttribute()) { + case ApplicationAlertRole: + case ApplicationAlertDialogRole: + case ApplicationDialogRole: + case ApplicationLogRole: + case ApplicationMarqueeRole: + case ApplicationStatusRole: + case ApplicationTimerRole: + case ComboBoxRole: + case DefinitionRole: + case DocumentRole: + case DocumentArticleRole: + case DocumentMathRole: + case DocumentNoteRole: + case LandmarkRegionRole: + case FormRole: + case GridRole: + case GroupRole: + case ImageRole: + case ListRole: + case ListBoxRole: + case LandmarkBannerRole: + case LandmarkComplementaryRole: + case LandmarkContentInfoRole: + case LandmarkNavigationRole: + case LandmarkMainRole: + case LandmarkSearchRole: + case MenuRole: + case MenuBarRole: + case ProgressIndicatorRole: + case RadioGroupRole: + case ScrollBarRole: + case SliderRole: + case SpinButtonRole: + case SplitterRole: + case TableRole: + case TabListRole: + case TabPanelRole: + case TextAreaRole: + case TextFieldRole: + case ToolbarRole: + case TreeGridRole: + case TreeRole: + case WebApplicationRole: + return false; + default: + break; + } + + // Now check for generically derived elements now that we know the element does not match a specific ARIA role. + switch (roleValue()) { + case SliderRole: + return false; + default: + break; + } + + return true; +} + +String AccessibilityObject::computedLabel() +{ + // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now. + // Also hold onto this object in case updateBackingStore deletes this node. + RefPtr<AccessibilityObject> protectedThis(this); + updateBackingStore(); + Vector<AccessibilityText> text; + accessibilityText(text); + if (text.size()) + return text[0].text; + return String(); +} + bool AccessibilityObject::isBlockquote() const { - return node() && node()->hasTagName(blockquoteTag); + return roleValue() == BlockquoteRole; } bool AccessibilityObject::isTextControl() const { switch (roleValue()) { + case ComboBoxRole: + case SearchFieldRole: case TextAreaRole: case TextFieldRole: - case ComboBoxRole: return true; default: return false; @@ -299,19 +389,24 @@ bool AccessibilityObject::isTextControl() const bool AccessibilityObject::isARIATextControl() const { - return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole; + return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole || ariaRoleAttribute() == SearchFieldRole; +} + +bool AccessibilityObject::isNonNativeTextControl() const +{ + return (isARIATextControl() || hasContentEditableAttributeSet()) && !isNativeTextControl(); } bool AccessibilityObject::isLandmark() const { AccessibilityRole role = roleValue(); - return role == LandmarkApplicationRole - || role == LandmarkBannerRole + return role == LandmarkBannerRole || role == LandmarkComplementaryRole || role == LandmarkContentInfoRole || role == LandmarkMainRole || role == LandmarkNavigationRole + || role == LandmarkRegionRole || role == LandmarkSearchRole; } @@ -330,13 +425,11 @@ bool AccessibilityObject::hasMisspelling() const if (!textChecker) return false; - const UChar* chars = stringValue().deprecatedCharacters(); - int charsLength = stringValue().length(); bool isMisspelled = false; if (unifiedTextCheckerEnabled(frame)) { Vector<TextCheckingResult> results; - checkTextOfParagraph(textChecker, chars, charsLength, TextCheckingTypeSpelling, results); + checkTextOfParagraph(*textChecker, stringValue(), TextCheckingTypeSpelling, results, frame->selection().selection()); if (!results.isEmpty()) isMisspelled = true; return isMisspelled; @@ -344,16 +437,16 @@ bool AccessibilityObject::hasMisspelling() const int misspellingLength = 0; int misspellingLocation = -1; - textChecker->checkSpellingOfString(chars, charsLength, &misspellingLocation, &misspellingLength); + textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength); if (misspellingLength || misspellingLocation != -1) isMisspelled = true; return isMisspelled; } -int AccessibilityObject::blockquoteLevel() const +unsigned AccessibilityObject::blockquoteLevel() const { - int level = 0; + unsigned level = 0; for (Node* elementNode = node(); elementNode; elementNode = elementNode->parentNode()) { if (elementNode->hasTagName(blockquoteTag)) ++level; @@ -364,29 +457,53 @@ int AccessibilityObject::blockquoteLevel() const AccessibilityObject* AccessibilityObject::parentObjectUnignored() const { - AccessibilityObject* parent; - for (parent = parentObject(); parent && parent->accessibilityIsIgnored(); parent = parent->parentObject()) { + return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { + return !object.accessibilityIsIgnored(); + })); +} + +AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const +{ + AccessibilityObject* previous; + ASSERT(limit >= 0); + for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) { + limit--; + if (limit <= 0) + break; } - - return parent; + return previous; +} + +AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const +{ + AccessibilityObject* next; + ASSERT(limit >= 0); + for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) { + limit--; + if (limit <= 0) + break; + } + return next; } AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) { if (!node) - return 0; + return nullptr; AXObjectCache* cache = node->document().axObjectCache(); - + if (!cache) + return nullptr; + AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { - node = NodeTraversal::next(node); + node = NodeTraversal::next(*node); while (node && !node->renderer()) - node = NodeTraversal::nextSkippingChildren(node); + node = NodeTraversal::nextSkippingChildren(*node); if (!node) - return 0; + return nullptr; accessibleObject = cache->getOrCreate(node->renderer()); } @@ -394,19 +511,26 @@ AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const No return accessibleObject; } +bool AccessibilityObject::isDescendantOfRole(AccessibilityRole role) const +{ + return AccessibilityObject::matchedParent(*this, false, [&role] (const AccessibilityObject& object) { + return object.roleValue() == role; + }) != nullptr; +} + static void appendAccessibilityObject(AccessibilityObject* object, AccessibilityObject::AccessibilityChildrenVector& results) { // Find the next descendant of this attachment object so search can continue through frames. if (object->isAttachment()) { Widget* widget = object->widgetForAttachmentView(); - if (!widget || !widget->isFrameView()) + if (!is<FrameView>(widget)) return; - Document* doc = toFrameView(widget)->frame().document(); - if (!doc || !doc->hasLivingRenderTree()) + Document* document = downcast<FrameView>(*widget).frame().document(); + if (!document || !document->hasLivingRenderTree()) return; - object = object->axObjectCache()->getOrCreate(doc); + object = object->axObjectCache()->getOrCreate(document); } if (object) @@ -417,7 +541,7 @@ static void appendChildrenToArray(AccessibilityObject* object, bool isForward, A { // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables). // The rows from the table should be queried, since those are direct descendants of the table, and they contain content. - const auto& searchChildren = object->isAccessibilityTable() ? toAccessibilityTable(object)->rows() : object->children(); + const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children(); size_t childrenSize = searchChildren.size(); @@ -463,7 +587,8 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite if (!criteria) return; - axObjectCache()->startCachingComputedObjectAttributesUntilTreeMutates(); + if (AXObjectCache* cache = axObjectCache()) + cache->startCachingComputedObjectAttributesUntilTreeMutates(); // This search mechanism only searches the elements before/after the starting object. // It does this by stepping up the parent chain and at each level doing a DFS. @@ -475,21 +600,23 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite bool isForward = criteria->searchDirection == SearchDirectionNext; - // In the first iteration of the loop, it will examine the children of the start object for matches. - // However, when going backwards, those children should not be considered, so the loop is skipped ahead. - AccessibilityObject* previousObject = 0; - if (!isForward) { + // The first iteration of the outer loop will examine the children of the start object for matches. However, when + // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an + // exception when no start object was specified because we want to search everything regardless of search direction. + AccessibilityObject* previousObject = nullptr; + if (!isForward && startObject != this) { previousObject = startObject; startObject = startObject->parentObjectUnignored(); } // The outer loop steps up the parent chain each time (unignored is important here because otherwise elements would be searched twice) - for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) { + for (AccessibilityObject* stopSearchElement = parentObjectUnignored(); startObject && startObject != stopSearchElement; startObject = startObject->parentObjectUnignored()) { // Only append the children after/before the previous element, so that the search does not check elements that are // already behind/ahead of start element. AccessibilityChildrenVector searchStack; - appendChildrenToArray(startObject, isForward, previousObject, searchStack); + if (!criteria->immediateDescendantsOnly || startObject == this) + appendChildrenToArray(startObject, isForward, previousObject, searchStack); // This now does a DFS at the current level of the parent. while (!searchStack.isEmpty()) { @@ -499,20 +626,194 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results)) break; - appendChildrenToArray(searchObject, isForward, 0, searchStack); + if (!criteria->immediateDescendantsOnly) + appendChildrenToArray(searchObject, isForward, 0, searchStack); } if (results.size() >= criteria->resultsLimit) break; // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element. - if (!isForward && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results)) + if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results)) break; previousObject = startObject; } } +// Returns the range that is fewer positions away from the reference range. +// NOTE: The after range is expected to ACTUALLY be after the reference range and the before +// range is expected to ACTUALLY be before. These are not checked for performance reasons. +static RefPtr<Range> rangeClosestToRange(Range* referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange) +{ + if (!referenceRange) + return nullptr; + + // The treeScope for shadow nodes may not be the same scope as another element in a document. + // Comparisons may fail in that case, which are expected behavior and should not assert. + if (afterRange && (referenceRange->endPosition().isNull() || ((afterRange->startPosition().anchorNode()->compareDocumentPosition(*referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))) + return nullptr; + ASSERT(!afterRange || afterRange->startPosition() >= referenceRange->endPosition()); + + if (beforeRange && (referenceRange->startPosition().isNull() || ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(*referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED))) + return nullptr; + ASSERT(!beforeRange || beforeRange->endPosition() <= referenceRange->startPosition()); + + if (!afterRange && !beforeRange) + return nullptr; + if (afterRange && !beforeRange) + return afterRange; + if (!afterRange && beforeRange) + return beforeRange; + + unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition()); + unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition()); + + return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange; +} + +RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String>& searchStrings) const +{ + Frame* frame = this->frame(); + if (!frame) + return nullptr; + + if (!referenceRange) + return nullptr; + + bool isBackwardSearch = searchDirection == SearchDirectionPrevious; + FindOptions findOptions = AtWordStarts | AtWordEnds | CaseInsensitive | StartInSelection; + if (isBackwardSearch) + findOptions |= Backwards; + + RefPtr<Range> closestStringRange = nullptr; + for (const auto& searchString : searchStrings) { + if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) { + if (!closestStringRange) + closestStringRange = searchStringRange; + else { + // If searching backward, use the trailing range edges to correctly determine which + // range is closest. Similarly, if searching forward, use the leading range edges. + Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition(); + Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition(); + + int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode(); + int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode(); + Node* closestContainerNode = closestStringPosition.containerNode(); + Node* searchContainerNode = searchStringPosition.containerNode(); + + short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset).releaseReturnValue(); + if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0)) + closestStringRange = searchStringRange; + } + } + } + return closestStringRange; +} + +// Returns the range of the entire document if there is no selection. +RefPtr<Range> AccessibilityObject::selectionRange() const +{ + Frame* frame = this->frame(); + if (!frame) + return nullptr; + + const VisibleSelection& selection = frame->selection().selection(); + if (!selection.isNone()) + return selection.firstRange(); + + return Range::create(*frame->document()); +} + +RefPtr<Range> AccessibilityObject::elementRange() const +{ + return AXObjectCache::rangeForNodeContents(node()); +} + +String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria) +{ + ASSERT(criteria); + + if (!criteria) + return String(); + + Frame* frame = this->frame(); + if (!frame) + return String(); + + AccessibilitySelectTextActivity& activity = criteria->activity; + AccessibilitySelectTextAmbiguityResolution& ambiguityResolution = criteria->ambiguityResolution; + String& replacementString = criteria->replacementString; + Vector<String>& searchStrings = criteria->searchStrings; + + RefPtr<Range> selectedStringRange = selectionRange(); + // When starting our search again, make this a zero length range so that search forwards will find this selected range if its appropriate. + selectedStringRange->setEnd(selectedStringRange->startContainer(), selectedStringRange->startOffset()); + + RefPtr<Range> closestAfterStringRange = nullptr; + RefPtr<Range> closestBeforeStringRange = nullptr; + // Search forward if necessary. + if (ambiguityResolution == ClosestAfterSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution) + closestAfterStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionNext, searchStrings); + // Search backward if necessary. + if (ambiguityResolution == ClosestBeforeSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution) + closestBeforeStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionPrevious, searchStrings); + + // Determine which candidate is closest to the selection and perform the activity. + if (RefPtr<Range> closestStringRange = rangeClosestToRange(selectedStringRange.get(), WTFMove(closestAfterStringRange), WTFMove(closestBeforeStringRange))) { + // If the search started within a text control, ensure that the result is inside that element. + if (element() && element()->isTextFormControl()) { + if (!closestStringRange->startContainer().isDescendantOrShadowDescendantOf(element()) || !closestStringRange->endContainer().isDescendantOrShadowDescendantOf(element())) + return String(); + } + + String closestString = closestStringRange->text(); + bool replaceSelection = false; + if (frame->selection().setSelectedRange(closestStringRange.get(), DOWNSTREAM, true)) { + switch (activity) { + case FindAndCapitalize: + replacementString = closestString; + makeCapitalized(&replacementString, 0); + replaceSelection = true; + break; + case FindAndUppercase: + replacementString = closestString.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + replaceSelection = true; + break; + case FindAndLowercase: + replacementString = closestString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + replaceSelection = true; + break; + case FindAndReplaceActivity: { + replaceSelection = true; + // When applying find and replace activities, we want to match the capitalization of the replaced text, + // (unless we're replacing with an abbreviation.) + if (closestString.length() > 0 && replacementString.length() > 2 && replacementString != replacementString.convertToUppercaseWithoutLocale()) { + if (closestString[0] == u_toupper(closestString[0])) + makeCapitalized(&replacementString, 0); + else + replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + } + break; + } + case FindAndSelectActivity: + break; + } + + // A bit obvious, but worth noting the API contract for this method is that we should + // return the replacement string when replacing, but the selected string if not. + if (replaceSelection) { + frame->editor().replaceSelectionWithText(replacementString, true, true); + return replacementString; + } + + return closestString; + } + } + + return String(); +} + bool AccessibilityObject::hasAttributesRequiredForInclusion() const { // These checks are simplified in the interest of execution speed. @@ -532,7 +833,7 @@ bool AccessibilityObject::hasAttributesRequiredForInclusion() const bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) { - return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; + return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole || ariaRole == SwitchRole || ariaRole == SearchFieldRole; } bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) @@ -576,34 +877,89 @@ IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector if (!obj) return IntRect(); - size_t count = quads.size(); - if (!count) - return IntRect(); - - IntRect result; - for (size_t i = 0; i < count; ++i) { - IntRect r = quads[i].enclosingBoundingBox(); + FloatRect result; + for (const auto& quad : quads) { + FloatRect r = quad.enclosingBoundingBox(); if (!r.isEmpty()) { if (obj->style().hasAppearance()) - obj->theme().adjustRepaintRect(obj, r); + obj->theme().adjustRepaintRect(*obj, r); result.unite(r); } } - return result; + return snappedIntRect(LayoutRect(result)); } -bool AccessibilityObject::press() const +bool AccessibilityObject::press() { + // The presence of the actionElement will confirm whether we should even attempt a press. Element* actionElem = actionElement(); if (!actionElem) return false; if (Frame* f = actionElem->document().frame()) f->loader().resetMultipleFormSubmissionProtection(); - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); - actionElem->accessKeyAction(true); + // Hit test at this location to determine if there is a sub-node element that should act + // as the target of the action. + Element* hitTestElement = nullptr; + Document* document = this->document(); + if (document) { + HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); + HitTestResult hitTestResult(clickPoint()); + document->renderView()->hitTest(request, hitTestResult); + if (hitTestResult.innerNode()) { + Node* innerNode = hitTestResult.innerNode()->deprecatedShadowAncestorNode(); + if (is<Element>(*innerNode)) + hitTestElement = downcast<Element>(innerNode); + else if (innerNode) + hitTestElement = innerNode->parentElement(); + } + } + + + // Prefer the actionElement instead of this node, if the actionElement is inside this node. + Element* pressElement = this->element(); + if (!pressElement || actionElem->isDescendantOf(*pressElement)) + pressElement = actionElem; + + ASSERT(pressElement); + // Prefer the hit test element, if it is inside the target element. + if (hitTestElement && hitTestElement->isDescendantOf(*pressElement)) + pressElement = hitTestElement; + + UserGestureIndicator gestureIndicator(ProcessingUserGesture, document); + + bool dispatchedTouchEvent = false; +#if PLATFORM(IOS) + if (hasTouchEventListener()) + dispatchedTouchEvent = dispatchTouchEvent(); +#endif + if (!dispatchedTouchEvent) + pressElement->accessKeyAction(true); + return true; } + +bool AccessibilityObject::dispatchTouchEvent() +{ + bool handled = false; +#if ENABLE(IOS_TOUCH_EVENTS) + MainFrame* frame = mainFrame(); + if (!frame) + return false; + + handled = frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint()); +#endif + return handled; +} + +Frame* AccessibilityObject::frame() const +{ + Node* node = this->node(); + if (!node) + return nullptr; + + return node->document().frame(); +} MainFrame* AccessibilityObject::mainFrame() const { @@ -794,7 +1150,7 @@ static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos) { RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); RenderObject* startRenderer = renderer; - RenderStyle* style = &renderer->style(); + auto* style = &renderer->style(); // traverse backward by renderer to look for style change for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { @@ -857,7 +1213,28 @@ VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const Pla return VisiblePositionRange(startPosition, endPosition); } -static bool replacedNodeNeedsCharacter(Node* replacedNode) +RefPtr<Range> AccessibilityObject::rangeForPlainTextRange(const PlainTextRange& range) const +{ + unsigned textLength = getLengthForTextRange(); + if (range.start + range.length > textLength) + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) { + CharacterOffset start = cache->characterOffsetForIndex(range.start, this); + CharacterOffset end = cache->characterOffsetForIndex(range.start + range.length, this); + return cache->rangeForUnorderedCharacterOffsets(start, end); + } + return nullptr; +} + +VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const +{ + VisiblePosition startPosition = startOfLine(visiblePosition); + VisiblePosition endPosition = endOfLine(visiblePosition); + return VisiblePositionRange(startPosition, endPosition); +} + +bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode) { // we should always be given a rendered node and a replaced node, but be safe // replaced nodes are either attachments (widgets) or images @@ -877,30 +1254,73 @@ static RenderListItem* renderListItemContainerForNode(Node* node) { for (; node; node = node->parentNode()) { RenderBoxModelObject* renderer = node->renderBoxModelObject(); - if (renderer && renderer->isListItem()) - return toRenderListItem(renderer); + if (is<RenderListItem>(renderer)) + return downcast<RenderListItem>(renderer); } - return 0; + return nullptr; } + +static String listMarkerTextForNode(Node* node) +{ + RenderListItem* listItem = renderListItemContainerForNode(node); + if (!listItem) + return String(); + // If this is in a list item, we need to manually add the text for the list marker + // because a RenderListMarker does not have a Node equivalent and thus does not appear + // when iterating text. + return listItem->markerTextWithSuffix(); +} + // Returns the text associated with a list marker if this node is contained within a list item. -String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const +String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) { // If the range does not contain the start of the line, the list marker text should not be included. if (!isStartOfLine(visiblePositionStart)) return String(); + // We should speak the list marker only for the first line. RenderListItem* listItem = renderListItemContainerForNode(node); if (!listItem) return String(); - - // If this is in a list item, we need to manually add the text for the list marker - // because a RenderListMarker does not have a Node equivalent and thus does not appear - // when iterating text. - return listItem->markerTextWithSuffix(); + if (!inSameLine(visiblePositionStart, firstPositionInNode(&listItem->element()))) + return String(); + + return listMarkerTextForNode(node); } + +String AccessibilityObject::stringForRange(RefPtr<Range> range) const +{ + if (!range) + return String(); + + TextIterator it(range.get()); + if (it.atEnd()) + return String(); -String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const + StringBuilder builder; + for (; !it.atEnd(); it.advance()) { + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.text().length()) { + // Add a textual representation for list marker text. + // Don't add list marker text for new line character. + if (it.text().length() != 1 || !isSpaceOrNewline(it.text()[0])) + builder.append(listMarkerTextForNodeAndPosition(it.node(), VisiblePosition(range->startPosition()))); + it.appendTextToStringBuilder(builder); + } else { + // locate the node and starting offset for this replaced range + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + builder.append(objectReplacementCharacter); + } + } + + return builder.toString(); +} + +String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) { if (visiblePositionRange.isNull()) return String(); @@ -909,21 +1329,16 @@ String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionR RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length()) { - // Add a textual representation for list marker text - String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start); - if (!listMarkerText.isEmpty()) - builder.append(listMarkerText); - + if (it.text().length()) { + // Add a textual representation for list marker text. + builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start)); it.appendTextToStringBuilder(builder); } else { // locate the node and starting offset for this replaced range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); - - if (replacedNodeNeedsCharacter(node->childNode(offset))) + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) builder.append(objectReplacementCharacter); } } @@ -941,17 +1356,16 @@ int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRang RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length()) - length += it.length(); + if (it.text().length()) + length += it.text().length(); else { // locate the node and starting offset for this replaced range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); - if (replacedNodeNeedsCharacter(node->childNode(offset))) - length++; + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + ++length; } } @@ -1141,15 +1555,66 @@ VisiblePosition AccessibilityObject::previousParagraphStartPosition(const Visibl AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) - return 0; + return nullptr; RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer(); if (!obj) - return 0; + return nullptr; return obj->document().axObjectCache()->getOrCreate(obj); } + +// If you call node->hasEditableStyle() since that will return true if an ancestor is editable. +// This only returns true if this is the element that actually has the contentEditable attribute set. +bool AccessibilityObject::hasContentEditableAttributeSet() const +{ + return contentEditableAttributeIsEnabled(element()); +} + +bool AccessibilityObject::supportsARIAReadOnly() const +{ + AccessibilityRole role = roleValue(); + + return role == CheckBoxRole + || role == ColumnHeaderRole + || role == ComboBoxRole + || role == GridRole + || role == GridCellRole + || role == ListBoxRole + || role == MenuItemCheckboxRole + || role == MenuItemRadioRole + || role == RadioGroupRole + || role == RowHeaderRole + || role == SearchFieldRole + || role == SliderRole + || role == SpinButtonRole + || role == SwitchRole + || role == TextFieldRole + || role == TreeGridRole + || isPasswordField(); +} +String AccessibilityObject::ariaReadOnlyValue() const +{ + if (!hasAttribute(aria_readonlyAttr)) + return ariaRoleAttribute() != UnknownRole && supportsARIAReadOnly() ? "false" : String(); + + return getAttribute(aria_readonlyAttr).string().convertToASCIILowercase(); +} + +bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element) +{ + if (!element) + return false; + + const AtomicString& contentEditableValue = element->attributeWithoutSynchronization(contenteditableAttr); + if (contentEditableValue.isNull()) + return false; + + // Both "true" (case-insensitive) and the empty string count as true. + return contentEditableValue.isEmpty() || equalLettersIgnoringASCIICase(contentEditableValue, "true"); +} + #if HAVE(ACCESSIBILITY) int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const { @@ -1224,18 +1689,32 @@ unsigned AccessibilityObject::doAXLineForIndex(unsigned index) void AccessibilityObject::updateBackingStore() { // Updating the layout may delete this object. + RefPtr<AccessibilityObject> protectedThis(this); + if (Document* document = this->document()) { - if (!document->view()->isInLayout()) + if (!document->view()->isInRenderTreeLayout()) document->updateLayoutIgnorePendingStylesheets(); } + + updateChildrenIfNecessary(); } #endif - + +ScrollView* AccessibilityObject::scrollViewAncestor() const +{ + if (const AccessibilityObject* scrollParent = AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { + return is<AccessibilityScrollView>(object); + })) + return downcast<AccessibilityScrollView>(*scrollParent).scrollView(); + + return nullptr; +} + Document* AccessibilityObject::document() const { FrameView* frameView = documentFrameView(); if (!frameView) - return 0; + return nullptr; return frameView->frame().document(); } @@ -1244,7 +1723,7 @@ Page* AccessibilityObject::page() const { Document* document = this->document(); if (!document) - return 0; + return nullptr; return document->page(); } @@ -1255,7 +1734,7 @@ FrameView* AccessibilityObject::documentFrameView() const object = object->parentObject(); if (!object) - return 0; + return nullptr; return object->documentFrameView(); } @@ -1272,8 +1751,11 @@ const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::chi void AccessibilityObject::updateChildrenIfNecessary() { - if (!hasChildren()) - addChildren(); + if (!hasChildren()) { + // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time. + AXAttributeCacheEnabler enableCache(axObjectCache()); + addChildren(); + } } void AccessibilityObject::clearChildren() @@ -1290,16 +1772,16 @@ AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) { RenderObject* obj = node->renderer(); if (!obj) - return 0; + return nullptr; RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj); Element* anchor = axObj->anchorElement(); if (!anchor) - return 0; + return nullptr; RenderObject* anchorRenderer = anchor->renderer(); if (!anchorRenderer) - return 0; + return nullptr; return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer); } @@ -1307,16 +1789,27 @@ AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node) { if (!node) - return 0; + return nullptr; RenderObject* renderObject = node->renderer(); if (!renderObject) - return 0; + return nullptr; AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject); - for (; axObject && axObject->roleValue() != HeadingRole; axObject = axObject->parentObject()) { } - return axObject; + return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*axObject, true, [] (const AccessibilityObject& object) { + return object.roleValue() == HeadingRole; + })); +} + +const AccessibilityObject* AccessibilityObject::matchedParent(const AccessibilityObject& object, bool includeSelf, const std::function<bool(const AccessibilityObject&)>& matches) +{ + const AccessibilityObject* parent = includeSelf ? &object : object.parentObject(); + for (; parent; parent = parent->parentObject()) { + if (matches(*parent)) + return parent; + } + return nullptr; } void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) @@ -1354,21 +1847,38 @@ void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& obj->ariaTreeRows(result); } } - + +const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role) +{ + switch (role) { + case ApplicationAlertDialogRole: + case ApplicationAlertRole: + return ASCIILiteral("assertive"); + case ApplicationLogRole: + case ApplicationStatusRole: + return ASCIILiteral("polite"); + case ApplicationTimerRole: + case ApplicationMarqueeRole: + return ASCIILiteral("off"); + default: + return nullAtom; + } +} + #if HAVE(ACCESSIBILITY) const String& AccessibilityObject::actionVerb() const { #if !PLATFORM(IOS) // FIXME: Need to add verbs for select elements. - DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); - DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); - DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb())); - DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb())); - DEFINE_STATIC_LOCAL(const String, listItemAction, (AXListItemActionVerb())); + static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); + static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); + static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); + static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); + static NeverDestroyed<const String> menuListAction(AXMenuListActionVerb()); + static NeverDestroyed<const String> menuListPopupAction(AXMenuListPopupActionVerb()); + static NeverDestroyed<const String> listItemAction(AXListItemActionVerb()); switch (roleValue()) { case ButtonRole: @@ -1380,6 +1890,7 @@ const String& AccessibilityObject::actionVerb() const case RadioButtonRole: return radioButtonAction; case CheckBoxRole: + case SwitchRole: return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; case LinkRole: case WebCoreLinkRole: @@ -1401,52 +1912,127 @@ const String& AccessibilityObject::actionVerb() const bool AccessibilityObject::ariaIsMultiline() const { - return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); + return equalLettersIgnoringASCIICase(getAttribute(aria_multilineAttr), "true"); } -const AtomicString& AccessibilityObject::invalidStatus() const +String AccessibilityObject::invalidStatus() const { - DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusTrue, ("true", AtomicString::ConstructFromLiteral)); - - // aria-invalid can return false (default), grammer, spelling, or true. - const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr); + String grammarValue = ASCIILiteral("grammar"); + String falseValue = ASCIILiteral("false"); + String spellingValue = ASCIILiteral("spelling"); + String trueValue = ASCIILiteral("true"); + String undefinedValue = ASCIILiteral("undefined"); + + // aria-invalid can return false (default), grammar, spelling, or true. + String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr)); - // If 'false', empty or not present, it should return false. - if (ariaInvalid.isEmpty() || equalIgnoringCase(ariaInvalid, invalidStatusFalse)) - return invalidStatusFalse; + if (ariaInvalid.isEmpty()) { + // We should expose invalid status for input types. + Node* node = this->node(); + if (node && is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.hasBadInput() || input.typeMismatch()) + return trueValue; + } + return falseValue; + } - // Only 'true', 'grammar' and 'spelling' are values recognised by the WAI-ARIA - // specification. Any other non empty string should be treated as 'true'. - if (equalIgnoringCase(ariaInvalid, "spelling") || equalIgnoringCase(ariaInvalid, "grammar")) - return ariaInvalid; - return invalidStatusTrue; + // If "false", "undefined" [sic, string value], empty, or missing, return "false". + if (ariaInvalid == falseValue || ariaInvalid == undefinedValue) + return falseValue; + // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0... + // ...for @aria-invalid are "grammar" and "spelling". + if (ariaInvalid == grammarValue) + return grammarValue; + if (ariaInvalid == spellingValue) + return spellingValue; + // Any other non empty string should be treated as "true". + return trueValue; } -bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const +AccessibilityARIACurrentState AccessibilityObject::ariaCurrentState() const +{ + // aria-current can return false (default), true, page, step, location, date or time. + String currentStateValue = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_currentAttr)); + + // If "false", empty, or missing, return false state. + if (currentStateValue.isEmpty() || currentStateValue == "false") + return ARIACurrentFalse; + + if (currentStateValue == "page") + return ARIACurrentPage; + if (currentStateValue == "step") + return ARIACurrentStep; + if (currentStateValue == "location") + return ARIACurrentLocation; + if (currentStateValue == "date") + return ARIACurrentDate; + if (currentStateValue == "time") + return ARIACurrentTime; + + // Any value not included in the list of allowed values should be treated as "true". + return ARIACurrentTrue; +} + +bool AccessibilityObject::isAriaModalDescendant(Node* ariaModalNode) const +{ + if (!ariaModalNode || !this->element()) + return false; + + if (this->element() == ariaModalNode) + return true; + + // ARIA 1.1 aria-modal, indicates whether an element is modal when displayed. + // For the decendants of the modal object, they should also be considered as aria-modal=true. + for (auto& ancestor : elementAncestors(this->element())) { + if (&ancestor == ariaModalNode) + return true; + } + return false; +} + +bool AccessibilityObject::ignoredFromARIAModalPresence() const { - Node* elementNode = node(); - if (!elementNode) + // We shouldn't ignore the top node. + if (!node() || !node()->parentNode()) + return false; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return false; + + // ariaModalNode is the current displayed modal dialog. + Node* ariaModalNode = cache->ariaModalNode(); + if (!ariaModalNode) return false; - if (!elementNode->isElementNode()) + // We only want to ignore the objects within the same frame as the modal dialog. + if (ariaModalNode->document().frame() != this->frame()) return false; - Element* element = toElement(elementNode); - return element->fastHasAttribute(attribute); + return !isAriaModalDescendant(ariaModalNode); +} + +bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const +{ + Node* node = this->node(); + return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName); } -const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const +bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const { - Node* elementNode = node(); - if (!elementNode) - return nullAtom; + Node* node = this->node(); + if (!is<Element>(node)) + return false; - if (!elementNode->isElementNode()) - return nullAtom; + return downcast<Element>(*node).hasAttributeWithoutSynchronization(attribute); +} - Element* element = toElement(elementNode); - return element->fastGetAttribute(attribute); +const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const +{ + if (Element* element = this->element()) + return element->attributeWithoutSynchronization(attribute); + return nullAtom; } // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; @@ -1458,20 +2044,17 @@ AccessibilityOrientation AccessibilityObject::orientation() const if (bounds.size().height() > bounds.size().width()) return AccessibilityOrientationVertical; - // A tie goes to horizontal. - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationUndefined; } bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const { if (!axObject || !axObject->hasChildren()) return false; - - for (const AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (parent == axObject) - return true; - } - return false; + + return AccessibilityObject::matchedParent(*this, false, [axObject] (const AccessibilityObject& object) { + return &object == axObject; + }) != nullptr; } bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const @@ -1488,22 +2071,30 @@ AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const if (child->renderer() && child->renderer()->isAnonymousBlock()) return child; } - return 0; + return nullptr; } -typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; +typedef HashMap<String, AccessibilityRole, ASCIICaseInsensitiveHash> ARIARoleMap; +typedef HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>> ARIAReverseRoleMap; + +static ARIARoleMap* gAriaRoleMap = nullptr; +static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr; struct RoleEntry { String ariaRole; AccessibilityRole webcoreRole; }; -static ARIARoleMap* createARIARoleMap() +static void initializeRoleMap() { + if (gAriaRoleMap) + return; + ASSERT(!gAriaReverseRoleMap); + const RoleEntry roles[] = { { "alert", ApplicationAlertRole }, { "alertdialog", ApplicationAlertDialogRole }, - { "application", LandmarkApplicationRole }, + { "application", WebApplicationRole }, { "article", DocumentArticleRole }, { "banner", LandmarkBannerRole }, { "button", ButtonRole }, @@ -1512,12 +2103,15 @@ static ARIARoleMap* createARIARoleMap() { "contentinfo", LandmarkContentInfoRole }, { "dialog", ApplicationDialogRole }, { "directory", DirectoryRole }, - { "grid", TableRole }, - { "gridcell", CellRole }, + { "grid", GridRole }, + { "gridcell", GridCellRole }, + { "table", TableRole }, + { "cell", CellRole }, { "columnheader", ColumnHeaderRole }, { "combobox", ComboBoxRole }, { "definition", DefinitionRole }, { "document", DocumentRole }, + { "form", FormRole }, { "rowheader", RowHeaderRole }, { "group", GroupRole }, { "heading", HeadingRole }, @@ -1536,6 +2130,7 @@ static ARIARoleMap* createARIARoleMap() { "menuitem", MenuItemRole }, { "menuitemcheckbox", MenuItemCheckboxRole }, { "menuitemradio", MenuItemRadioRole }, + { "none", PresentationalRole }, { "note", DocumentNoteRole }, { "navigation", LandmarkNavigationRole }, { "option", ListBoxOptionRole }, @@ -1543,14 +2138,17 @@ static ARIARoleMap* createARIARoleMap() { "progressbar", ProgressIndicatorRole }, { "radio", RadioButtonRole }, { "radiogroup", RadioGroupRole }, - { "region", DocumentRegionRole }, + { "region", LandmarkRegionRole }, { "row", RowRole }, + { "rowgroup", RowGroupRole }, { "scrollbar", ScrollBarRole }, { "search", LandmarkSearchRole }, + { "searchbox", SearchFieldRole }, { "separator", SplitterRole }, { "slider", SliderRole }, { "spinbutton", SpinButtonRole }, { "status", ApplicationStatusRole }, + { "switch", SwitchRole }, { "tab", TabRole }, { "tablist", TabListRole }, { "tabpanel", TabPanelRole }, @@ -1563,26 +2161,37 @@ static ARIARoleMap* createARIARoleMap() { "treegrid", TreeGridRole }, { "treeitem", TreeItemRole } }; - ARIARoleMap* roleMap = new ARIARoleMap; - for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i) - roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); - return roleMap; + gAriaRoleMap = new ARIARoleMap; + gAriaReverseRoleMap = new ARIAReverseRoleMap; + size_t roleLength = WTF_ARRAY_LENGTH(roles); + for (size_t i = 0; i < roleLength; ++i) { + gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole); + gAriaReverseRoleMap->set(roles[i].webcoreRole, roles[i].ariaRole); + } +} + +static ARIARoleMap& ariaRoleMap() +{ + initializeRoleMap(); + return *gAriaRoleMap; +} + +static ARIAReverseRoleMap& reverseAriaRoleMap() +{ + initializeRoleMap(); + return *gAriaReverseRoleMap; } AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) { ASSERT(!value.isEmpty()); - static const ARIARoleMap* roleMap = createARIARoleMap(); - Vector<String> roleVector; value.split(' ', roleVector); AccessibilityRole role = UnknownRole; - unsigned size = roleVector.size(); - for (unsigned i = 0; i < size; ++i) { - String roleName = roleVector[i]; - role = roleMap->get(roleName); + for (const auto& roleName : roleVector) { + role = ariaRoleMap().get(roleName); if (role) return role; } @@ -1590,6 +2199,18 @@ AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value return role; } +String AccessibilityObject::computedRoleString() const +{ + // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296 + AccessibilityRole role = roleValue(); + if (role == HorizontalRuleRole) + role = SplitterRole; + if (role == PopUpButtonRole || role == ToggleButtonRole) + role = ButtonRole; + + return reverseAriaRoleMap().get(role); +} + bool AccessibilityObject::hasHighlighting() const { for (Node* node = this->node(); node; node = node->parentNode()) { @@ -1600,12 +2221,89 @@ bool AccessibilityObject::hasHighlighting() const return false; } +String AccessibilityObject::roleDescription() const +{ + return getAttribute(aria_roledescriptionAttr); +} + +static bool nodeHasPresentationRole(Node* node) +{ + return nodeHasRole(node, "presentation") || nodeHasRole(node, "none"); +} + +bool AccessibilityObject::supportsPressAction() const +{ + if (isButton()) + return true; + if (roleValue() == DetailsRole) + return true; + + Element* actionElement = this->actionElement(); + if (!actionElement) + return false; + + // [Bug: 136247] Heuristic: element handlers that have more than one accessible descendant should not be exposed as supporting press. + if (actionElement != element()) { + if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(actionElement)) { + AccessibilityChildrenVector results; + // Search within for immediate descendants that are static text. If we find more than one + // then this is an event delegator actionElement and we should expose the press action. + Vector<AccessibilitySearchKey> keys({ StaticTextSearchKey, ControlSearchKey, GraphicSearchKey, HeadingSearchKey, LinkSearchKey }); + AccessibilitySearchCriteria criteria(axObj, SearchDirectionNext, emptyString(), 2, false, false); + criteria.searchKeys = keys; + axObj->findMatchingObjects(&criteria, results); + if (results.size() > 1) + return false; + } + } + + // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action. + return !nodeHasPresentationRole(actionElement); +} + +bool AccessibilityObject::supportsDatetimeAttribute() const +{ + return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag); +} + Element* AccessibilityObject::element() const { Node* node = this->node(); - if (node && node->isElementNode()) - return toElement(node); - return 0; + if (is<Element>(node)) + return downcast<Element>(node); + return nullptr; +} + +bool AccessibilityObject::isValueAutofillAvailable() const +{ + if (!isNativeTextControl()) + return false; + + Node* node = this->node(); + if (!is<HTMLInputElement>(node)) + return false; + + return downcast<HTMLInputElement>(*node).autoFillButtonType() != AutoFillButtonType::None; +} + +AutoFillButtonType AccessibilityObject::valueAutofillButtonType() const +{ + if (!isValueAutofillAvailable()) + return AutoFillButtonType::None; + + return downcast<HTMLInputElement>(*this->node()).autoFillButtonType(); +} + +bool AccessibilityObject::isValueAutofilled() const +{ + if (!isNativeTextControl()) + return false; + + Node* node = this->node(); + if (!is<HTMLInputElement>(node)) + return false; + + return downcast<HTMLInputElement>(*node).isAutoFilled(); } const AtomicString& AccessibilityObject::placeholderValue() const @@ -1614,6 +2312,10 @@ const AtomicString& AccessibilityObject::placeholderValue() const if (!placeholder.isEmpty()) return placeholder; + const AtomicString& ariaPlaceholder = getAttribute(aria_placeholderAttr); + if (!ariaPlaceholder.isEmpty()) + return ariaPlaceholder; + return nullAtom; } @@ -1622,12 +2324,9 @@ bool AccessibilityObject::isInsideARIALiveRegion() const if (supportsARIALiveRegion()) return true; - for (AccessibilityObject* axParent = parentObject(); axParent; axParent = axParent->parentObject()) { - if (axParent->supportsARIALiveRegion()) - return true; - } - - return false; + return AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { + return object.supportsARIALiveRegion(); + }) != nullptr; } bool AccessibilityObject::supportsARIAAttributes() const @@ -1636,13 +2335,14 @@ bool AccessibilityObject::supportsARIAAttributes() const return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() - || supportsARIAFlowTo() || supportsARIAOwns() || hasAttribute(aria_atomicAttr) || hasAttribute(aria_busyAttr) || hasAttribute(aria_controlsAttr) + || hasAttribute(aria_currentAttr) || hasAttribute(aria_describedbyAttr) || hasAttribute(aria_disabledAttr) + || hasAttribute(aria_flowtoAttr) || hasAttribute(aria_haspopupAttr) || hasAttribute(aria_invalidAttr) || hasAttribute(aria_labelAttr) @@ -1650,10 +2350,14 @@ bool AccessibilityObject::supportsARIAAttributes() const || hasAttribute(aria_relevantAttr); } +bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomicString& liveRegionStatus) +{ + return equalLettersIgnoringASCIICase(liveRegionStatus, "polite") || equalLettersIgnoringASCIICase(liveRegionStatus, "assertive"); +} + bool AccessibilityObject::supportsARIALiveRegion() const { - const AtomicString& liveRegion = ariaLiveRegionStatus(); - return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); + return liveRegionStatusIsEnabled(ariaLiveRegionStatus()); } AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const @@ -1662,8 +2366,10 @@ AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntP if (isAttachment()) { Widget* widget = widgetForAttachmentView(); // Normalize the point for the widget's bounds. - if (widget && widget->isFrameView()) - return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); + if (widget && widget->isFrameView()) { + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); + } } // Check if there are any mock elements that need to be handled. @@ -1672,7 +2378,7 @@ AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntP return child->elementAccessibilityHitTest(point); } - return const_cast<AccessibilityObject*>(this); + return const_cast<AccessibilityObject*>(this); } AXObjectCache* AccessibilityObject::axObjectCache() const @@ -1680,18 +2386,18 @@ AXObjectCache* AccessibilityObject::axObjectCache() const Document* doc = document(); if (doc) return doc->axObjectCache(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityObject::focusedUIElement() const { Document* doc = document(); if (!doc) - return 0; + return nullptr; Page* page = doc->page(); if (!page) - return 0; + return nullptr; return AXObjectCache::focusedUIElementForPage(page); } @@ -1699,11 +2405,11 @@ AccessibilityObject* AccessibilityObject::focusedUIElement() const AccessibilitySortDirection AccessibilityObject::sortDirection() const { const AtomicString& sortAttribute = getAttribute(aria_sortAttr); - if (equalIgnoringCase(sortAttribute, "ascending")) + if (equalLettersIgnoringASCIICase(sortAttribute, "ascending")) return SortDirectionAscending; - if (equalIgnoringCase(sortAttribute, "descending")) + if (equalLettersIgnoringASCIICase(sortAttribute, "descending")) return SortDirectionDescending; - if (equalIgnoringCase(sortAttribute, "other")) + if (equalLettersIgnoringASCIICase(sortAttribute, "other")) return SortDirectionOther; return SortDirectionNone; @@ -1714,7 +2420,8 @@ bool AccessibilityObject::supportsRangeValue() const return isProgressIndicator() || isSlider() || isScrollbar() - || isSpinButton(); + || isSpinButton() + || isAttachmentElement(); } bool AccessibilityObject::supportsARIASetSize() const @@ -1745,50 +2452,100 @@ String AccessibilityObject::identifierAttribute() const void AccessibilityObject::classList(Vector<String>& classList) const { Node* node = this->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return; - Element* element = toElement(node); - DOMTokenList* list = element->classList(); - if (!list) - return; - unsigned length = list->length(); + Element* element = downcast<Element>(node); + DOMTokenList& list = element->classList(); + unsigned length = list.length(); for (unsigned k = 0; k < length; k++) - classList.append(list->item(k).string()); + classList.append(list.item(k).string()); } +bool AccessibilityObject::supportsARIAPressed() const +{ + const AtomicString& expanded = getAttribute(aria_pressedAttr); + return equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false"); +} -bool AccessibilityObject::supportsARIAExpanded() const +bool AccessibilityObject::supportsExpanded() const { // Undefined values should not result in this attribute being exposed to ATs according to ARIA. const AtomicString& expanded = getAttribute(aria_expandedAttr); - return equalIgnoringCase(expanded, "true") || equalIgnoringCase(expanded, "false"); + if (equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false")) + return true; + switch (roleValue()) { + case ComboBoxRole: + case DisclosureTriangleRole: + case DetailsRole: + return true; + default: + return false; + } } bool AccessibilityObject::isExpanded() const { - if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_expandedAttr), "true")) return true; + if (is<HTMLDetailsElement>(node())) + return downcast<HTMLDetailsElement>(node())->isOpen(); + + // Summary element should use its details parent's expanded status. + if (isSummary()) { + if (const AccessibilityObject* parent = AccessibilityObject::matchedParent(*this, false, [] (const AccessibilityObject& object) { + return object.roleValue() == DetailsRole; + })) + return parent->isExpanded(); + } + return false; } - + +bool AccessibilityObject::supportsChecked() const +{ + switch (roleValue()) { + case CheckBoxRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: + case RadioButtonRole: + case SwitchRole: + return true; + default: + return false; + } +} + AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const { // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. - // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. - + // If it's an ARIA checkbox, radio, or switch the aria-checked attribute should be used. + // If it's a toggle button, the aria-pressed attribute is consulted. + + if (isToggleButton()) { + const AtomicString& ariaPressed = getAttribute(aria_pressedAttr); + if (equalLettersIgnoringASCIICase(ariaPressed, "true")) + return ButtonStateOn; + if (equalLettersIgnoringASCIICase(ariaPressed, "mixed")) + return ButtonStateMixed; + return ButtonStateOff; + } + const AtomicString& result = getAttribute(aria_checkedAttr); - if (equalIgnoringCase(result, "true")) + if (equalLettersIgnoringASCIICase(result, "true")) return ButtonStateOn; - if (equalIgnoringCase(result, "mixed")) { - // ARIA says that radio and menuitemradio elements must NOT expose button state mixed. + if (equalLettersIgnoringASCIICase(result, "mixed")) { + // ARIA says that radio, menuitemradio, and switch elements must NOT expose button state mixed. AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole == RadioButtonRole || ariaRole == MenuItemRadioRole) + if (ariaRole == RadioButtonRole || ariaRole == MenuItemRadioRole || ariaRole == SwitchRole) return ButtonStateOff; return ButtonStateMixed; } + if (isIndeterminate()) + return ButtonStateMixed; + return ButtonStateOff; } @@ -1797,71 +2554,89 @@ AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const // logic is the same. The goal is to compute the best scroll offset // in order to make an object visible within a viewport. // +// If the object is already fully visible, returns the same scroll +// offset. +// // In case the whole object cannot fit, you can specify a // subfocus - a smaller region within the object that should // be prioritized. If the whole object can fit, the subfocus is // ignored. // -// Example: the viewport is scrolled to the right just enough -// that the object is in view. +// If possible, the object and subfocus are centered within the +// viewport. +// +// Example 1: the object is already visible, so nothing happens. +// +----------Viewport---------+ +// +---Object---+ +// +--SubFocus--+ +// +// Example 2: the object is not fully visible, so it's centered +// within the viewport. // Before: // +----------Viewport---------+ // +---Object---+ // +--SubFocus--+ // // After: -// +----------Viewport---------+ +// +----------Viewport---------+ // +---Object---+ // +--SubFocus--+ // +// Example 3: the object is larger than the viewport, so the +// viewport moves to show as much of the object as possible, +// while also trying to center the subfocus. +// Before: +// +----------Viewport---------+ +// +---------------Object--------------+ +// +-SubFocus-+ +// +// After: +// +----------Viewport---------+ +// +---------------Object--------------+ +// +-SubFocus-+ +// // When constraints cannot be fully satisfied, the min // (left/top) position takes precedence over the max (right/bottom). // // Note that the return value represents the ideal new scroll offset. // This may be out of range - the calling function should clip this // to the available range. -static int computeBestScrollOffset(int currentScrollOffset, - int subfocusMin, int subfocusMax, - int objectMin, int objectMax, - int viewportMin, int viewportMax) { +static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax) +{ int viewportSize = viewportMax - viewportMin; - - // If the focus size is larger than the viewport size, shrink it in the - // direction of subfocus. + + // If the object size is larger than the viewport size, consider + // only a portion that's as large as the viewport, centering on + // the subfocus as much as possible. if (objectMax - objectMin > viewportSize) { - // Subfocus must be within focus: + // Since it's impossible to fit the whole object in the + // viewport, exit now if the subfocus is already within the viewport. + if (subfocusMin - currentScrollOffset >= viewportMin && subfocusMax - currentScrollOffset <= viewportMax) + return currentScrollOffset; + + // Subfocus must be within focus. subfocusMin = std::max(subfocusMin, objectMin); subfocusMax = std::min(subfocusMax, objectMax); - + // Subfocus must be no larger than the viewport size; favor top/left. if (subfocusMax - subfocusMin > viewportSize) subfocusMax = subfocusMin + viewportSize; + + // Compute the size of an object centered on the subfocus, the size of the viewport. + int centeredObjectMin = (subfocusMin + subfocusMax - viewportSize) / 2; + int centeredObjectMax = centeredObjectMin + viewportSize; - if (subfocusMin + viewportSize > objectMax) - objectMin = objectMax - viewportSize; - else { - objectMin = subfocusMin; - objectMax = subfocusMin + viewportSize; - } + objectMin = std::max(objectMin, centeredObjectMin); + objectMax = std::min(objectMax, centeredObjectMax); } // Exit now if the focus is already within the viewport. if (objectMin - currentScrollOffset >= viewportMin && objectMax - currentScrollOffset <= viewportMax) return currentScrollOffset; - - // Scroll left if we're too far to the right. - if (objectMax - currentScrollOffset > viewportMax) - return objectMax - viewportMax; - - // Scroll right if we're too far to the left. - if (objectMin - currentScrollOffset < viewportMin) - return objectMin - viewportMin; - - ASSERT_NOT_REACHED(); - - // This shouldn't happen. - return currentScrollOffset; + + // Center the object in the viewport. + return (objectMin + objectMax - viewportMin - viewportMax) / 2; } bool AccessibilityObject::isOnscreen() const @@ -1886,8 +2661,8 @@ bool AccessibilityObject::isOnscreen() const const AccessibilityObject* outer = objects[i]; const AccessibilityObject* inner = objects[i - 1]; // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. - const IntRect outerRect = i < levels ? pixelSnappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); - const IntRect innerRect = pixelSnappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect()); + const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect()); if (!outerRect.intersects(innerRect)) { isOnscreen = false; @@ -1900,7 +2675,7 @@ bool AccessibilityObject::isOnscreen() const void AccessibilityObject::scrollToMakeVisible() const { - IntRect objectRect = pixelSnappedIntRect(boundingBoxRect()); + IntRect objectRect = snappedIntRect(boundingBoxRect()); objectRect.setLocation(IntPoint()); scrollToMakeVisibleWithSubFocus(objectRect); } @@ -1910,7 +2685,7 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu // Search up the parent chain until we find the first one that's scrollable. AccessibilityObject* scrollParent = parentObject(); ScrollableArea* scrollableArea; - for (scrollableArea = 0; + for (scrollableArea = nullptr; scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); scrollParent = scrollParent->parentObject()) { } if (!scrollableArea) @@ -1921,6 +2696,11 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + if (!scrollParent->isScrollView()) { + objectRect.moveBy(scrollPosition); + objectRect.moveBy(-snappedIntRect(scrollParent->elementRect()).location()); + } + int desiredX = computeBestScrollOffset( scrollPosition.x(), objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), @@ -1934,9 +2714,16 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu scrollParent->scrollTo(IntPoint(desiredX, desiredY)); + // Convert the subfocus into the coordinates of the scroll parent. + IntRect newSubfocus = subfocus; + IntRect newElementRect = snappedIntRect(elementRect()); + IntRect scrollParentRect = snappedIntRect(scrollParent->elementRect()); + newSubfocus.move(newElementRect.x(), newElementRect.y()); + newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y()); + // Recursively make sure the scroll parent itself is visible. if (scrollParent->parentObject()) - scrollParent->scrollToMakeVisible(); + scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus); } void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const @@ -2001,6 +2788,102 @@ void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const } } } + +void AccessibilityObject::scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>& scrollers) const +{ + // Search up the parent chain until we find the first one that's scrollable. + scrollers.first = nullptr; + for (scrollers.second = parentObject(); scrollers.second; scrollers.second = scrollers.second->parentObject()) { + if ((scrollers.first = scrollers.second->getScrollableAreaIfScrollable())) + break; + } +} + +ScrollableArea* AccessibilityObject::scrollableAreaAncestor() const +{ + std::pair<ScrollableArea*, AccessibilityObject*> scrollers; + scrollAreaAndAncestor(scrollers); + return scrollers.first; +} + +IntPoint AccessibilityObject::scrollPosition() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->scrollPosition(); + + return IntPoint(); +} + +IntRect AccessibilityObject::scrollVisibleContentRect() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + + return IntRect(); +} + +IntSize AccessibilityObject::scrollContentsSize() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->contentsSize(); + + return IntSize(); +} + +bool AccessibilityObject::scrollByPage(ScrollByPageDirection direction) const +{ + std::pair<ScrollableArea*, AccessibilityObject*> scrollers; + scrollAreaAndAncestor(scrollers); + ScrollableArea* scrollableArea = scrollers.first; + AccessibilityObject* scrollParent = scrollers.second; + + if (!scrollableArea) + return false; + + IntPoint scrollPosition = scrollableArea->scrollPosition(); + IntPoint newScrollPosition = scrollPosition; + IntSize scrollSize = scrollableArea->contentsSize(); + IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + switch (direction) { + case Right: { + int scrollAmount = scrollVisibleRect.size().width(); + int newX = scrollPosition.x() - scrollAmount; + newScrollPosition.setX(std::max(newX, 0)); + break; + } + case Left: { + int scrollAmount = scrollVisibleRect.size().width(); + int newX = scrollAmount + scrollPosition.x(); + int maxX = scrollSize.width() - scrollAmount; + newScrollPosition.setX(std::min(newX, maxX)); + break; + } + case Up: { + int scrollAmount = scrollVisibleRect.size().height(); + int newY = scrollPosition.y() - scrollAmount; + newScrollPosition.setY(std::max(newY, 0)); + break; + } + case Down: { + int scrollAmount = scrollVisibleRect.size().height(); + int newY = scrollAmount + scrollPosition.y(); + int maxY = scrollSize.height() - scrollAmount; + newScrollPosition.setY(std::min(newY, maxY)); + break; + } + default: + break; + } + + if (newScrollPosition != scrollPosition) { + scrollParent->scrollTo(newScrollPosition); + document()->updateLayoutIgnorePendingStylesheets(); + return true; + } + + return false; +} + bool AccessibilityObject::lastKnownIsIgnoredValue() { @@ -2019,7 +2902,8 @@ void AccessibilityObject::notifyIfIgnoredValueChanged() { bool isIgnored = accessibilityIsIgnored(); if (lastKnownIsIgnoredValue() != isIgnored) { - axObjectCache()->childrenChanged(parentObject()); + if (AXObjectCache* cache = axObjectCache()) + cache->childrenChanged(parentObject()); setLastKnownIsIgnoredValue(isIgnored); } } @@ -2033,7 +2917,7 @@ TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() con { TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility; -#if PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(GTK) // We need to emit replaced elements for GTK, and present // them with the 'object replacement character' (0xFFFC). behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters); @@ -2072,11 +2956,9 @@ bool AccessibilityObject::accessibilityIsIgnoredByDefault() const // http://www.w3.org/TR/wai-aria/terms#def_hidden bool AccessibilityObject::isARIAHidden() const { - for (const AccessibilityObject* object = this; object; object = object->parentObject()) { - if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true")) - return true; - } - return false; + return AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { + return equalLettersIgnoringASCIICase(object.getAttribute(aria_hiddenAttr), "true"); + }) != nullptr; } // DOM component of hidden definition. @@ -2096,6 +2978,9 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const if (isARIAHidden()) return IgnoreObject; + if (ignoredFromARIAModalPresence()) + return IgnoreObject; + if (isPresentationalChildOfAriaRole()) return IgnoreObject; @@ -2104,7 +2989,11 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const bool AccessibilityObject::accessibilityIsIgnored() const { - AXComputedObjectAttributeCache* attributeCache = axObjectCache()->computedObjectAttributeCache(); + AXComputedObjectAttributeCache* attributeCache = nullptr; + AXObjectCache* cache = axObjectCache(); + if (cache) + attributeCache = cache->computedObjectAttributeCache(); + if (attributeCache) { AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID()); switch (ignored) { @@ -2119,7 +3008,8 @@ bool AccessibilityObject::accessibilityIsIgnored() const bool result = computeAccessibilityIsIgnored(); - if (attributeCache) + // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists. + if (cache && (attributeCache = cache->computedObjectAttributeCache())) attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject); return result; @@ -2141,10 +3031,146 @@ void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, cons Vector<String> idVector; idList.split(' ', idVector); - for (auto idName : idVector) { + for (const auto& idName : idVector) { if (Element* idElement = treeScope.getElementById(idName)) elements.append(idElement); } } +#if PLATFORM(COCOA) +bool AccessibilityObject::preventKeyboardDOMEventDispatch() const +{ + Frame* frame = this->frame(); + return frame && frame->settings().preventKeyboardDOMEventDispatch(); +} + +void AccessibilityObject::setPreventKeyboardDOMEventDispatch(bool on) +{ + Frame* frame = this->frame(); + if (!frame) + return; + frame->settings().setPreventKeyboardDOMEventDispatch(on); +} +#endif + +AccessibilityObject* AccessibilityObject::focusableAncestor() +{ + return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { + return object.canSetFocusAttribute(); + })); +} + +AccessibilityObject* AccessibilityObject::editableAncestor() +{ + return const_cast<AccessibilityObject*>(AccessibilityObject::matchedParent(*this, true, [] (const AccessibilityObject& object) { + return object.isTextControl(); + })); +} + +AccessibilityObject* AccessibilityObject::highestEditableAncestor() +{ + AccessibilityObject* editableAncestor = this->editableAncestor(); + AccessibilityObject* previousEditableAncestor = nullptr; + while (editableAncestor) { + if (editableAncestor == previousEditableAncestor) { + if (AccessibilityObject* parent = editableAncestor->parentObject()) { + editableAncestor = parent->editableAncestor(); + continue; + } + break; + } + previousEditableAncestor = editableAncestor; + editableAncestor = editableAncestor->editableAncestor(); + } + return previousEditableAncestor; +} + +bool AccessibilityObject::isStyleFormatGroup() const +{ + Node* node = this->node(); + if (!node) + return false; + + return node->hasTagName(kbdTag) || node->hasTagName(codeTag) + || node->hasTagName(preTag) || node->hasTagName(sampTag) + || node->hasTagName(varTag) || node->hasTagName(citeTag) + || node->hasTagName(insTag) || node->hasTagName(delTag) + || node->hasTagName(supTag) || node->hasTagName(subTag); +} + +bool AccessibilityObject::isSubscriptStyleGroup() const +{ + Node* node = this->node(); + return node && node->hasTagName(subTag); +} + +bool AccessibilityObject::isSuperscriptStyleGroup() const +{ + Node* node = this->node(); + return node && node->hasTagName(supTag); +} + +bool AccessibilityObject::isFigure() const +{ + Node* node = this->node(); + return node && node->hasTagName(figureTag); +} + +bool AccessibilityObject::isOutput() const +{ + Node* node = this->node(); + return node && node->hasTagName(outputTag); +} + +bool AccessibilityObject::isContainedByPasswordField() const +{ + Node* node = this->node(); + if (!node) + return false; + + if (ariaRoleAttribute() != UnknownRole) + return false; + + Element* element = node->shadowHost(); + return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(*element).isPasswordField(); +} + +void AccessibilityObject::ariaElementsFromAttribute(AccessibilityChildrenVector& children, const QualifiedName& attributeName) const +{ + Vector<Element*> elements; + elementsFromAttribute(elements, attributeName); + AXObjectCache* cache = axObjectCache(); + for (const auto& element : elements) { + if (AccessibilityObject* axObject = cache->getOrCreate(element)) + children.append(axObject); + } +} + +void AccessibilityObject::ariaControlsElements(AccessibilityChildrenVector& ariaControls) const +{ + ariaElementsFromAttribute(ariaControls, aria_controlsAttr); +} + +void AccessibilityObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const +{ + ariaElementsFromAttribute(ariaDescribedBy, aria_describedbyAttr); +} + +void AccessibilityObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const +{ + ariaElementsFromAttribute(flowTo, aria_flowtoAttr); +} + +void AccessibilityObject::ariaLabelledByElements(AccessibilityChildrenVector& ariaLabelledBy) const +{ + ariaElementsFromAttribute(ariaLabelledBy, aria_labelledbyAttr); + if (!ariaLabelledBy.size()) + ariaElementsFromAttribute(ariaLabelledBy, aria_labeledbyAttr); +} + +void AccessibilityObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const +{ + ariaElementsFromAttribute(axObjects, aria_ownsAttr); +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h index 390ae2f9e..63ea8c638 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ b/Source/WebCore/accessibility/AccessibilityObject.h @@ -11,7 +11,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -27,27 +27,28 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityObject_h -#define AccessibilityObject_h +#pragma once #include "FloatQuad.h" +#include "HTMLTextFormControlElement.h" #include "LayoutRect.h" #include "Path.h" -#include "TextIterator.h" +#include "Range.h" +#include "TextIteratorBehavior.h" #include "VisiblePosition.h" #include "VisibleSelection.h" #include <wtf/Forward.h> #include <wtf/RefPtr.h> #include <wtf/Vector.h> -#if PLATFORM(MAC) +#if PLATFORM(COCOA) #include <wtf/RetainPtr.h> #elif PLATFORM(WIN) #include "AccessibilityObjectWrapperWin.h" #include "COMPtr.h" #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) typedef struct _NSRange NSRange; @@ -62,7 +63,7 @@ OBJC_CLASS WebAccessibilityObjectWrapper; typedef WebAccessibilityObjectWrapper AccessibilityObjectWrapper; -#elif PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY)) +#elif PLATFORM(GTK) typedef struct _AtkObject AtkObject; typedef struct _AtkObject AccessibilityObjectWrapper; #else @@ -76,17 +77,14 @@ class AXObjectCache; class Element; class Frame; class FrameView; -class HTMLAnchorElement; -class HTMLAreaElement; class IntPoint; class IntSize; class MainFrame; class Node; class Page; class RenderObject; -class RenderListItem; class ScrollableArea; -class VisibleSelection; +class ScrollView; class Widget; typedef unsigned AXID; @@ -102,10 +100,12 @@ enum AccessibilityRole { ApplicationStatusRole, ApplicationTimerRole, AudioRole, + BlockquoteRole, BrowserRole, BusyIndicatorRole, ButtonRole, CanvasRole, + CaptionRole, CellRole, CheckBoxRole, ColorWellRole, @@ -116,6 +116,7 @@ enum AccessibilityRole { DescriptionListRole, DescriptionListTermRole, DescriptionListDetailRole, + DetailsRole, DirectoryRole, DisclosureTriangleRole, DivRole, @@ -123,29 +124,30 @@ enum AccessibilityRole { DocumentArticleRole, DocumentMathRole, DocumentNoteRole, - DocumentRegionRole, DrawerRole, EditableTextRole, FooterRole, FormRole, GridRole, + GridCellRole, GroupRole, GrowAreaRole, HeadingRole, HelpTagRole, HorizontalRuleRole, IgnoredRole, + InlineRole, ImageRole, ImageMapRole, ImageMapLinkRole, IncrementorRole, LabelRole, - LandmarkApplicationRole, LandmarkBannerRole, LandmarkComplementaryRole, LandmarkContentInfoRole, LandmarkMainRole, LandmarkNavigationRole, + LandmarkRegionRole, LandmarkSearchRole, LegendRole, LinkRole, @@ -154,6 +156,7 @@ enum AccessibilityRole { ListBoxOptionRole, ListItemRole, ListMarkerRole, + MarkRole, MathElementRole, MatteRole, MenuRole, @@ -167,17 +170,24 @@ enum AccessibilityRole { OutlineRole, ParagraphRole, PopUpButtonRole, + PreRole, PresentationalRole, ProgressIndicatorRole, RadioButtonRole, RadioGroupRole, RowHeaderRole, RowRole, + RowGroupRole, + RubyBaseRole, + RubyBlockRole, + RubyInlineRole, + RubyRunRole, + RubyTextRole, RulerRole, RulerMarkerRole, ScrollAreaRole, ScrollBarRole, - SeamlessWebAreaRole, + SearchFieldRole, SheetRole, SliderRole, SliderThumbRole, @@ -186,8 +196,13 @@ enum AccessibilityRole { SplitGroupRole, SplitterRole, StaticTextRole, + SummaryRole, + SwitchRole, SystemWideRole, SVGRootRole, + SVGTextRole, + SVGTSpanRole, + SVGTextPathRole, TabGroupRole, TabListRole, TabPanelRole, @@ -205,6 +220,7 @@ enum AccessibilityRole { UserInterfaceTooltipRole, ValueIndicatorRole, VideoRole, + WebApplicationRole, WebAreaRole, WebCoreLinkRole, WindowRole, @@ -219,6 +235,9 @@ enum AccessibilityTextSource { TitleTagText, PlaceholderText, LabelByElementText, + TitleText, + SubtitleText, + ActionText, }; struct AccessibilityText { @@ -231,10 +250,10 @@ struct AccessibilityText { , textSource(s) { } - AccessibilityText(const String& t, const AccessibilityTextSource& s, const Vector<RefPtr<AccessibilityObject>> elements) + AccessibilityText(const String& t, const AccessibilityTextSource& s, Vector<RefPtr<AccessibilityObject>> elements) : text(t) , textSource(s) - , textElements(elements) + , textElements(WTFMove(elements)) { } AccessibilityText(const String& t, const AccessibilityTextSource& s, const RefPtr<AccessibilityObject> element) @@ -249,20 +268,24 @@ struct AccessibilityTextUnderElementMode { enum ChildrenInclusion { TextUnderElementModeSkipIgnoredChildren, TextUnderElementModeIncludeAllChildren, + TextUnderElementModeIncludeNameFromContentsChildren, // This corresponds to ARIA concept: nameFrom }; ChildrenInclusion childrenInclusion; bool includeFocusableContent; + Node* ignoredChildNode; - AccessibilityTextUnderElementMode(ChildrenInclusion c = TextUnderElementModeSkipIgnoredChildren, bool i = false) - : childrenInclusion(c) - , includeFocusableContent(i) - { } + AccessibilityTextUnderElementMode(ChildrenInclusion c = TextUnderElementModeSkipIgnoredChildren, bool i = false, Node* ignored = nullptr) + : childrenInclusion(c) + , includeFocusableContent(i) + , ignoredChildNode(ignored) + { } }; enum AccessibilityOrientation { AccessibilityOrientationVertical, AccessibilityOrientationHorizontal, + AccessibilityOrientationUndefined, }; enum AccessibilityObjectInclusion { @@ -317,6 +340,7 @@ enum AccessibilitySearchKey { ListSearchKey, LiveRegionSearchKey, MisspelledWordSearchKey, + OutlineSearchKey, PlainTextSearchKey, RadioGroupSearchKey, SameTypeSearchKey, @@ -342,13 +366,15 @@ struct AccessibilitySearchCriteria { String searchText; unsigned resultsLimit; bool visibleOnly; + bool immediateDescendantsOnly; - AccessibilitySearchCriteria(AccessibilityObject* startObject, AccessibilitySearchDirection searchDirection, String searchText, unsigned resultsLimit, bool visibleOnly) - : startObject(startObject) - , searchDirection(searchDirection) - , searchText(searchText) - , resultsLimit(resultsLimit) - , visibleOnly(visibleOnly) + AccessibilitySearchCriteria(AccessibilityObject* startObject, AccessibilitySearchDirection searchDirection, String searchText, unsigned resultsLimit, bool visibleOnly, bool immediateDescendantsOnly) + : startObject(startObject) + , searchDirection(searchDirection) + , searchText(searchText) + , resultsLimit(resultsLimit) + , visibleOnly(visibleOnly) + , immediateDescendantsOnly(immediateDescendantsOnly) { } }; @@ -366,6 +392,11 @@ struct VisiblePositionRange { , end(e) { } + VisiblePositionRange(const VisibleSelection& selection) + : start(selection.start()) + , end(selection.end()) + { } + bool isNull() const { return start.isNull() || end.isNull(); } }; @@ -387,6 +418,38 @@ struct PlainTextRange { bool isNull() const { return !start && !length; } }; +enum AccessibilitySelectTextActivity { + FindAndReplaceActivity, + FindAndSelectActivity, + FindAndCapitalize, + FindAndLowercase, + FindAndUppercase +}; + +enum AccessibilitySelectTextAmbiguityResolution { + ClosestAfterSelectionAmbiguityResolution, + ClosestBeforeSelectionAmbiguityResolution, + ClosestToSelectionAmbiguityResolution +}; + +struct AccessibilitySelectTextCriteria { + AccessibilitySelectTextActivity activity; + AccessibilitySelectTextAmbiguityResolution ambiguityResolution; + String replacementString; + Vector<String> searchStrings; + + AccessibilitySelectTextCriteria(AccessibilitySelectTextActivity activity, AccessibilitySelectTextAmbiguityResolution ambiguityResolution, const String& replacementString) + : activity(activity) + , ambiguityResolution(ambiguityResolution) + , replacementString(replacementString) + { } +}; + +enum AccessibilityMathScriptObjectType { Subscript, Superscript }; +enum AccessibilityMathMultiscriptObjectType { PreSubscript, PreSuperscript, PostSubscript, PostSuperscript }; + +enum AccessibilityARIACurrentState { ARIACurrentFalse, ARIACurrentTrue, ARIACurrentPage, ARIACurrentStep, ARIACurrentLocation, ARIACurrentDate, ARIACurrentTime }; + class AccessibilityObject : public RefCounted<AccessibilityObject> { protected: AccessibilityObject(); @@ -412,25 +475,29 @@ public: virtual bool isAccessibilityScrollbar() const { return false; } virtual bool isAccessibilityScrollView() const { return false; } virtual bool isAccessibilitySVGRoot() const { return false; } + virtual bool isAccessibilitySVGElement() const { return false; } bool accessibilityObjectContainsText(String *) const; - - virtual bool isAnchor() const { return false; } - virtual bool isAttachment() const { return false; } + + virtual bool isAttachmentElement() const { return false; } virtual bool isHeading() const { return false; } virtual bool isLink() const { return false; } virtual bool isImage() const { return false; } + virtual bool isImageMap() const { return roleValue() == ImageMapRole; } virtual bool isNativeImage() const { return false; } virtual bool isImageButton() const { return false; } virtual bool isPasswordField() const { return false; } + bool isContainedByPasswordField() const; + virtual AccessibilityObject* passwordFieldOrContainingPasswordField() { return nullptr; } virtual bool isNativeTextControl() const { return false; } virtual bool isSearchField() const { return false; } bool isWebArea() const { return roleValue() == WebAreaRole; } - bool isSeamlessWebArea() const { return roleValue() == SeamlessWebAreaRole; } virtual bool isCheckbox() const { return roleValue() == CheckBoxRole; } virtual bool isRadioButton() const { return roleValue() == RadioButtonRole; } - virtual bool isListBox() const { return roleValue() == ListBoxRole; } + virtual bool isNativeListBox() const { return false; } + bool isListBox() const { return roleValue() == ListBoxRole; } virtual bool isListBoxOption() const { return false; } + virtual bool isAttachment() const { return false; } virtual bool isMediaTimeline() const { return false; } virtual bool isMenuRelated() const { return false; } virtual bool isMenu() const { return false; } @@ -444,9 +511,9 @@ public: virtual bool isSliderThumb() const { return false; } virtual bool isInputSlider() const { return false; } virtual bool isControl() const { return false; } + virtual bool isLabel() const { return false; } virtual bool isList() const { return false; } virtual bool isTable() const { return false; } - virtual bool isAccessibilityTable() const { return false; } virtual bool isDataTable() const { return false; } virtual bool isTableRow() const { return false; } virtual bool isTableColumn() const { return false; } @@ -463,8 +530,11 @@ public: virtual bool isSpinButtonPart() const { return false; } virtual bool isMockObject() const { return false; } virtual bool isMediaControlLabel() const { return false; } + bool isSwitch() const { return roleValue() == SwitchRole; } + bool isToggleButton() const { return roleValue() == ToggleButtonRole; } bool isTextControl() const; bool isARIATextControl() const; + bool isNonNativeTextControl() const; bool isTabList() const { return roleValue() == TabListRole; } bool isTabItem() const { return roleValue() == TabRole; } bool isRadioGroup() const { return roleValue() == RadioGroupRole; } @@ -483,7 +553,15 @@ public: bool isColorWell() const { return roleValue() == ColorWellRole; } bool isRangeControl() const; bool isMeter() const; - + bool isSplitter() const { return roleValue() == SplitterRole; } + bool isToolbar() const { return roleValue() == ToolbarRole; } + bool isStyleFormatGroup() const; + bool isSubscriptStyleGroup() const; + bool isSuperscriptStyleGroup() const; + bool isFigure() const; + bool isSummary() const { return roleValue() == SummaryRole; } + bool isOutput() const; + virtual bool isChecked() const { return false; } virtual bool isEnabled() const { return false; } virtual bool isSelected() const { return false; } @@ -494,7 +572,6 @@ public: virtual bool isMultiSelectable() const { return false; } virtual bool isOffScreen() const { return false; } virtual bool isPressed() const { return false; } - virtual bool isReadOnly() const { return false; } virtual bool isUnvisited() const { return false; } virtual bool isVisited() const { return false; } virtual bool isRequired() const { return false; } @@ -519,6 +596,8 @@ public: virtual bool hasUnderline() const { return false; } bool hasHighlighting() const; + bool supportsDatetimeAttribute() const; + virtual bool canSetFocusAttribute() const { return false; } virtual bool canSetTextRangeAttributes() const { return false; } virtual bool canSetValueAttribute() const { return false; } @@ -527,14 +606,14 @@ public: virtual bool canSetSelectedChildrenAttribute() const { return false; } virtual bool canSetExpandedAttribute() const { return false; } - Element* element() const; - virtual Node* node() const { return 0; } - virtual RenderObject* renderer() const { return 0; } + virtual Element* element() const; + virtual Node* node() const { return nullptr; } + virtual RenderObject* renderer() const { return nullptr; } virtual bool accessibilityIsIgnored() const; virtual AccessibilityObjectInclusion defaultObjectInclusion() const; bool accessibilityIsIgnoredByDefault() const; - int blockquoteLevel() const; + unsigned blockquoteLevel() const; virtual int headingLevel() const { return 0; } virtual int tableLevel() const { return 0; } virtual AccessibilityButtonState checkboxOrRadioValue() const; @@ -543,28 +622,38 @@ public: virtual float maxValueForRange() const { return 0.0f; } virtual float minValueForRange() const { return 0.0f; } virtual float stepValueForRange() const { return 0.0f; } - virtual AccessibilityObject* selectedRadioButton() { return 0; } - virtual AccessibilityObject* selectedTabItem() { return 0; } + virtual AccessibilityObject* selectedRadioButton() { return nullptr; } + virtual AccessibilityObject* selectedTabItem() { return nullptr; } virtual int layoutCount() const { return 0; } virtual double estimatedLoadingProgress() const { return 0; } static bool isARIAControl(AccessibilityRole); static bool isARIAInput(AccessibilityRole); + virtual bool supportsARIAOwns() const { return false; } - virtual void ariaOwnsElements(AccessibilityChildrenVector&) const { } - virtual bool supportsARIAFlowTo() const { return false; } - virtual void ariaFlowToElements(AccessibilityChildrenVector&) const { } - virtual bool supportsARIADescribedBy() const { return false; } - virtual void ariaDescribedByElements(AccessibilityChildrenVector&) const { } + void ariaControlsElements(AccessibilityChildrenVector&) const; + void ariaDescribedByElements(AccessibilityChildrenVector&) const; + void ariaFlowToElements(AccessibilityChildrenVector&) const; + void ariaLabelledByElements(AccessibilityChildrenVector&) const; + void ariaOwnsElements(AccessibilityChildrenVector&) const; + virtual bool ariaHasPopup() const { return false; } - virtual bool ariaPressedIsPresent() const; + bool ariaPressedIsPresent() const; bool ariaIsMultiline() const; - virtual const AtomicString& invalidStatus() const; - bool supportsARIAExpanded() const; + String invalidStatus() const; + bool supportsARIAPressed() const; + bool supportsExpanded() const; + bool supportsChecked() const; AccessibilitySortDirection sortDirection() const; virtual bool canvasHasFallbackContent() const { return false; } bool supportsRangeValue() const; String identifierAttribute() const; void classList(Vector<String>&) const; + virtual String roleDescription() const; + AccessibilityARIACurrentState ariaCurrentState() const; + + // This function checks if the object should be ignored when there's a modal dialog displayed. + bool ignoredFromARIAModalPresence() const; + bool isAriaModalDescendant(Node*) const; bool supportsARIASetSize() const; bool supportsARIAPosInSet() const; @@ -579,38 +668,50 @@ public: virtual void determineARIADropEffects(Vector<String>&) { } // Called on the root AX object to return the deepest available element. - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const { return 0; } + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const { return nullptr; } // Called on the AX object after the render tree determines which is the right AccessibilityRenderObject. virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; virtual AccessibilityObject* focusedUIElement() const; - virtual AccessibilityObject* firstChild() const { return 0; } - virtual AccessibilityObject* lastChild() const { return 0; } - virtual AccessibilityObject* previousSibling() const { return 0; } - virtual AccessibilityObject* nextSibling() const { return 0; } + virtual AccessibilityObject* firstChild() const { return nullptr; } + virtual AccessibilityObject* lastChild() const { return nullptr; } + virtual AccessibilityObject* previousSibling() const { return nullptr; } + virtual AccessibilityObject* nextSibling() const { return nullptr; } + virtual AccessibilityObject* nextSiblingUnignored(int limit) const; + virtual AccessibilityObject* previousSiblingUnignored(int limit) const; virtual AccessibilityObject* parentObject() const = 0; virtual AccessibilityObject* parentObjectUnignored() const; - virtual AccessibilityObject* parentObjectIfExists() const { return 0; } + virtual AccessibilityObject* parentObjectIfExists() const { return nullptr; } static AccessibilityObject* firstAccessibleObjectFromNode(const Node*); void findMatchingObjects(AccessibilitySearchCriteria*, AccessibilityChildrenVector&); virtual bool isDescendantOfBarrenParent() const { return false; } + + bool isDescendantOfRole(AccessibilityRole) const; + + // Text selection + RefPtr<Range> rangeOfStringClosestToRangeInDirection(Range*, AccessibilitySearchDirection, Vector<String>&) const; + RefPtr<Range> selectionRange() const; + String selectText(AccessibilitySelectTextCriteria*); - virtual AccessibilityObject* observableObject() const { return 0; } + virtual AccessibilityObject* observableObject() const { return nullptr; } virtual void linkedUIElements(AccessibilityChildrenVector&) const { } - virtual AccessibilityObject* titleUIElement() const { return 0; } + virtual AccessibilityObject* titleUIElement() const { return nullptr; } virtual bool exposesTitleUIElement() const { return true; } - virtual AccessibilityObject* correspondingLabelForControlElement() const { return 0; } - virtual AccessibilityObject* correspondingControlForLabelElement() const { return 0; } - virtual AccessibilityObject* scrollBar(AccessibilityOrientation) { return 0; } + virtual AccessibilityObject* correspondingLabelForControlElement() const { return nullptr; } + virtual AccessibilityObject* correspondingControlForLabelElement() const { return nullptr; } + virtual AccessibilityObject* scrollBar(AccessibilityOrientation) { return nullptr; } virtual AccessibilityRole ariaRoleAttribute() const { return UnknownRole; } virtual bool isPresentationalChildOfAriaRole() const { return false; } virtual bool ariaRoleHasPresentationalChildren() const { return false; } + virtual bool inheritsPresentationalRole() const { return false; } // Accessibility Text virtual void accessibilityText(Vector<AccessibilityText>&) { }; - + // A single method for getting a computed label for an AXObject. It condenses the nuances of accessibilityText. Used by Inspector. + String computedLabel(); + // A programmatic way to set a name on an AccessibleObject. virtual void setAccessibleName(const AtomicString&) { } virtual bool hasAttributesRequiredForInclusion() const; @@ -628,13 +729,17 @@ public: virtual String ariaLabeledByAttribute() const { return String(); } virtual String ariaDescribedByAttribute() const { return String(); } const AtomicString& placeholderValue() const; - + bool accessibleNameDerivesFromContent() const; + + // Abbreviations + virtual String expandedTextValue() const { return String(); } + virtual bool supportsExpandedTextValue() const { return false; } + void elementsFromAttribute(Vector<Element*>&, const QualifiedName&) const; // Only if isColorWell() virtual void colorValue(int& r, int& g, int& b) const { r = 0; g = 0; b = 0; } - void setRoleValue(AccessibilityRole role) { m_role = role; } virtual AccessibilityRole roleValue() const { return m_role; } virtual AXObjectCache* axObjectCache() const; @@ -642,14 +747,13 @@ public: static AccessibilityObject* anchorElementForNode(Node*); static AccessibilityObject* headingElementForNode(Node*); - virtual Element* anchorElement() const { return 0; } - virtual Element* actionElement() const { return 0; } + virtual Element* anchorElement() const { return nullptr; } + bool supportsPressAction() const; + virtual Element* actionElement() const { return nullptr; } virtual LayoutRect boundingBoxRect() const { return LayoutRect(); } - IntRect pixelSnappedBoundingBoxRect() const { return pixelSnappedIntRect(boundingBoxRect()); } + IntRect pixelSnappedBoundingBoxRect() const { return snappedIntRect(boundingBoxRect()); } virtual LayoutRect elementRect() const = 0; - IntRect pixelSnappedElementRect() const { return pixelSnappedIntRect(elementRect()); } LayoutSize size() const { return elementRect().size(); } - IntSize pixelSnappedSize() const { return elementRect().pixelSnappedSize(); } virtual IntPoint clickPoint(); static IntRect boundingBoxForQuads(RenderObject*, const Vector<FloatQuad>&); virtual Path elementPath() const { return Path(); } @@ -665,13 +769,15 @@ public: virtual String selectedText() const { return String(); } virtual const AtomicString& accessKey() const { return nullAtom; } const String& actionVerb() const; - virtual Widget* widget() const { return 0; } - virtual Widget* widgetForAttachmentView() const { return 0; } + virtual Widget* widget() const { return nullptr; } + virtual Widget* widgetForAttachmentView() const { return nullptr; } Page* page() const; virtual Document* document() const; virtual FrameView* documentFrameView() const; + Frame* frame() const; MainFrame* mainFrame() const; Document* topDocument() const; + ScrollView* scrollViewAncestor() const; String language() const; // 1-based, to match the aria-level spec. virtual unsigned hierarchicalLevel() const { return 0; } @@ -685,8 +791,8 @@ public: virtual void setSelectedRows(AccessibilityChildrenVector&) { } virtual void makeRangeVisible(const PlainTextRange&) { } - virtual bool press() const; - bool performDefaultAction() const { return press(); } + virtual bool press(); + bool performDefaultAction() { return press(); } virtual AccessibilityOrientation orientation() const; virtual void increment() { } @@ -699,23 +805,27 @@ public: virtual void addChildren() { } virtual void addChild(AccessibilityObject*) { } virtual void insertChild(AccessibilityObject*, unsigned) { } + + virtual bool shouldIgnoreAttributeRole() const { return false; } virtual bool canHaveChildren() const { return true; } virtual bool hasChildren() const { return m_haveChildren; } virtual void updateChildrenIfNecessary(); virtual void setNeedsToUpdateChildren() { } virtual void clearChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) virtual void detachFromParent(); #else virtual void detachFromParent() { } #endif + virtual bool isDetachedFromParent() { return false; } + virtual bool canHaveSelectedChildren() const { return false; } virtual void selectedChildren(AccessibilityChildrenVector&) { } virtual void visibleChildren(AccessibilityChildrenVector&) { } virtual void tabChildren(AccessibilityChildrenVector&) { } virtual bool shouldFocusActiveDescendant() const { return false; } - virtual AccessibilityObject* activeDescendant() const { return 0; } + virtual AccessibilityObject* activeDescendant() const { return nullptr; } virtual void handleActiveDescendantChanged() { } virtual void handleAriaExpandedChanged() { } bool isDescendantOfObject(const AccessibilityObject*) const; @@ -725,10 +835,14 @@ public: static AccessibilityRole ariaRoleToWebCoreRole(const String&); bool hasAttribute(const QualifiedName&) const; const AtomicString& getAttribute(const QualifiedName&) const; + bool hasTagName(const QualifiedName&) const; virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); } virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); } + RefPtr<Range> elementRange() const; + static bool replacedNodeNeedsCharacter(Node* replacedNode); + VisiblePositionRange visiblePositionRangeForUnorderedPositions(const VisiblePosition&, const VisiblePosition&) const; VisiblePositionRange positionOfLeftWord(const VisiblePosition&) const; VisiblePositionRange positionOfRightWord(const VisiblePosition&) const; @@ -738,9 +852,14 @@ public: VisiblePositionRange paragraphForPosition(const VisiblePosition&) const; VisiblePositionRange styleRangeForPosition(const VisiblePosition&) const; VisiblePositionRange visiblePositionRangeForRange(const PlainTextRange&) const; + VisiblePositionRange lineRangeForPosition(const VisiblePosition&) const; + + RefPtr<Range> rangeForPlainTextRange(const PlainTextRange&) const; - String stringForVisiblePositionRange(const VisiblePositionRange&) const; + static String stringForVisiblePositionRange(const VisiblePositionRange&); + String stringForRange(RefPtr<Range>) const; virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const { return IntRect(); } + virtual IntRect boundsForRange(const RefPtr<Range>) const { return IntRect(); } int lengthForVisiblePositionRange(const VisiblePositionRange&) const; virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const { } @@ -774,10 +893,13 @@ public: virtual String doAXStringForRange(const PlainTextRange&) const { return String(); } virtual IntRect doAXBoundsForRange(const PlainTextRange&) const { return IntRect(); } - String listMarkerTextForNodeAndPosition(Node*, const VisiblePosition&) const; + virtual IntRect doAXBoundsForRangeUsingCharacterOffset(const PlainTextRange&) const { return IntRect(); } + static String listMarkerTextForNodeAndPosition(Node*, const VisiblePosition&); unsigned doAXLineForIndex(unsigned); + String computedRoleString() const; + virtual String stringValueForMSAA() const { return String(); } virtual String stringRoleForMSAA() const { return String(); } virtual String nameForMSAA() const { return String(); } @@ -785,7 +907,10 @@ public: virtual AccessibilityRole roleValueForMSAA() const { return roleValue(); } virtual String passwordFieldValue() const { return String(); } - + bool isValueAutofilled() const; + bool isValueAutofillAvailable() const; + AutoFillButtonType valueAutofillButtonType() const; + // Used by an ARIA tree to get all its rows. void ariaTreeRows(AccessibilityChildrenVector&); // Used by an ARIA tree item to get all of its direct rows that it can disclose. @@ -796,10 +921,17 @@ public: // ARIA live-region features. bool supportsARIALiveRegion() const; bool isInsideARIALiveRegion() const; - virtual const AtomicString& ariaLiveRegionStatus() const { return nullAtom; } + virtual const String ariaLiveRegionStatus() const { return String(); } virtual const AtomicString& ariaLiveRegionRelevant() const { return nullAtom; } virtual bool ariaLiveRegionAtomic() const { return false; } - virtual bool ariaLiveRegionBusy() const { return false; } + virtual bool isBusy() const { return false; } + static const String defaultLiveRegionStatusForRole(AccessibilityRole); + static bool liveRegionStatusIsEnabled(const AtomicString&); + static bool contentEditableAttributeIsEnabled(Element*); + bool hasContentEditableAttributeSet() const; + + bool supportsARIAReadOnly() const; + String ariaReadOnlyValue() const; bool supportsARIAAttributes() const; @@ -812,7 +944,13 @@ public: virtual void scrollToMakeVisibleWithSubFocus(const IntRect&) const; // Scroll this object to a given point in global coordinates of the top-level window. virtual void scrollToGlobalPoint(const IntPoint&) const; - + + enum ScrollByPageDirection { Up, Down, Left, Right }; + bool scrollByPage(ScrollByPageDirection) const; + IntPoint scrollPosition() const; + IntSize scrollContentsSize() const; + IntRect scrollVisibleContentRect() const; + bool lastKnownIsIgnoredValue(); void setLastKnownIsIgnoredValue(bool); @@ -838,28 +976,32 @@ public: virtual bool isMathTableRow() const { return false; } virtual bool isMathTableCell() const { return false; } virtual bool isMathMultiscript() const { return false; } + virtual bool isMathToken() const { return false; } + virtual bool isMathScriptObject(AccessibilityMathScriptObjectType) const { return false; } + virtual bool isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType) const { return false; } // Root components. - virtual AccessibilityObject* mathRadicandObject() { return 0; } - virtual AccessibilityObject* mathRootIndexObject() { return 0; } + virtual AccessibilityObject* mathRadicandObject() { return nullptr; } + virtual AccessibilityObject* mathRootIndexObject() { return nullptr; } // Under over components. - virtual AccessibilityObject* mathUnderObject() { return 0; } - virtual AccessibilityObject* mathOverObject() { return 0; } + virtual AccessibilityObject* mathUnderObject() { return nullptr; } + virtual AccessibilityObject* mathOverObject() { return nullptr; } // Fraction components. - virtual AccessibilityObject* mathNumeratorObject() { return 0; } - virtual AccessibilityObject* mathDenominatorObject() { return 0; } + virtual AccessibilityObject* mathNumeratorObject() { return nullptr; } + virtual AccessibilityObject* mathDenominatorObject() { return nullptr; } // Subscript/superscript components. - virtual AccessibilityObject* mathBaseObject() { return 0; } - virtual AccessibilityObject* mathSubscriptObject() { return 0; } - virtual AccessibilityObject* mathSuperscriptObject() { return 0; } + virtual AccessibilityObject* mathBaseObject() { return nullptr; } + virtual AccessibilityObject* mathSubscriptObject() { return nullptr; } + virtual AccessibilityObject* mathSuperscriptObject() { return nullptr; } // Fenced components. virtual String mathFencedOpenString() const { return String(); } virtual String mathFencedCloseString() const { return String(); } virtual int mathLineThickness() const { return 0; } + virtual bool isAnonymousMathOperator() const { return false; } // Multiscripts components. typedef Vector<std::pair<AccessibilityObject*, AccessibilityObject*>> AccessibilityMathMultiscriptPairs; @@ -872,7 +1014,7 @@ public: bool isHidden() const { return isARIAHidden() || isDOMHidden(); } #if HAVE(ACCESSIBILITY) -#if PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(GTK) AccessibilityObjectWrapper* wrapper() const; void setWrapper(AccessibilityObjectWrapper*); #else @@ -884,7 +1026,7 @@ public: #endif #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void overrideAttachmentParent(AccessibilityObject* parent); #else void overrideAttachmentParent(AccessibilityObject*) { } @@ -902,12 +1044,30 @@ public: #if PLATFORM(IOS) int accessibilityPasswordFieldLength(); + bool hasTouchEventListener() const; + bool isInputTypePopupButton() const; #endif // allows for an AccessibilityObject to update its render tree or perform // other operations update type operations void updateBackingStore(); +#if PLATFORM(COCOA) + bool preventKeyboardDOMEventDispatch() const; + void setPreventKeyboardDOMEventDispatch(bool); +#endif + +#if PLATFORM(COCOA) && !PLATFORM(IOS) + bool caretBrowsingEnabled() const; + void setCaretBrowsingEnabled(bool); +#endif + + AccessibilityObject* focusableAncestor(); + AccessibilityObject* editableAncestor(); + AccessibilityObject* highestEditableAncestor(); + + static const AccessibilityObject* matchedParent(const AccessibilityObject&, bool includeSelf, const std::function<bool(const AccessibilityObject&)>&); + protected: AXID m_id; AccessibilityChildrenVector m_children; @@ -918,17 +1078,22 @@ protected: virtual bool computeAccessibilityIsIgnored() const { return true; } // If this object itself scrolls, return its ScrollableArea. - virtual ScrollableArea* getScrollableAreaIfScrollable() const { return 0; } + virtual ScrollableArea* getScrollableAreaIfScrollable() const { return nullptr; } virtual void scrollTo(const IntPoint&) const { } - + ScrollableArea* scrollableAreaAncestor() const; + void scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>&) const; + static bool isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject*, AccessibilitySearchCriteria*, size_t); static bool isAccessibilityObjectSearchMatch(AccessibilityObject*, AccessibilitySearchCriteria*); static bool isAccessibilityTextSearchMatch(AccessibilityObject*, AccessibilitySearchCriteria*); static bool objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject*, AccessibilitySearchCriteria*, AccessibilityChildrenVector&); virtual AccessibilityRole buttonRoleType() const; bool isOnscreen() const; - -#if (PLATFORM(GTK) || PLATFORM(EFL)) && HAVE(ACCESSIBILITY) + bool dispatchTouchEvent(); + + void ariaElementsFromAttribute(AccessibilityChildrenVector&, const QualifiedName&) const; + +#if PLATFORM(GTK) && HAVE(ACCESSIBILITY) bool allowsTextRanges() const; unsigned getLengthForTextRange() const; #else @@ -936,11 +1101,11 @@ protected: unsigned getLengthForTextRange() const { return text().length(); } #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) RetainPtr<WebAccessibilityObjectWrapper> m_wrapper; #elif PLATFORM(WIN) COMPtr<AccessibilityObjectWrapper> m_wrapper; -#elif PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY)) +#elif PLATFORM(GTK) AtkObject* m_wrapper; #endif }; @@ -952,9 +1117,9 @@ inline int AccessibilityObject::lineForPosition(const VisiblePosition&) const { inline void AccessibilityObject::updateBackingStore() { } #endif -#define ACCESSIBILITY_OBJECT_TYPE_CASTS(ToValueTypeName, predicate) \ - TYPE_CASTS_BASE(ToValueTypeName, AccessibilityObject, object, object->predicate, object.predicate) - } // namespace WebCore -#endif // AccessibilityObject_h +#define SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToValueTypeName) \ + static bool isType(const WebCore::AccessibilityObject& object) { return object.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp index 1dac10988..c14177cfc 100644 --- a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp @@ -21,11 +21,12 @@ #include "config.h" #include "AccessibilityProgressIndicator.h" -#if ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) +#include "AXObjectCache.h" #include "FloatConversion.h" #include "HTMLMeterElement.h" #include "HTMLNames.h" #include "HTMLProgressElement.h" +#include "LocalizedStrings.h" #include "RenderMeter.h" #include "RenderObject.h" #include "RenderProgress.h" @@ -34,17 +35,15 @@ namespace WebCore { using namespace HTMLNames; -#if ENABLE(PROGRESS_ELEMENT) AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderProgress* renderer) : AccessibilityRenderObject(renderer) { } -PassRefPtr<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer) +Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer) { - return adoptRef(new AccessibilityProgressIndicator(renderer)); + return adoptRef(*new AccessibilityProgressIndicator(renderer)); } -#endif #if ENABLE(METER_ELEMENT) AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderMeter* renderer) @@ -52,9 +51,9 @@ AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderMeter* rend { } -PassRefPtr<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderMeter* renderer) +Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderMeter* renderer) { - return adoptRef(new AccessibilityProgressIndicator(renderer)); + return adoptRef(*new AccessibilityProgressIndicator(renderer)); } #endif @@ -63,18 +62,46 @@ bool AccessibilityProgressIndicator::computeAccessibilityIsIgnored() const return accessibilityIsIgnoredByDefault(); } +String AccessibilityProgressIndicator::valueDescription() const +{ + // If the author has explicitly provided a value through aria-valuetext, use it. + String description = AccessibilityRenderObject::valueDescription(); + if (!description.isEmpty()) + return description; + +#if ENABLE(METER_ELEMENT) + if (!m_renderer->isMeter()) + return String(); + + HTMLMeterElement* meter = meterElement(); + if (!meter) + return String(); + + // The HTML spec encourages authors to include a textual representation of the meter's state in + // the element's contents. We'll fall back on that if there is not a more accessible alternative. + AccessibilityObject* axMeter = axObjectCache()->getOrCreate(meter); + if (is<AccessibilityNodeObject>(axMeter)) { + description = downcast<AccessibilityNodeObject>(axMeter)->accessibilityDescriptionForChildren(); + if (!description.isEmpty()) + return description; + } + + return meter->textContent(); +#endif + + return String(); +} + float AccessibilityProgressIndicator::valueForRange() const { if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) { HTMLProgressElement* progress = progressElement(); if (progress && progress->position() >= 0) return narrowPrecisionToFloat(progress->value()); } -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -92,12 +119,10 @@ float AccessibilityProgressIndicator::maxValueForRange() const if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) { if (HTMLProgressElement* progress = progressElement()) return narrowPrecisionToFloat(progress->max()); } -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -114,10 +139,8 @@ float AccessibilityProgressIndicator::minValueForRange() const if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) return 0.0; -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -129,27 +152,62 @@ float AccessibilityProgressIndicator::minValueForRange() const return 0.0; } -#if ENABLE(PROGRESS_ELEMENT) HTMLProgressElement* AccessibilityProgressIndicator::progressElement() const { - if (!m_renderer->isProgress()) - return 0; + if (!is<RenderProgress>(*m_renderer)) + return nullptr; - return toRenderProgress(m_renderer)->progressElement(); + return downcast<RenderProgress>(*m_renderer).progressElement(); } -#endif #if ENABLE(METER_ELEMENT) HTMLMeterElement* AccessibilityProgressIndicator::meterElement() const { - if (!m_renderer->isMeter()) - return 0; + if (!is<RenderMeter>(*m_renderer)) + return nullptr; - return toRenderMeter(m_renderer)->meterElement(); + return downcast<RenderMeter>(*m_renderer).meterElement(); +} + +String AccessibilityProgressIndicator::gaugeRegionValueDescription() const +{ +#if PLATFORM(COCOA) + if (!m_renderer || !m_renderer->isMeter()) + return String(); + + // Only expose this when the author has explicitly specified the following attributes. + if (!hasAttribute(lowAttr) && !hasAttribute(highAttr) && !hasAttribute(optimumAttr)) + return String(); + + if (HTMLMeterElement* element = meterElement()) { + switch (element->gaugeRegion()) { + case HTMLMeterElement::GaugeRegionOptimum: + return AXMeterGaugeRegionOptimumText(); + case HTMLMeterElement::GaugeRegionSuboptimal: + return AXMeterGaugeRegionSuboptimalText(); + case HTMLMeterElement::GaugeRegionEvenLessGood: + return AXMeterGaugeRegionLessGoodText(); + default: + break; + } + } +#endif + return String(); } #endif -} // namespace WebCore +Element* AccessibilityProgressIndicator::element() const +{ + if (m_renderer->isProgress()) + return progressElement(); + +#if ENABLE(METER_ELEMENT) + if (m_renderer->isMeter()) + return meterElement(); +#endif -#endif // ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) + return AccessibilityObject::element(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.h b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h index 1edfe8a0b..3f5cfe054 100644 --- a/Source/WebCore/accessibility/AccessibilityProgressIndicator.h +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h @@ -18,10 +18,8 @@ * */ -#ifndef AccessibilityProgressIndicator_h -#define AccessibilityProgressIndicator_h +#pragma once -#if ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) #include "AccessibilityRenderObject.h" namespace WebCore { @@ -31,44 +29,39 @@ class HTMLMeterElement; class RenderMeter; #endif -#if ENABLE(PROGRESS_ELEMENT) class HTMLProgressElement; class RenderProgress; -#endif -class AccessibilityProgressIndicator : public AccessibilityRenderObject { +class AccessibilityProgressIndicator final : public AccessibilityRenderObject { public: -#if ENABLE(PROGRESS_ELEMENT) - static PassRefPtr<AccessibilityProgressIndicator> create(RenderProgress*); -#endif + static Ref<AccessibilityProgressIndicator> create(RenderProgress*); #if ENABLE(METER_ELEMENT) - static PassRefPtr<AccessibilityProgressIndicator> create(RenderMeter*); + static Ref<AccessibilityProgressIndicator> create(RenderMeter*); + String gaugeRegionValueDescription() const; #endif + Element* element() const override; private: - virtual AccessibilityRole roleValue() const override { return ProgressIndicatorRole; } + AccessibilityRole roleValue() const override { return ProgressIndicatorRole; } - virtual bool isProgressIndicator() const override { return true; } + bool isProgressIndicator() const override { return true; } - virtual float valueForRange() const override; - virtual float maxValueForRange() const override; - virtual float minValueForRange() const override; + String valueDescription() const override; + float valueForRange() const override; + float maxValueForRange() const override; + float minValueForRange() const override; -#if ENABLE(PROGRESS_ELEMENT) explicit AccessibilityProgressIndicator(RenderProgress*); HTMLProgressElement* progressElement() const; -#endif + #if ENABLE(METER_ELEMENT) explicit AccessibilityProgressIndicator(RenderMeter*); HTMLMeterElement* meterElement() const; #endif - virtual bool computeAccessibilityIsIgnored() const override; + bool computeAccessibilityIsIgnored() const override; }; - } // namespace WebCore -#endif // ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) - -#endif // AccessibilityProgressIndicator_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityProgressIndicator, isProgressIndicator()) diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp index 80e59420e..3b7edcaa9 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -10,7 +10,7 @@ * 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. -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -36,32 +36,33 @@ #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" #include "CachedImage.h" -#include "Chrome.h" #include "ElementIterator.h" -#include "EventNames.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "HTMLAreaElement.h" +#include "HTMLAudioElement.h" +#include "HTMLDetailsElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLMapElement.h" +#include "HTMLMeterElement.h" #include "HTMLNames.h" -#include "HTMLOptGroupElement.h" #include "HTMLOptionElement.h" #include "HTMLOptionsCollection.h" #include "HTMLSelectElement.h" +#include "HTMLSummaryElement.h" #include "HTMLTableElement.h" #include "HTMLTextAreaElement.h" +#include "HTMLVideoElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "Image.h" #include "LocalizedStrings.h" -#include "MathMLNames.h" #include "NodeList.h" #include "Page.h" #include "ProgressTracker.h" @@ -75,13 +76,13 @@ #include "RenderLayer.h" #include "RenderLineBreak.h" #include "RenderListBox.h" +#include "RenderListItem.h" #include "RenderListMarker.h" #include "RenderMathMLBlock.h" -#include "RenderMathMLFraction.h" -#include "RenderMathMLOperator.h" #include "RenderMenuList.h" #include "RenderSVGRoot.h" #include "RenderSVGShape.h" +#include "RenderTableCell.h" #include "RenderText.h" #include "RenderTextControl.h" #include "RenderTextControlSingleLine.h" @@ -92,15 +93,14 @@ #include "RenderedPosition.h" #include "SVGDocument.h" #include "SVGImage.h" -#include "SVGImageChromeClient.h" -#include "SVGNames.h" #include "SVGSVGElement.h" #include "Text.h" #include "TextControlInnerElements.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> -#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -126,9 +126,9 @@ void AccessibilityRenderObject::init() AccessibilityNodeObject::init(); } -PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) +Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) { - return adoptRef(new AccessibilityRenderObject(renderer)); + return adoptRef(*new AccessibilityRenderObject(renderer)); } void AccessibilityRenderObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) @@ -141,14 +141,14 @@ void AccessibilityRenderObject::detach(AccessibilityDetachmentType detachmentTyp if (m_renderer) m_renderer->setHasAXObject(false); #endif - m_renderer = 0; + m_renderer = nullptr; } RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const { - if (!m_renderer || !m_renderer->isBoxModelObject()) - return 0; - return toRenderBoxModelObject(m_renderer); + if (!is<RenderBoxModelObject>(m_renderer)) + return nullptr; + return downcast<RenderBoxModelObject>(m_renderer); } void AccessibilityRenderObject::setRenderer(RenderObject* renderer) @@ -157,16 +157,9 @@ void AccessibilityRenderObject::setRenderer(RenderObject* renderer) setNode(renderer->node()); } -static inline bool isInlineWithContinuation(RenderObject* object) +static inline bool isInlineWithContinuation(RenderObject& object) { - if (!object->isBoxModelObject()) - return false; - - RenderBoxModelObject* renderer = toRenderBoxModelObject(object); - if (!renderer->isRenderInline()) - return false; - - return toRenderInline(renderer)->continuation(); + return is<RenderInline>(object) && downcast<RenderInline>(object).continuation(); } static inline RenderObject* firstChildInContinuation(RenderInline& renderer) @@ -174,47 +167,51 @@ static inline RenderObject* firstChildInContinuation(RenderInline& renderer) auto continuation = renderer.continuation(); while (continuation) { - if (continuation->isRenderBlock()) + if (is<RenderBlock>(*continuation)) return continuation; if (RenderObject* child = continuation->firstChild()) return child; - continuation = toRenderInline(continuation)->continuation(); + continuation = downcast<RenderInline>(*continuation).continuation(); } return nullptr; } -static inline RenderObject* firstChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* firstChildConsideringContinuation(RenderObject& renderer) { - RenderObject* firstChild = renderer->firstChildSlow(); + RenderObject* firstChild = renderer.firstChildSlow(); + // We don't want to include the end of a continuation as the firstChild of the + // anonymous parent, because everything has already been linked up via continuation. + // CSS first-letter selector is an example of this case. + if (renderer.isAnonymous() && firstChild && firstChild->isInlineElementContinuation()) + firstChild = nullptr; + if (!firstChild && isInlineWithContinuation(renderer)) - firstChild = firstChildInContinuation(toRenderInline(*renderer)); + firstChild = firstChildInContinuation(downcast<RenderInline>(renderer)); return firstChild; } -static inline RenderObject* lastChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* lastChildConsideringContinuation(RenderObject& renderer) { - RenderObject* lastChild = renderer->lastChildSlow(); - RenderObject* prev; - RenderObject* cur = renderer; - - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - while (cur) { - prev = cur; + RenderObject* lastChild = downcast<RenderBoxModelObject>(renderer).lastChild(); + RenderBoxModelObject* previous; + for (auto* current = &downcast<RenderBoxModelObject>(renderer); current; ) { + previous = current; - if (RenderObject* lc = cur->lastChildSlow()) - lastChild = lc; + if (RenderObject* newLastChild = current->lastChild()) + lastChild = newLastChild; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT_UNUSED(prev, cur || !toRenderInline(prev)->continuation()); + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT_UNUSED(previous, current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } return lastChild; @@ -223,9 +220,9 @@ static inline RenderObject* lastChildConsideringContinuation(RenderObject* rende AccessibilityObject* AccessibilityRenderObject::firstChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); + RenderObject* firstChild = firstChildConsideringContinuation(*m_renderer); // If an object can't have children, then it is using this method to help // calculate some internal property (like its description). @@ -240,9 +237,9 @@ AccessibilityObject* AccessibilityRenderObject::firstChild() const AccessibilityObject* AccessibilityRenderObject::lastChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* lastChild = lastChildConsideringContinuation(m_renderer); + RenderObject* lastChild = lastChildConsideringContinuation(*m_renderer); if (!lastChild && !canHaveChildren()) return AccessibilityNodeObject::lastChild(); @@ -250,104 +247,91 @@ AccessibilityObject* AccessibilityRenderObject::lastChild() const return axObjectCache()->getOrCreate(lastChild); } -static inline RenderInline* startOfContinuations(RenderObject* r) +static inline RenderInline* startOfContinuations(RenderObject& renderer) { - if (r->isInlineElementContinuation()) { -#if ENABLE(MATHML) - // MathML elements make anonymous RenderObjects, then set their node to the parent's node. - // This makes it so that the renderer() != renderer()->node()->renderer() - // (which is what isInlineElementContinuation() uses as a determinant). - if (r->node()->isMathMLElement()) - return nullptr; -#endif - - return toRenderInline(r->node()->renderer()); - } + if (renderer.isInlineElementContinuation() && is<RenderInline>(renderer.node()->renderer())) + return downcast<RenderInline>(renderer.node()->renderer()); // Blocks with a previous continuation always have a next continuation - if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) - return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->element()->renderer()); + if (is<RenderBlock>(renderer) && downcast<RenderBlock>(renderer).inlineElementContinuation()) + return downcast<RenderInline>(downcast<RenderBlock>(renderer).inlineElementContinuation()->element()->renderer()); - return 0; + return nullptr; } -static inline RenderObject* endOfContinuations(RenderObject* renderer) +static inline RenderObject* endOfContinuations(RenderObject& renderer) { - RenderObject* prev = renderer; - RenderObject* cur = renderer; - - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - while (cur) { - prev = cur; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT(cur || !toRenderInline(prev)->continuation()); + auto* previous = &downcast<RenderBoxModelObject>(renderer); + for (auto* current = previous; current; ) { + previous = current; + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT(current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } - return prev; + return previous; } -static inline RenderObject* childBeforeConsideringContinuations(RenderInline* r, RenderObject* child) +static inline RenderObject* childBeforeConsideringContinuations(RenderInline* renderer, RenderObject* child) { - RenderBoxModelObject* curContainer = r; - RenderObject* cur = 0; - RenderObject* prev = 0; - - while (curContainer) { - if (curContainer->isRenderInline()) { - cur = curContainer->firstChild(); - while (cur) { - if (cur == child) - return prev; - prev = cur; - cur = cur->nextSibling(); + RenderObject* previous = nullptr; + for (RenderBoxModelObject* currentContainer = renderer; currentContainer; ) { + if (is<RenderInline>(*currentContainer)) { + auto* current = currentContainer->firstChild(); + while (current) { + if (current == child) + return previous; + previous = current; + current = current->nextSibling(); } - curContainer = toRenderInline(curContainer)->continuation(); - } else if (curContainer->isRenderBlock()) { - if (curContainer == child) - return prev; + currentContainer = downcast<RenderInline>(*currentContainer).continuation(); + } else if (is<RenderBlock>(*currentContainer)) { + if (currentContainer == child) + return previous; - prev = curContainer; - curContainer = toRenderBlock(curContainer)->inlineElementContinuation(); + previous = currentContainer; + currentContainer = downcast<RenderBlock>(*currentContainer).inlineElementContinuation(); } } ASSERT_NOT_REACHED(); - - return 0; + return nullptr; } -static inline bool firstChildIsInlineContinuation(RenderObject* renderer) +static inline bool firstChildIsInlineContinuation(RenderElement& renderer) { - RenderObject* child = renderer->firstChildSlow(); + RenderObject* child = renderer.firstChild(); return child && child->isInlineElementContinuation(); } AccessibilityObject* AccessibilityRenderObject::previousSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* previousSibling = 0; + RenderObject* previousSibling = nullptr; // Case 1: The node is a block and is an inline's continuation. In that case, the inline's // last child is our previous sibling (or further back in the continuation chain) RenderInline* startOfConts; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + if (is<RenderBox>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer); // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before // the parent of the start, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(m_renderer)) { - RenderObject* firstParent = startOfContinuations(m_renderer->firstChildSlow())->parent(); - while (firstChildIsInlineContinuation(firstParent)) - firstParent = startOfContinuations(firstParent->firstChildSlow())->parent(); + else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderBlock& renderBlock = downcast<RenderBlock>(*m_renderer); + auto* firstParent = startOfContinuations(*renderBlock.firstChild())->parent(); + ASSERT(firstParent); + while (firstChildIsInlineContinuation(*firstParent)) + firstParent = startOfContinuations(*firstParent->firstChild())->parent(); previousSibling = firstParent->previousSibling(); } @@ -357,40 +341,41 @@ AccessibilityObject* AccessibilityRenderObject::previousSibling() const // Case 4: This node has no previous siblings, but its parent is an inline, // and is another node's inline continutation. Follow the continuation chain. - else if (m_renderer->parent()->isRenderInline() && (startOfConts = startOfContinuations(m_renderer->parent()))) + else if (is<RenderInline>(*m_renderer->parent()) && (startOfConts = startOfContinuations(*m_renderer->parent()))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild()); if (!previousSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(previousSibling); } -static inline bool lastChildHasContinuation(RenderObject* renderer) +static inline bool lastChildHasContinuation(RenderElement& renderer) { - RenderObject* child = renderer->lastChildSlow(); - return child && isInlineWithContinuation(child); + RenderObject* child = renderer.lastChild(); + return child && isInlineWithContinuation(*child); } AccessibilityObject* AccessibilityRenderObject::nextSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* nextSibling = 0; + RenderObject* nextSibling = nullptr; // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's // first child. RenderInline* inlineContinuation; - if (m_renderer->isRenderBlock() && (inlineContinuation = toRenderBlock(m_renderer)->inlineElementContinuation())) - nextSibling = firstChildConsideringContinuation(inlineContinuation); + if (is<RenderBlock>(*m_renderer) && (inlineContinuation = downcast<RenderBlock>(*m_renderer).inlineElementContinuation())) + nextSibling = firstChildConsideringContinuation(*inlineContinuation); // Case 2: Anonymous block parent of the start of a continuation - skip all the way to // after the parent of the end, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_renderer)) { - RenderElement* lastParent = endOfContinuations(toRenderBlock(m_renderer)->lastChild())->parent(); - while (lastChildHasContinuation(lastParent)) - lastParent = endOfContinuations(lastParent->lastChild())->parent(); + else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderElement* lastParent = endOfContinuations(*downcast<RenderBlock>(*m_renderer).lastChild())->parent(); + ASSERT(lastParent); + while (lastChildHasContinuation(*lastParent)) + lastParent = endOfContinuations(*lastParent->lastChild())->parent(); nextSibling = lastParent->nextSibling(); } @@ -400,54 +385,61 @@ AccessibilityObject* AccessibilityRenderObject::nextSibling() const // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end // of the continuation chain. - else if (isInlineWithContinuation(m_renderer)) - nextSibling = endOfContinuations(m_renderer)->nextSibling(); + else if (isInlineWithContinuation(*m_renderer)) + nextSibling = endOfContinuations(*m_renderer)->nextSibling(); // Case 5: node has no next sibling, and its parent is an inline with a continuation. - else if (isInlineWithContinuation(m_renderer->parent())) { - auto continuation = toRenderInline(m_renderer->parent())->continuation(); + // Case 5.1: After case 4, (the element was inline w/ continuation but had no sibling), then check it's parent. + if (!nextSibling && isInlineWithContinuation(*m_renderer->parent())) { + auto& continuation = *downcast<RenderInline>(*m_renderer->parent()).continuation(); // Case 5a: continuation is a block - in this case the block itself is the next sibling. - if (continuation->isRenderBlock()) - nextSibling = continuation; + if (is<RenderBlock>(continuation)) + nextSibling = &continuation; // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling else nextSibling = firstChildConsideringContinuation(continuation); + + // After case 4, there are chances that nextSibling has the same node as the current renderer, + // which might lead to adding the same child repeatedly. + if (nextSibling && nextSibling->node() == m_renderer->node()) { + if (AccessibilityObject* nextObj = axObjectCache()->getOrCreate(nextSibling)) + return nextObj->nextSibling(); + } } if (!nextSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(nextSibling); } -static RenderBoxModelObject* nextContinuation(RenderObject* renderer) +static RenderBoxModelObject* nextContinuation(RenderObject& renderer) { - ASSERT(renderer); - if (renderer->isRenderInline() && !renderer->isReplaced()) - return toRenderInline(renderer)->continuation(); - if (renderer->isRenderBlock()) - return toRenderBlock(renderer)->inlineElementContinuation(); - return 0; + if (is<RenderInline>(renderer) && !renderer.isReplaced()) + return downcast<RenderInline>(renderer).continuation(); + if (is<RenderBlock>(renderer)) + return downcast<RenderBlock>(renderer).inlineElementContinuation(); + return nullptr; } RenderObject* AccessibilityRenderObject::renderParentObject() const { if (!m_renderer) - return 0; + return nullptr; RenderElement* parent = m_renderer->parent(); // Case 1: node is a block and is an inline's continuation. Parent // is the start of the continuation chain. RenderInline* startOfConts = nullptr; - RenderObject* firstChild = 0; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + RenderObject* firstChild = nullptr; + if (is<RenderBlock>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) parent = startOfConts; // Case 2: node's parent is an inline which is some node's continuation; parent is // the earliest node in the continuation chain. - else if (parent && parent->isRenderInline() && (startOfConts = startOfContinuations(parent))) + else if (is<RenderInline>(parent) && (startOfConts = startOfContinuations(*parent))) parent = startOfConts; // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation. @@ -455,7 +447,7 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const // Get the node's renderer and follow that continuation chain until the first child is found RenderObject* nodeRenderFirstChild = firstChild->node()->renderer(); while (nodeRenderFirstChild != firstChild) { - for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(contsTest)) { + for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(*contsTest)) { if (contsTest == firstChild) { parent = nodeRenderFirstChild->parent(); break; @@ -476,17 +468,21 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->get(&m_renderer->view().frameView()); + if (isWebArea()) + return cache->get(&m_renderer->view().frameView()); - return axObjectCache()->get(renderParentObject()); + return cache->get(renderParentObject()); } AccessibilityObject* AccessibilityRenderObject::parentObject() const { if (!m_renderer) - return 0; + return nullptr; if (ariaRoleAttribute() == MenuBarRole) return axObjectCache()->getOrCreate(m_renderer->parent()); @@ -498,15 +494,19 @@ AccessibilityObject* AccessibilityRenderObject::parentObject() const return parent; } + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + RenderObject* parentObj = renderParentObject(); if (parentObj) - return axObjectCache()->getOrCreate(parentObj); + return cache->getOrCreate(parentObj); // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->getOrCreate(&m_renderer->view().frameView()); + if (isWebArea()) + return cache->getOrCreate(&m_renderer->view().frameView()); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAttachment() const @@ -522,34 +522,20 @@ bool AccessibilityRenderObject::isAttachment() const bool AccessibilityRenderObject::isFileUploadButton() const { - if (m_renderer && m_renderer->node() && isHTMLInputElement(m_renderer->node())) { - HTMLInputElement* input = toHTMLInputElement(m_renderer->node()); - return input->isFileUpload(); + if (m_renderer && is<HTMLInputElement>(m_renderer->node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*m_renderer->node()); + return input.isFileUpload(); } return false; } - -bool AccessibilityRenderObject::isReadOnly() const -{ - ASSERT(m_renderer); - - if (isWebArea()) { - if (HTMLElement* body = m_renderer->document().body()) { - if (body->hasEditableStyle()) - return false; - } - - return !m_renderer->document().hasEditableStyle(); - } - - return AccessibilityNodeObject::isReadOnly(); -} bool AccessibilityRenderObject::isOffScreen() const { - ASSERT(m_renderer); - IntRect contentRect = pixelSnappedIntRect(m_renderer->absoluteClippedOverflowRect()); + if (!m_renderer) + return true; + + IntRect contentRect = snappedIntRect(m_renderer->absoluteClippedOverflowRect()); // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. IntRect viewRect = m_renderer->view().frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); viewRect.intersect(contentRect); @@ -559,33 +545,34 @@ bool AccessibilityRenderObject::isOffScreen() const Element* AccessibilityRenderObject::anchorElement() const { if (!m_renderer) - return 0; + return nullptr; AXObjectCache* cache = axObjectCache(); - RenderObject* currRenderer; + if (!cache) + return nullptr; + + RenderObject* currentRenderer; // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. - for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) { - if (currRenderer->isAnonymousBlock()) { - RenderObject* continuation = toRenderBlock(currRenderer)->continuation(); - if (continuation) + for (currentRenderer = m_renderer; currentRenderer && !currentRenderer->node(); currentRenderer = currentRenderer->parent()) { + if (currentRenderer->isAnonymousBlock()) { + if (RenderObject* continuation = downcast<RenderBlock>(*currentRenderer).continuation()) return cache->getOrCreate(continuation)->anchorElement(); } } // bail if none found - if (!currRenderer) - return 0; + if (!currentRenderer) + return nullptr; // search up the DOM tree for an anchor element // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement - Node* node = currRenderer->node(); - for ( ; node; node = node->parentNode()) { - if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) - return toElement(node); + for (Node* node = currentRenderer->node(); node; node = node->parentNode()) { + if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink())) + return downcast<Element>(node); } - return 0; + return nullptr; } String AccessibilityRenderObject::helpText() const @@ -602,21 +589,22 @@ String AccessibilityRenderObject::helpText() const return describedBy; String description = accessibilityDescription(); - for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { - if (curr->node() && curr->node()->isHTMLElement()) { - const AtomicString& summary = toElement(curr->node())->getAttribute(summaryAttr); + for (RenderObject* ancestor = m_renderer; ancestor; ancestor = ancestor->parent()) { + if (is<HTMLElement>(ancestor->node())) { + HTMLElement& element = downcast<HTMLElement>(*ancestor->node()); + const AtomicString& summary = element.getAttribute(summaryAttr); if (!summary.isEmpty()) return summary; // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = toElement(curr->node())->getAttribute(titleAttr); + const AtomicString& title = element.getAttribute(titleAttr); if (!title.isEmpty() && description != title) return title; } // Only take help text from an ancestor element if its a group or an unknown role. If help was // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor); if (axObj) { AccessibilityRole role = axObj->roleValue(); if (role != GroupRole && role != UnknownRole) @@ -632,28 +620,25 @@ String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElement if (!m_renderer) return String(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->buttonValue(); + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).buttonValue(); // Reflect when a content author has explicitly marked a line break. if (m_renderer->isBR()) return ASCIILiteral("\n"); -#if ENABLE(MATHML) - // Math operators create RenderText nodes on the fly that are not tied into the DOM in a reasonable way, - // so rangeOfContents does not work for them (nor does regular text selection). - if (m_renderer->isText() && isMathElement()) { - if (ancestorsOfType<RenderMathMLOperator>(*m_renderer).first()) - return toRenderText(*m_renderer).text(); - } -#endif + bool isRenderText = is<RenderText>(*m_renderer); + + if (shouldGetTextFromNode(mode)) + return AccessibilityNodeObject::textUnderElement(mode); // We use a text iterator for text objects AND for those cases where we are // explicitly asking for the full text under a given element. - if (m_renderer->isText() || mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) { + bool shouldIncludeAllChildren = mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + if (isRenderText || shouldIncludeAllChildren) { // If possible, use a text iterator to get the text, so that whitespace // is handled consistently. - Document* nodeDocument = 0; + Document* nodeDocument = nullptr; RefPtr<Range> textRange; if (Node* node = m_renderer->node()) { nodeDocument = &node->document(); @@ -683,34 +668,65 @@ String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElement // catch stale WebCoreAXObject (see <rdar://problem/3960196>) if (frame->document() != nodeDocument) return String(); + + // The tree should be stable before looking through the children of a non-Render Text object. + // Otherwise, further uses of TextIterator will force a layout update, potentially altering + // the accessibility tree and causing crashes in the loop that computes the result text. + ASSERT((isRenderText || !shouldIncludeAllChildren) || (!nodeDocument->renderView()->layoutState() && !nodeDocument->childNeedsStyleRecalc())); + return plainText(textRange.get(), textIteratorBehaviorForTextRange()); } } // Sometimes text fragments don't have Nodes associated with them (like when // CSS content is used to insert text or when a RenderCounter is used.) - if (m_renderer->isText()) { - RenderText* renderTextObject = toRenderText(m_renderer); - if (renderTextObject->isTextFragment()) { - + if (is<RenderText>(*m_renderer)) { + RenderText& renderTextObject = downcast<RenderText>(*m_renderer); + if (is<RenderTextFragment>(renderTextObject)) { + RenderTextFragment& renderTextFragment = downcast<RenderTextFragment>(renderTextObject); // The alt attribute may be set on a text fragment through CSS, which should be honored. - const String& altText = toRenderTextFragment(renderTextObject)->altText(); + const String& altText = renderTextFragment.altText(); if (!altText.isEmpty()) return altText; - return String(static_cast<RenderTextFragment*>(m_renderer)->contentString()); + return renderTextFragment.contentString(); } - return String(renderTextObject->text()); + return renderTextObject.text(); } } return AccessibilityNodeObject::textUnderElement(mode); } +bool AccessibilityRenderObject::shouldGetTextFromNode(AccessibilityTextUnderElementMode mode) const +{ + if (!m_renderer) + return false; + + // AccessibilityRenderObject::textUnderElement() gets the text of anonymous blocks by using + // the child nodes to define positions. CSS tables and their anonymous descendants lack + // children with nodes. + if (m_renderer->isAnonymous() && m_renderer->isTablePart()) + return mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + + // AccessibilityRenderObject::textUnderElement() calls rangeOfContents() to create the text + // range. rangeOfContents() does not include CSS-generated content. + if (m_renderer->isBeforeOrAfterContent()) + return true; + if (Node* node = m_renderer->node()) { + Node* firstChild = node->pseudoAwareFirstChild(); + Node* lastChild = node->pseudoAwareLastChild(); + if ((firstChild && firstChild->isPseudoElement()) || (lastChild && lastChild->isPseudoElement())) + return true; + } + + return false; +} + Node* AccessibilityRenderObject::node() const { if (!m_renderer) - return 0; + return nullptr; if (m_renderer->isRenderView()) return &m_renderer->document(); return m_renderer->node(); @@ -733,25 +749,25 @@ String AccessibilityRenderObject::stringValue() const return staticText; } - if (m_renderer->isText()) + if (is<RenderText>(*m_renderer)) return textUnderElement(); - - if (cssBox && cssBox->isMenuList()) { + + if (is<RenderMenuList>(cssBox)) { // RenderMenuList will go straight to the text() of its selected item. // This has to be overridden in the case where the selected item has an ARIA label. - HTMLSelectElement* selectElement = toHTMLSelectElement(m_renderer->node()); - int selectedIndex = selectElement->selectedIndex(); - const Vector<HTMLElement*> listItems = selectElement->listItems(); + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*m_renderer->node()); + int selectedIndex = selectElement.selectedIndex(); + const Vector<HTMLElement*>& listItems = selectElement.listItems(); if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { - const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); + const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr); if (!overriddenDescription.isNull()) return overriddenDescription; } - return toRenderMenuList(m_renderer)->text(); + return downcast<RenderMenuList>(*m_renderer).text(); } - if (m_renderer->isListMarker()) - return toRenderListMarker(*m_renderer).text(); + if (is<RenderListMarker>(*m_renderer)) + return downcast<RenderListMarker>(*m_renderer).text(); if (isWebArea()) return String(); @@ -759,8 +775,13 @@ String AccessibilityRenderObject::stringValue() const if (isTextControl()) return text(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->fileTextValue(); +#if PLATFORM(IOS) + if (isInputTypePopupButton()) + return textUnderElement(); +#endif + + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).fileTextValue(); // FIXME: We might need to implement a value here for more types // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; @@ -772,19 +793,19 @@ String AccessibilityRenderObject::stringValue() const HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const { if (!m_renderer) - return 0; + return nullptr; // the control element should not be considered part of the label if (isControl()) - return 0; + return nullptr; // find if this has a parent that is a label for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) { - if (isHTMLLabelElement(parentNode)) - return toHTMLLabelElement(parentNode); + if (is<HTMLLabelElement>(*parentNode)) + return downcast<HTMLLabelElement>(parentNode); } - return 0; + return nullptr; } // The boundingBox for elements within the remote SVG element needs to be offset by its position @@ -814,27 +835,25 @@ LayoutRect AccessibilityRenderObject::boundingBoxRect() const // We should also use absoluteQuads for SVG elements, otherwise transforms won't be applied. Vector<FloatQuad> quads; bool isSVGRoot = false; -#if ENABLE(SVG) + if (obj->isSVGRoot()) isSVGRoot = true; -#endif - if (obj->isText()) - quads = toRenderText(obj)->absoluteQuadsClippedToEllipsis(); - else if (isWebArea() || isSeamlessWebArea() || isSVGRoot) + + if (is<RenderText>(*obj)) + quads = downcast<RenderText>(*obj).absoluteQuadsClippedToEllipsis(); + else if (isWebArea() || isSVGRoot) obj->absoluteQuads(quads); else obj->absoluteFocusRingQuads(quads); LayoutRect result = boundingBoxForQuads(obj, quads); -#if ENABLE(SVG) Document* document = this->document(); if (document && document->isSVGDocument()) offsetBoundingBoxForRemoteSVGElement(result); -#endif // The size of the web area should be the content size, not the clipped size. - if (isWebArea() || isSeamlessWebArea()) + if (isWebArea()) result.setSize(obj->view().frameView().contentsSize()); return result; @@ -845,7 +864,7 @@ LayoutRect AccessibilityRenderObject::checkboxOrRadioRect() const if (!m_renderer) return LayoutRect(); - HTMLLabelElement* label = labelForElement(toElement(m_renderer->node())); + HTMLLabelElement* label = labelForElement(downcast<Element>(m_renderer->node())); if (!label || !label->renderer()) return boundingBoxRect(); @@ -865,19 +884,13 @@ LayoutRect AccessibilityRenderObject::elementRect() const bool AccessibilityRenderObject::supportsPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape()) - return true; -#endif - - return false; + return is<RenderSVGShape>(m_renderer); } Path AccessibilityRenderObject::elementPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape() && toRenderSVGShape(m_renderer)->hasPath()) { - Path path = toRenderSVGShape(m_renderer)->path(); + if (is<RenderSVGShape>(m_renderer) && downcast<RenderSVGShape>(*m_renderer).hasPath()) { + Path path = downcast<RenderSVGShape>(*m_renderer).path(); // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates. if (auto svgRoot = ancestorsOfType<RenderSVGRoot>(*m_renderer).first()) { @@ -887,7 +900,6 @@ Path AccessibilityRenderObject::elementPath() const return path; } -#endif return Path(); } @@ -899,13 +911,13 @@ IntPoint AccessibilityRenderObject::clickPoint() return children()[0]->clickPoint(); // use the default position unless this is an editable web area, in which case we use the selection bounds. - if (!isWebArea() || isReadOnly()) + if (!isWebArea() || !canSetValueAttribute()) return AccessibilityObject::clickPoint(); VisibleSelection visSelection = selection(); VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd()); IntRect bounds = boundsForVisiblePositionRange(range); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) bounds.setLocation(m_renderer->view().frameView().screenToContents(bounds.location())); #endif return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2)); @@ -914,27 +926,24 @@ IntPoint AccessibilityRenderObject::clickPoint() AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const { Element* element = anchorElement(); - if (!element) - return 0; - // Right now, we do not support ARIA links as internal link elements - if (!isHTMLAnchorElement(element)) - return 0; - HTMLAnchorElement* anchor = toHTMLAnchorElement(element); + if (!is<HTMLAnchorElement>(element)) + return nullptr; + HTMLAnchorElement& anchor = downcast<HTMLAnchorElement>(*element); - URL linkURL = anchor->href(); + URL linkURL = anchor.href(); String fragmentIdentifier = linkURL.fragmentIdentifier(); if (fragmentIdentifier.isEmpty()) - return 0; + return nullptr; // check if URL is the same as current URL URL documentURL = m_renderer->document().url(); if (!equalIgnoringFragmentIdentifier(documentURL, linkURL)) - return 0; + return nullptr; Node* linkedNode = m_renderer->document().findAnchor(fragmentIdentifier); if (!linkedNode) - return 0; + return nullptr; // The element we find may not be accessible, so find the first accessible object. return firstAccessibleObjectFromNode(linkedNode); @@ -948,36 +957,33 @@ ESpeak AccessibilityRenderObject::speakProperty() const return m_renderer->style().speak(); } -void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const +void AccessibilityRenderObject::addRadioButtonGroupChildren(AccessibilityObject* parent, AccessibilityChildrenVector& linkedUIElements) const { - if (!m_renderer || roleValue() != RadioButtonRole) - return; + for (const auto& child : parent->children()) { + if (child->roleValue() == RadioButtonRole) + linkedUIElements.append(child); + else + addRadioButtonGroupChildren(child.get(), linkedUIElements); + } +} - Node* node = m_renderer->node(); - if (!node || !isHTMLInputElement(node)) +void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const +{ + if (roleValue() != RadioButtonRole) return; - HTMLInputElement* input = toHTMLInputElement(node); - // if there's a form, then this is easy - if (input->form()) { - Vector<Ref<Element>> formElements; - input->form()->getNamedElements(input->name(), formElements); - - for (auto& associateElement : formElements) { - if (AccessibilityObject* object = axObjectCache()->getOrCreate(&associateElement.get())) - linkedUIElements.append(object); - } + Node* node = this->node(); + if (is<HTMLInputElement>(node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + for (auto& radioSibling : input.radioButtonGroup()) { + if (AccessibilityObject* object = axObjectCache()->getOrCreate(radioSibling)) + linkedUIElements.append(object); + } } else { - RefPtr<NodeList> list = node->document().getElementsByTagName("input"); - unsigned len = list->length(); - for (unsigned i = 0; i < len; ++i) { - if (isHTMLInputElement(list->item(i))) { - HTMLInputElement* associateElement = toHTMLInputElement(list->item(i)); - if (associateElement->isRadioButton() && associateElement->name() == input->name()) { - if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement)) - linkedUIElements.append(object); - } - } + // If we didn't find any radio button siblings with the traditional naming, lets search for a radio group role and find its children. + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (parent->roleValue() == RadioGroupRole) + addRadioButtonGroupChildren(parent, linkedUIElements); } } } @@ -988,7 +994,7 @@ void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& li { ariaFlowToElements(linkedUIElements); - if (isAnchor()) { + if (isLink()) { AccessibilityObject* linkedAXElement = internalLinkElement(); if (linkedAXElement) linkedUIElements.append(linkedAXElement); @@ -1010,42 +1016,6 @@ bool AccessibilityRenderObject::ariaHasPopup() const return elementAttributeValue(aria_haspopupAttr); } -bool AccessibilityRenderObject::supportsARIAFlowTo() const -{ - return !getAttribute(aria_flowtoAttr).isEmpty(); -} - -void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_flowtoAttr); - - AXObjectCache* cache = axObjectCache(); - for (const auto& element : elements) { - AccessibilityObject* flowToElement = cache->getOrCreate(element); - if (flowToElement) - flowTo.append(flowToElement); - } - -} - -bool AccessibilityRenderObject::supportsARIADescribedBy() const -{ - return !getAttribute(aria_describedbyAttr).isEmpty(); -} - -void AccessibilityRenderObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_describedbyAttr); - - AXObjectCache* cache = axObjectCache(); - for (const auto& element : elements) { - if (AccessibilityObject* describedByElement = cache->getOrCreate(element)) - ariaDescribedBy.append(describedByElement); - } -} - bool AccessibilityRenderObject::supportsARIADropping() const { const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); @@ -1055,7 +1025,7 @@ bool AccessibilityRenderObject::supportsARIADropping() const bool AccessibilityRenderObject::supportsARIADragging() const { const AtomicString& grabbed = getAttribute(aria_grabbedAttr); - return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false"); + return equalLettersIgnoringASCIICase(grabbed, "true") || equalLettersIgnoringASCIICase(grabbed, "false"); } bool AccessibilityRenderObject::isARIAGrabbed() @@ -1078,7 +1048,7 @@ void AccessibilityRenderObject::determineARIADropEffects(Vector<String>& effects bool AccessibilityRenderObject::exposesTitleUIElement() const { - if (!isControl()) + if (!isControl() && !isFigure()) return false; // If this control is ignored (because it's invisible), @@ -1090,26 +1060,42 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const if (hasTextAlternative()) return false; + // When <label> element has aria-label or aria-labelledby on it, we shouldn't expose it as the + // titleUIElement, otherwise its inner text will be announced by a screenreader. + if (isLabelable()) { + if (HTMLLabelElement* label = labelForElement(downcast<Element>(node()))) { + if (!label->attributeWithoutSynchronization(aria_labelAttr).isEmpty()) + return false; + if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label)) { + if (!labelObject->ariaLabeledByAttribute().isEmpty()) + return false; + } + } + } + return true; } AccessibilityObject* AccessibilityRenderObject::titleUIElement() const { if (!m_renderer) - return 0; + return nullptr; // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset if (isFieldset()) - return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); + return axObjectCache()->getOrCreate(downcast<RenderFieldset>(*m_renderer).findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); + + if (isFigure()) + return captionForFigure(); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) - return 0; - HTMLLabelElement* label = labelForElement(toElement(node)); + if (!is<Element>(node)) + return nullptr; + HTMLLabelElement* label = labelForElement(downcast<Element>(node)); if (label && label->renderer()) return axObjectCache()->getOrCreate(label); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAllowedChildOfTree() const @@ -1117,7 +1103,10 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. AccessibilityObject* axObj = parentObject(); bool isInTree = false; + bool isTreeItemDescendant = false; while (axObj) { + if (axObj->roleValue() == TreeItemRole) + isTreeItemDescendant = true; if (axObj->isTree()) { isInTree = true; break; @@ -1128,7 +1117,7 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // If the object is in a tree, only tree items should be exposed (and the children of tree items). if (isInTree) { AccessibilityRole role = roleValue(); - if (role != TreeItemRole && role != StaticTextRole) + if (role != TreeItemRole && role != StaticTextRole && !isTreeItemDescendant) return false; } return true; @@ -1156,7 +1145,7 @@ AccessibilityObjectInclusion AccessibilityRenderObject::defaultObjectInclusion() if (m_renderer->style().visibility() != VISIBLE) { // aria-hidden is meant to override visibility as the determinant in AX hierarchy inclusion. - if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_hiddenAttr), "false")) return DefaultBehavior; return IgnoreObject; @@ -1171,6 +1160,9 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const ASSERT(m_initialized); #endif + if (!m_renderer) + return true; + // Check first if any of the common reasons cause this element to be ignored. // Then process other use cases that need to be applied to all the various roles // that AccessibilityRenderObjects take on. @@ -1199,7 +1191,7 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return accessibilityIgnoreAttachment(); // ignore popup menu items because AppKit does - if (ancestorsOfType<RenderMenuList>(*m_renderer).first()) + if (m_renderer && ancestorsOfType<RenderMenuList>(*m_renderer).first()) return true; // find out if this element is inside of a label element. @@ -1207,16 +1199,20 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const AccessibilityObject* controlObject = correspondingControlForLabelElement(); if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio()) return true; + + // https://webkit.org/b/161276 Getting the controlObject might cause the m_renderer to be nullptr. + if (!m_renderer) + return true; if (m_renderer->isBR()) return true; - if (m_renderer->isText()) { + if (is<RenderText>(*m_renderer)) { // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level AccessibilityObject* parent = parentObjectUnignored(); if (parent && (parent->isMenuItem() || parent->ariaRoleAttribute() == MenuButtonRole)) return true; - auto& renderText = toRenderText(*m_renderer); + auto& renderText = downcast<RenderText>(*m_renderer); if (!renderText.hasRenderedText()) return true; @@ -1226,9 +1222,13 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return true; } + // Walking up the parent chain might reset the m_renderer. + if (!m_renderer) + return true; + // The alt attribute may be set on a text fragment through CSS, which should be honored. - if (renderText.isTextFragment()) { - AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(toRenderTextFragment(&renderText)->altText()); + if (is<RenderTextFragment>(renderText)) { + AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(downcast<RenderTextFragment>(renderText).altText()); if (altTextInclusion == IgnoreObject) return true; if (altTextInclusion == IncludeObject) @@ -1245,16 +1245,39 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isLink()) return false; + if (isLandmark()) + return false; + // all controls are accessible if (isControl()) return false; - if (ariaRoleAttribute() != UnknownRole) + if (isFigure()) return false; + switch (roleValue()) { + case AudioRole: + case DescriptionListTermRole: + case DescriptionListDetailRole: + case DetailsRole: + case DocumentArticleRole: + case LandmarkRegionRole: + case ListItemRole: + case VideoRole: + return false; + default: + break; + } + + if (ariaRoleAttribute() != UnknownRole) + return false; + + if (roleValue() == HorizontalRuleRole) + return false; + // don't ignore labels, because they serve as TitleUIElements Node* node = m_renderer->node(); - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return false; // Anything that is content editable should not be ignored. @@ -1263,14 +1286,6 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (hasContentEditableAttributeSet()) return false; - switch (roleValue()) { - case AudioRole: - case ListItemRole: - case VideoRole: - return false; - default: - break; - } // if this element has aria attributes on it, it should not be ignored. if (supportsARIAAttributes()) @@ -1285,8 +1300,8 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return false; #endif - if (m_renderer->isRenderBlockFlow() && m_renderer->childrenInline() && !canSetFocusAttribute()) - return !toRenderBlockFlow(m_renderer)->hasLines() && !mouseButtonListener(); + if (is<RenderBlockFlow>(*m_renderer) && m_renderer->childrenInline() && !canSetFocusAttribute()) + return !downcast<RenderBlockFlow>(*m_renderer).hasLines() && !mouseButtonListener(); // ignore images seemingly used as spacers if (isImage()) { @@ -1298,9 +1313,9 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const // First check the RenderImage's altText (which can be set through a style sheet, or come from the Element). // However, if this is not a native image, fallback to the attribute on the Element. AccessibilityObjectInclusion altTextInclusion = DefaultBehavior; - bool isRenderImage = m_renderer && m_renderer->isRenderImage(); + bool isRenderImage = is<RenderImage>(m_renderer); if (isRenderImage) - altTextInclusion = objectInclusionFromAltText(toRenderImage(m_renderer)->altText()); + altTextInclusion = objectInclusionFromAltText(downcast<RenderImage>(*m_renderer).altText()); else altTextInclusion = objectInclusionFromAltText(getAttribute(altAttr).string()); @@ -1315,13 +1330,13 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isRenderImage) { // check for one-dimensional image - RenderImage* image = toRenderImage(m_renderer); - if (image->height() <= 1 || image->width() <= 1) + RenderImage& image = downcast<RenderImage>(*m_renderer); + if (image.height() <= 1 || image.width() <= 1) return true; // check whether rendered image was stretched from one-dimensional file image - if (image->cachedImage()) { - LayoutSize imageSize = image->cachedImage()->imageSizeForRenderer(toRenderElement(m_renderer), image->view().zoomFactor()); + if (image.cachedImage()) { + LayoutSize imageSize = image.cachedImage()->imageSizeForRenderer(&image, image.view().zoomFactor()); return imageSize.height() <= 1 || imageSize.width() <= 1; } } @@ -1332,17 +1347,31 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (canvasHasFallbackContent()) return false; - if (m_renderer->isBox()) { - RenderBox* canvasBox = toRenderBox(m_renderer); - if (canvasBox->height() <= 1 || canvasBox->width() <= 1) + if (is<RenderBox>(*m_renderer)) { + auto& canvasBox = downcast<RenderBox>(*m_renderer); + if (canvasBox.height() <= 1 || canvasBox.width() <= 1) return true; } // Otherwise fall through; use presence of help text, title, or description to decide. } - if (isWebArea() || isSeamlessWebArea() || m_renderer->isListMarker()) + if (m_renderer->isListMarker()) { + AccessibilityObject* parent = parentObjectUnignored(); + return parent && !parent->isListItem(); + } + + if (isWebArea()) return false; +#if ENABLE(METER_ELEMENT) + // The render tree of meter includes a RenderBlock (meter) and a RenderMeter (div). + // We expose the latter and thus should ignore the former. However, if the author + // includes a title attribute on the element, hasAttributesRequiredForInclusion() + // will return true, potentially resulting in a redundant accessible object. + if (is<HTMLMeterElement>(node)) + return true; +#endif + // Using the presence of an accessible name to decide an element's visibility is not // as definitive as previous checks, so this should remain as one of the last. if (hasAttributesRequiredForInclusion()) @@ -1366,7 +1395,14 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (node && node->hasTagName(dfnTag)) return false; - // By default, objects should be ignored so that the AX hierarchy is not + if (isStyleFormatGroup()) + return false; + + // Make sure that ruby containers are not ignored. + if (m_renderer->isRubyRun() || m_renderer->isRubyBlock() || m_renderer->isRubyInline()) + return false; + + // By default, objects should be ignored so that the AX hierarchy is not // filled with unnecessary items. return true; } @@ -1384,18 +1420,14 @@ double AccessibilityRenderObject::estimatedLoadingProgress() const if (isLoaded()) return 1.0; - Page* page = m_renderer->document().page(); - if (!page) - return 0; - - return page->progress().estimatedProgress(); + return m_renderer->page().progress().estimatedProgress(); } int AccessibilityRenderObject::layoutCount() const { - if (!m_renderer->isRenderView()) + if (!is<RenderView>(*m_renderer)) return 0; - return toRenderView(*m_renderer).frameView().layoutCount(); + return downcast<RenderView>(*m_renderer).frameView().layoutCount(); } String AccessibilityRenderObject::text() const @@ -1416,20 +1448,27 @@ int AccessibilityRenderObject::textLength() const return text().length(); } -PlainTextRange AccessibilityRenderObject::ariaSelectedTextRange() const +PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const { Node* node = m_renderer->node(); if (!node) return PlainTextRange(); - + VisibleSelection visibleSelection = selection(); RefPtr<Range> currentSelectionRange = visibleSelection.toNormalizedRange(); - if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, IGNORE_EXCEPTION)) + if (!currentSelectionRange) return PlainTextRange(); - + // FIXME: The reason this does the correct thing when the selection is in the + // shadow tree of an input element is that we get an exception below, and we + // choose to interpret all exceptions as "does not intersect". Seems likely + // that does not handle all cases correctly. + auto intersectsResult = currentSelectionRange->intersectsNode(*node); + if (!intersectsResult.hasException() && !intersectsResult.releaseReturnValue()) + return PlainTextRange(); + int start = indexForVisiblePosition(visibleSelection.start()); int end = indexForVisiblePosition(visibleSelection.end()); - + return PlainTextRange(start, end - start); } @@ -1441,24 +1480,19 @@ String AccessibilityRenderObject::selectedText() const return String(); // need to return something distinct from empty string if (isNativeTextControl()) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); return textControl.selectedText(); } - if (ariaRoleAttribute() == UnknownRole) - return String(); - - return doAXStringForRange(ariaSelectedTextRange()); + return doAXStringForRange(documentBasedSelectedTextRange()); } const AtomicString& AccessibilityRenderObject::accessKey() const { Node* node = m_renderer->node(); - if (!node) - return nullAtom; - if (!node->isElementNode()) + if (!is<Element>(node)) return nullAtom; - return toElement(node)->getAttribute(accesskeyAttr); + return downcast<Element>(*node).getAttribute(accesskeyAttr); } VisibleSelection AccessibilityRenderObject::selection() const @@ -1474,57 +1508,82 @@ PlainTextRange AccessibilityRenderObject::selectedTextRange() const return PlainTextRange(); AccessibilityRole ariaRole = ariaRoleAttribute(); - if (isNativeTextControl() && ariaRole == UnknownRole) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + // Use the text control native range if it's a native object and it has no ARIA role (or has a text based ARIA role). + if (isNativeTextControl() && (ariaRole == UnknownRole || isARIATextControl())) { + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); return PlainTextRange(textControl.selectionStart(), textControl.selectionEnd() - textControl.selectionStart()); } - if (ariaRole == UnknownRole) - return PlainTextRange(); - - return ariaSelectedTextRange(); + return documentBasedSelectedTextRange(); +} + +static void setTextSelectionIntent(AXObjectCache* cache, AXTextStateChangeType type) +{ + if (!cache) + return; + AXTextStateChangeIntent intent(type, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }); + cache->setTextSelectionIntent(intent); + cache->setIsSynchronizingSelection(true); +} + +static void clearTextSelectionIntent(AXObjectCache* cache) +{ + if (!cache) + return; + cache->setTextSelectionIntent(AXTextStateChangeIntent()); + cache->setIsSynchronizingSelection(false); } void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) { + setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeTypeSelectionExtend : AXTextStateChangeTypeSelectionMove); + if (isNativeTextControl()) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); textControl.setSelectionRange(range.start, range.start + range.length); - return; + } else { + ASSERT(node()); + VisiblePosition start = visiblePositionForIndexUsingCharacterIterator(*node(), range.start); + VisiblePosition end = visiblePositionForIndexUsingCharacterIterator(*node(), range.start + range.length); + m_renderer->frame().selection().setSelection(VisibleSelection(start, end), FrameSelection::defaultSetSelectionOptions(UserTriggered)); } - - Node* node = m_renderer->node(); - m_renderer->frame().selection().setSelection(VisibleSelection(Position(node, range.start, Position::PositionIsOffsetInAnchor), - Position(node, range.start + range.length, Position::PositionIsOffsetInAnchor), DOWNSTREAM)); + + clearTextSelectionIntent(axObjectCache()); } URL AccessibilityRenderObject::url() const { - if (isAnchor() && isHTMLAnchorElement(m_renderer->node())) { - if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement())) + if (isLink() && is<HTMLAnchorElement>(*m_renderer->node())) { + if (HTMLAnchorElement* anchor = downcast<HTMLAnchorElement>(anchorElement())) return anchor->href(); } if (isWebArea()) return m_renderer->document().url(); - if (isImage() && m_renderer->node() && isHTMLImageElement(m_renderer->node())) - return toHTMLImageElement(m_renderer->node())->src(); + if (isImage() && is<HTMLImageElement>(m_renderer->node())) + return downcast<HTMLImageElement>(*m_renderer->node()).src(); if (isInputImage()) - return toHTMLInputElement(m_renderer->node())->src(); + return downcast<HTMLInputElement>(*m_renderer->node()).src(); return URL(); } bool AccessibilityRenderObject::isUnvisited() const { + if (!m_renderer) + return true; + // FIXME: Is it a privacy violation to expose unvisited information to accessibility APIs? return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideUnvisitedLink; } bool AccessibilityRenderObject::isVisited() const { + if (!m_renderer) + return false; + // FIXME: Is it a privacy violation to expose visited information to accessibility APIs? return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideVisitedLink; } @@ -1535,11 +1594,10 @@ void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& at return; Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return; - Element* element = toElement(node); - element->setAttribute(attributeName, (value) ? "true" : "false"); + downcast<Element>(*node).setAttribute(attributeName, (value) ? "true" : "false"); } bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const @@ -1547,7 +1605,7 @@ bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attri if (!m_renderer) return false; - return equalIgnoringCase(getAttribute(attributeName), "true"); + return equalLettersIgnoringASCIICase(getAttribute(attributeName), "true"); } bool AccessibilityRenderObject::isSelected() const @@ -1555,17 +1613,19 @@ bool AccessibilityRenderObject::isSelected() const if (!m_renderer) return false; - Node* node = m_renderer->node(); - if (!node) + if (!m_renderer->node()) return false; - const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); - if (equalIgnoringCase(ariaSelected, "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true")) return true; if (isTabItem() && isTabItemSelected()) return true; + // Menu items are considered selectable by assistive technologies + if (isMenuItem()) + return isFocused() || parentObjectUnignored()->activeDescendant() == this; + return false; } @@ -1588,8 +1648,12 @@ bool AccessibilityRenderObject::isTabItemSelected() const Vector<Element*> elements; elementsFromAttribute(elements, aria_controlsAttr); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return false; + for (const auto& element : elements) { - AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element); + AccessibilityObject* tabPanel = cache->getOrCreate(element); // A tab item should only control tab panels. if (!tabPanel || tabPanel->roleValue() != TabPanelRole) @@ -1635,25 +1699,35 @@ void AccessibilityRenderObject::setFocused(bool on) Document* document = this->document(); Node* node = this->node(); - if (!on || !node || !node->isElementNode()) { - document->setFocusedElement(0); + if (!on || !is<Element>(node)) { + document->setFocusedElement(nullptr); return; } + // When a node is told to set focus, that can cause it to be deallocated, which means that doing + // anything else inside this object will crash. To fix this, we added a RefPtr to protect this object + // long enough for duration. + RefPtr<AccessibilityObject> protectedThis(this); + // If this node is already the currently focused node, then calling focus() won't do anything. // That is a problem when focus is removed from the webpage to chrome, and then returns. // In these cases, we need to do what keyboard and mouse focus do, which is reset focus first. if (document->focusedElement() == node) - document->setFocusedElement(0); + document->setFocusedElement(nullptr); - toElement(node)->focus(); + // If we return from setFocusedElement and our element has been removed from a tree, axObjectCache() may be null. + if (AXObjectCache* cache = axObjectCache()) { + cache->setIsSynchronizingSelection(true); + downcast<Element>(*node).focus(); + cache->setIsSynchronizingSelection(false); + } } void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) { // Setting selected only makes sense in trees and tables (and tree-tables). AccessibilityRole role = roleValue(); - if (role != TreeRole && role != TreeGridRole && role != TableRole) + if (role != TreeRole && role != TreeGridRole && role != TableRole && role != GridRole) return; bool isMulti = isMultiSelectable(); @@ -1667,35 +1741,19 @@ void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& sel void AccessibilityRenderObject::setValue(const String& string) { - if (!m_renderer || !m_renderer->node() || !m_renderer->node()->isElementNode()) + if (!m_renderer || !is<Element>(m_renderer->node())) return; - Element* element = toElement(m_renderer->node()); + Element& element = downcast<Element>(*m_renderer->node()); - if (!m_renderer->isBoxModelObject()) + if (!is<RenderBoxModelObject>(*m_renderer)) return; - RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); + RenderBoxModelObject& renderer = downcast<RenderBoxModelObject>(*m_renderer); // FIXME: Do we want to do anything here for ARIA textboxes? - if (renderer->isTextField()) { - // FIXME: This is not safe! Other elements could have a TextField renderer. - toHTMLInputElement(element)->setValue(string); - } else if (renderer->isTextArea()) { - // FIXME: This is not safe! Other elements could have a TextArea renderer. - toHTMLTextAreaElement(element)->setValue(string); - } -} - -void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_ownsAttr); - - for (const auto& element : elements) { - RenderObject* render = element->renderer(); - AccessibilityObject* obj = axObjectCache()->getOrCreate(render); - if (obj) - axObjects.append(obj); - } + if (renderer.isTextField() && is<HTMLInputElement>(element)) + downcast<HTMLInputElement>(element).setValue(string); + else if (renderer.isTextArea() && is<HTMLTextAreaElement>(element)) + downcast<HTMLTextAreaElement>(element).setValue(string); } bool AccessibilityRenderObject::supportsARIAOwns() const @@ -1711,7 +1769,7 @@ RenderView* AccessibilityRenderObject::topRenderer() const { Document* topDoc = topDocument(); if (!topDoc) - return 0; + return nullptr; return topDoc->renderView(); } @@ -1719,51 +1777,53 @@ RenderView* AccessibilityRenderObject::topRenderer() const Document* AccessibilityRenderObject::document() const { if (!m_renderer) - return 0; + return nullptr; return &m_renderer->document(); } Widget* AccessibilityRenderObject::widget() const { - if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isWidget()) - return 0; - return toRenderWidget(m_renderer)->widget(); + if (!is<RenderWidget>(*m_renderer)) + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const { // find an image that is using this map if (!map) - return 0; + return nullptr; HTMLImageElement* imageElement = map->imageElement(); if (!imageElement) - return 0; + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(imageElement); - return axObjectCache()->getOrCreate(imageElement); + return nullptr; } void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) { Document& document = m_renderer->document(); - RefPtr<HTMLCollection> links = document.links(); - for (unsigned i = 0; Node* curr = links->item(i); i++) { - RenderObject* obj = curr->renderer(); - if (obj) { - RefPtr<AccessibilityObject> axobj = document.axObjectCache()->getOrCreate(obj); - ASSERT(axobj); - if (!axobj->accessibilityIsIgnored() && axobj->isLink()) - result.append(axobj); + Ref<HTMLCollection> links = document.links(); + for (unsigned i = 0; auto* current = links->item(i); ++i) { + if (auto* renderer = current->renderer()) { + RefPtr<AccessibilityObject> axObject = document.axObjectCache()->getOrCreate(renderer); + ASSERT(axObject); + if (!axObject->accessibilityIsIgnored() && axObject->isLink()) + result.append(axObject); } else { - Node* parent = curr->parentNode(); - if (parent && isHTMLAreaElement(curr) && isHTMLMapElement(parent)) { - AccessibilityImageMapLink* areaObject = toAccessibilityImageMapLink(axObjectCache()->getOrCreate(ImageMapLinkRole)); - HTMLMapElement* map = toHTMLMapElement(parent); - areaObject->setHTMLAreaElement(toHTMLAreaElement(curr)); - areaObject->setHTMLMapElement(map); - areaObject->setParent(accessibilityParentForImageMap(map)); - - result.append(areaObject); + auto* parent = current->parentNode(); + if (is<HTMLAreaElement>(*current) && is<HTMLMapElement>(parent)) { + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + HTMLMapElement& map = downcast<HTMLMapElement>(*parent); + areaObject.setHTMLAreaElement(downcast<HTMLAreaElement>(current)); + areaObject.setHTMLMapElement(&map); + areaObject.setParent(accessibilityParentForImageMap(&map)); + + result.append(&areaObject); } } } @@ -1772,7 +1832,7 @@ void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& re FrameView* AccessibilityRenderObject::documentFrameView() const { if (!m_renderer) - return 0; + return nullptr; // this is the RenderObject's Document's Frame's FrameView return &m_renderer->view().frameView(); @@ -1781,8 +1841,8 @@ FrameView* AccessibilityRenderObject::documentFrameView() const Widget* AccessibilityRenderObject::widgetForAttachmentView() const { if (!isAttachment()) - return 0; - return toRenderWidget(m_renderer)->widget(); + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns @@ -1821,7 +1881,7 @@ VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsi // iterate over the lines // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the // last offset of the last line - VisiblePosition visiblePos = m_renderer->view().positionForPoint(IntPoint()); + VisiblePosition visiblePos = m_renderer->view().positionForPoint(IntPoint(), nullptr); VisiblePosition savedVisiblePos; while (--lineCount) { savedVisiblePos = visiblePos; @@ -1847,22 +1907,22 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) co return VisiblePosition(); if (isNativeTextControl()) - return toRenderTextControl(m_renderer)->textFormControlElement().visiblePositionForIndex(index); + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().visiblePositionForIndex(index); - if (!allowsTextRanges() && !m_renderer->isText()) + if (!allowsTextRanges() && !is<RenderText>(*m_renderer)) return VisiblePosition(); Node* node = m_renderer->node(); if (!node) return VisiblePosition(); - return visiblePositionForIndexUsingCharacterIterator(node, index); + return visiblePositionForIndexUsingCharacterIterator(*node, index); } -int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const +int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& position) const { if (isNativeTextControl()) - return toRenderTextControl(m_renderer)->textFormControlElement().indexForVisiblePosition(pos); + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().indexForVisiblePosition(position); if (!isTextControl()) return 0; @@ -1871,7 +1931,7 @@ int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& po if (!node) return 0; - Position indexPosition = pos.deepEquivalent(); + Position indexPosition = position.deepEquivalent(); if (indexPosition.isNull() || highestEditableRoot(indexPosition, HasEditableAXRole) != node) return 0; @@ -1883,13 +1943,13 @@ int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& po bool forSelectionPreservation = false; #endif - return WebCore::indexForVisiblePosition(node, pos, forSelectionPreservation); + return WebCore::indexForVisiblePosition(*node, position, forSelectionPreservation); } Element* AccessibilityRenderObject::rootEditableElementForPosition(const Position& position) const { // Find the root editable or pseudo-editable (i.e. having an editable ARIA role) element. - Element* result = 0; + Element* result = nullptr; Element* rootEditableElement = position.rootEditableElement(); @@ -1911,11 +1971,32 @@ bool AccessibilityRenderObject::nodeIsTextControl(const Node* node) const if (!node) return false; - const AccessibilityObject* axObjectForNode = axObjectCache()->getOrCreate(const_cast<Node*>(node)); - if (!axObjectForNode) - return false; + if (AXObjectCache* cache = axObjectCache()) { + if (AccessibilityObject* axObjectForNode = cache->getOrCreate(const_cast<Node*>(node))) + return axObjectForNode->isTextControl(); + } - return axObjectForNode->isTextControl(); + return false; +} + +IntRect AccessibilityRenderObject::boundsForRects(LayoutRect& rect1, LayoutRect& rect2, RefPtr<Range> dataRange) const +{ + LayoutRect ourRect = rect1; + ourRect.unite(rect2); + + // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead + if (rect1.maxY() != rect2.maxY()) { + LayoutRect boundingBox = dataRange->absoluteBoundingBox(); + String rangeString = plainText(dataRange.get()); + if (rangeString.length() > 1 && !boundingBox.isEmpty()) + ourRect = boundingBox; + } + +#if PLATFORM(MAC) + return m_renderer->view().frameView().contentsToScreen(snappedIntRect(ourRect)); +#else + return snappedIntRect(ourRect); +#endif } IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const @@ -1941,23 +2022,56 @@ IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePo } } - LayoutRect ourrect = rect1; - ourrect.unite(rect2); + RefPtr<Range> dataRange = makeRange(range.start, range.end); + return boundsForRects(rect1, rect2, dataRange); +} + +IntRect AccessibilityRenderObject::boundsForRange(const RefPtr<Range> range) const +{ + if (!range) + return IntRect(); - // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead - if (rect1.maxY() != rect2.maxY()) { - RefPtr<Range> dataRange = makeRange(range.start, range.end); - LayoutRect boundingBox = dataRange->boundingBox(); - String rangeString = plainText(dataRange.get()); - if (rangeString.length() > 1 && !boundingBox.isEmpty()) - ourrect = boundingBox; + AXObjectCache* cache = this->axObjectCache(); + if (!cache) + return IntRect(); + + CharacterOffset start = cache->startOrEndCharacterOffsetForRange(range, true); + CharacterOffset end = cache->startOrEndCharacterOffsetForRange(range, false); + + LayoutRect rect1 = cache->absoluteCaretBoundsForCharacterOffset(start); + LayoutRect rect2 = cache->absoluteCaretBoundsForCharacterOffset(end); + + // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds. + if (rect2.y() != rect1.y()) { + CharacterOffset endOfFirstLine = cache->endCharacterOffsetOfLine(start); + if (start.isEqual(endOfFirstLine)) { + start = cache->nextCharacterOffset(start, false); + rect1 = cache->absoluteCaretBoundsForCharacterOffset(start); + } + if (end.isEqual(endOfFirstLine)) { + end = cache->previousCharacterOffset(end, false); + rect2 = cache->absoluteCaretBoundsForCharacterOffset(end); + } } -#if PLATFORM(MAC) && !PLATFORM(IOS) - return m_renderer->view().frameView().contentsToScreen(pixelSnappedIntRect(ourrect)); -#else - return pixelSnappedIntRect(ourrect); -#endif + return boundsForRects(rect1, rect2, range); +} + +bool AccessibilityRenderObject::isVisiblePositionRangeInDifferentDocument(const VisiblePositionRange& range) const +{ + if (range.start.isNull() || range.end.isNull()) + return false; + + VisibleSelection newSelection = VisibleSelection(range.start, range.end); + if (Document* newSelectionDocument = newSelection.base().document()) { + if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) { + Frame* frame = this->frame(); + if (!frame || (newSelectionFrame != frame && newSelectionDocument != frame->document())) + return true; + } + } + + return false; } void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const @@ -1965,12 +2079,24 @@ void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePos if (range.start.isNull() || range.end.isNull()) return; + // In WebKit1, when the top web area sets the selection to be an input element in an iframe, the caret will disappear. + // FrameSelection::setSelectionWithoutUpdatingAppearance is setting the selection on the new frame in this case, and causing this behavior. + if (isWebArea() && parentObject() && parentObject()->isAttachment()) { + if (isVisiblePositionRangeInDifferentDocument(range)) + return; + } + // make selection and tell the document to use it. if it's zero length, then move to that position - if (range.start == range.end) + if (range.start == range.end) { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionMove); m_renderer->frame().selection().moveTo(range.start, UserTriggered); + clearTextSelectionIntent(axObjectCache()); + } else { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionExtend); VisibleSelection newSelection = VisibleSelection(range.start, range.end); - m_renderer->frame().selection().setSelection(newSelection); + m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions()); + clearTextSelectionIntent(axObjectCache()); } } @@ -1984,17 +2110,17 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin if (!renderView) return VisiblePosition(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) FrameView* frameView = &renderView->frameView(); #endif - Node* innerNode = 0; + Node* innerNode = nullptr; // locate the node containing the point LayoutPoint pointResult; while (1) { LayoutPoint ourpoint; -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) ourpoint = frameView->screenToContents(point); #else ourpoint = point; @@ -2014,21 +2140,21 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin pointResult = result.localPoint(); // done if hit something other than a widget - if (!renderer->isWidget()) + if (!is<RenderWidget>(*renderer)) break; // descend into widget (FRAME, IFRAME, OBJECT...) - Widget* widget = toRenderWidget(renderer)->widget(); - if (!widget || !widget->isFrameView()) + Widget* widget = downcast<RenderWidget>(*renderer).widget(); + if (!is<FrameView>(widget)) break; - Frame& frame = toFrameView(widget)->frame(); + Frame& frame = downcast<FrameView>(*widget).frame(); renderView = frame.document()->renderView(); -#if PLATFORM(MAC) - frameView = toFrameView(widget); +#if PLATFORM(COCOA) + frameView = downcast<FrameView>(widget); #endif } - return innerNode->renderer()->positionForPoint(pointResult); + return innerNode->renderer()->positionForPoint(pointResult, nullptr); } // NOTE: Consider providing this utility method as AX API @@ -2147,34 +2273,41 @@ IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& rang return IntRect(); } +IntRect AccessibilityRenderObject::doAXBoundsForRangeUsingCharacterOffset(const PlainTextRange& range) const +{ + if (allowsTextRanges()) + return boundsForRange(rangeForPlainTextRange(range)); + return IntRect(); +} + AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const { if (!area) - return 0; + return nullptr; - AccessibilityObject* parent = 0; + AccessibilityObject* parent = nullptr; for (Element* mapParent = area->parentElement(); mapParent; mapParent = mapParent->parentElement()) { - if (isHTMLMapElement(mapParent)) { - parent = accessibilityParentForImageMap(toHTMLMapElement(mapParent)); + if (is<HTMLMapElement>(*mapParent)) { + parent = accessibilityParentForImageMap(downcast<HTMLMapElement>(mapParent)); break; } } if (!parent) - return 0; + return nullptr; for (const auto& child : parent->children()) { if (child->elementRect().contains(point)) return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::remoteSVGElementHitTest(const IntPoint& point) const { - AccessibilityObject* remote = remoteSVGRootElement(); + AccessibilityObject* remote = remoteSVGRootElement(Create); if (!remote) - return 0; + return nullptr; IntSize offset = point - roundedIntPoint(boundingBoxRect().location()); return remote->accessibilityHitTest(IntPoint(offset)); @@ -2188,29 +2321,41 @@ AccessibilityObject* AccessibilityRenderObject::elementAccessibilityHitTest(cons return AccessibilityObject::elementAccessibilityHitTest(point); } +static bool shouldUseShadowHostForHitTesting(Node* shadowHost) +{ + // We need to allow automation of mouse events on video tags. + return shadowHost && !shadowHost->hasTagName(videoTag); +} + AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const { if (!m_renderer || !m_renderer->hasLayer()) - return 0; + return nullptr; - RenderLayer* layer = toRenderBox(m_renderer)->layer(); + m_renderer->document().updateLayout(); + + RenderLayer* layer = downcast<RenderBox>(*m_renderer).layer(); HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); HitTestResult hitTestResult = HitTestResult(point); layer->hitTest(request, hitTestResult); - if (!hitTestResult.innerNode()) - return 0; - Node* node = hitTestResult.innerNode()->deprecatedShadowAncestorNode(); + Node* node = hitTestResult.innerNode(); + if (!node) + return nullptr; + Node* shadowAncestorNode = node->shadowHost(); + if (shouldUseShadowHostForHitTesting(shadowAncestorNode)) + node = shadowAncestorNode; + ASSERT(node); - if (isHTMLAreaElement(node)) - return accessibilityImageMapHitTest(toHTMLAreaElement(node), point); + if (is<HTMLAreaElement>(*node)) + return accessibilityImageMapHitTest(downcast<HTMLAreaElement>(node), point); - if (isHTMLOptionElement(node)) - node = toHTMLOptionElement(node)->ownerSelectElement(); + if (is<HTMLOptionElement>(*node)) + node = downcast<HTMLOptionElement>(*node).ownerSelectElement(); RenderObject* obj = node->renderer(); if (!obj) - return 0; + return nullptr; AccessibilityObject* result = obj->document().axObjectCache()->getOrCreate(obj); result->updateChildrenIfNecessary(); @@ -2271,29 +2416,35 @@ bool AccessibilityRenderObject::shouldFocusActiveDescendant() const AccessibilityObject* AccessibilityRenderObject::activeDescendant() const { if (!m_renderer) - return 0; + return nullptr; - if (m_renderer->node() && !m_renderer->node()->isElementNode()) - return 0; - Element* element = toElement(m_renderer->node()); - - const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr); + const AtomicString& activeDescendantAttrStr = getAttribute(aria_activedescendantAttr); if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) - return 0; + return nullptr; + + Element* element = this->element(); + if (!element) + return nullptr; Element* target = element->treeScope().getElementById(activeDescendantAttrStr); if (!target) - return 0; + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) { + AccessibilityObject* obj = cache->getOrCreate(target); + if (obj && obj->isAccessibilityRenderObject()) + // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification + return obj; + } - AccessibilityObject* obj = axObjectCache()->getOrCreate(target); - if (obj && obj->isAccessibilityRenderObject()) - // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification - return obj; - return 0; + return nullptr; } void AccessibilityRenderObject::handleAriaExpandedChanged() { + // This object might be deleted under the call to the parentObject() method. + auto protectedThis = makeRef(*this); + // Find if a parent of this object should handle aria-expanded changes. AccessibilityObject* containerParent = this->parentObject(); while (containerParent) { @@ -2318,23 +2469,29 @@ void AccessibilityRenderObject::handleAriaExpandedChanged() } // Post that the row count changed. + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + if (containerParent) - axObjectCache()->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged); + cache->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged); // Post that the specific row either collapsed or expanded. if (roleValue() == RowRole || roleValue() == TreeItemRole) - axObjectCache()->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed); + cache->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed); + else + cache->postNotification(this, document(), AXObjectCache::AXExpandedChanged); } void AccessibilityRenderObject::handleActiveDescendantChanged() { - Element* element = toElement(renderer()->node()); + Element* element = downcast<Element>(renderer()->node()); if (!element) return; if (!renderer()->frame().selection().isFocusedAndActive() || renderer()->document().focusedElement() != element) return; - if (toAccessibilityRenderObject(activeDescendant()) && shouldNotifyActiveDescendant()) + if (activeDescendant() && shouldNotifyActiveDescendant()) renderer()->document().axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged); } @@ -2342,15 +2499,15 @@ AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElem { HTMLLabelElement* labelElement = labelElementContainer(); if (!labelElement) - return 0; + return nullptr; HTMLElement* correspondingControl = labelElement->control(); if (!correspondingControl) - return 0; + return nullptr; // Make sure the corresponding control isn't a descendant of this label that's in the middle of being destroyed. if (correspondingControl->renderer() && !correspondingControl->renderer()->parent()) - return 0; + return nullptr; return axObjectCache()->getOrCreate(correspondingControl); } @@ -2358,36 +2515,38 @@ AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElem AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const { if (!m_renderer) - return 0; + return nullptr; // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should // override the "label" element association. if (hasTextAlternative()) - return 0; + return nullptr; Node* node = m_renderer->node(); - if (node && node->isHTMLElement()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label) + if (is<HTMLElement>(node)) { + if (HTMLLabelElement* label = labelForElement(downcast<HTMLElement>(node))) return axObjectCache()->getOrCreate(label); } - return 0; + return nullptr; } -bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject* renderer) const +bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject& renderer) const { // AX clients will listen for AXValueChange on a text control. - if (renderer->isTextControl()) + if (is<RenderTextControl>(renderer)) return true; // AX clients will listen for AXSelectedChildrenChanged on listboxes. - Node* node = renderer->node(); - if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListBox())) + Node* node = renderer.node(); + if (!node) + return false; + + if (nodeHasRole(node, "listbox") || (is<RenderBoxModelObject>(renderer) && downcast<RenderBoxModelObject>(renderer).isListBox())) return true; // Textboxes should send out notifications. - if (nodeHasRole(node, "textbox")) + if (nodeHasRole(node, "textbox") || (is<Element>(*node) && contentEditableAttributeIsEnabled(downcast<Element>(node)))) return true; return false; @@ -2397,41 +2556,61 @@ AccessibilityObject* AccessibilityRenderObject::observableObject() const { // Find the object going up the parent chain that is used in accessibility to monitor certain notifications. for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) { - if (renderObjectIsObservable(renderer)) - return axObjectCache()->getOrCreate(renderer); + if (renderObjectIsObservable(*renderer)) { + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(renderer); + } } - return 0; + return nullptr; } bool AccessibilityRenderObject::isDescendantOfElementType(const QualifiedName& tagName) const { - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->node() && parent->node()->hasTagName(tagName)) + for (auto& ancestor : ancestorsOfType<RenderElement>(*m_renderer)) { + if (ancestor.element() && ancestor.element()->hasTagName(tagName)) return true; } return false; } + +String AccessibilityRenderObject::expandedTextValue() const +{ + if (AccessibilityObject* parent = parentObject()) { + if (parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag)) + return parent->getAttribute(titleAttr); + } + + return String(); +} + +bool AccessibilityRenderObject::supportsExpandedTextValue() const +{ + if (roleValue() == StaticTextRole) { + if (AccessibilityObject* parent = parentObject()) + return parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag); + } + + return false; +} AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() { if (!m_renderer) return UnknownRole; - m_ariaRole = determineAriaRoleAttribute(); + // Sometimes we need to ignore the attribute role. Like if a tree is malformed, + // we want to ignore the treeitem's attribute role. + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole && !shouldIgnoreAttributeRole()) + return m_ariaRole; Node* node = m_renderer->node(); - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; - RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (node && node->isLink()) { - if (cssBox && cssBox->isImage()) - return ImageMapRole; + if (node && node->isLink()) return WebCoreLinkRole; - } + if (node && is<HTMLImageElement>(*node) && downcast<HTMLImageElement>(*node).hasAttributeWithoutSynchronization(usemapAttr)) + return ImageMapRole; if ((cssBox && cssBox->isListItem()) || (node && node->hasTagName(liTag))) return ListItemRole; if (m_renderer->isListMarker()) @@ -2443,7 +2622,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (m_renderer->isText()) return StaticTextRole; if (cssBox && cssBox->isImage()) { - if (node && isHTMLInputElement(node)) + if (is<HTMLInputElement>(node)) return ariaHasPopup() ? PopUpButtonRole : ButtonRole; if (isSVGImage()) return SVGRootRole; @@ -2453,35 +2632,39 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(canvasTag)) return CanvasRole; - if (cssBox && cssBox->isRenderView()) { - // If the iframe is seamless, it should not be announced as a web area to AT clients. - if (document() && document()->shouldDisplaySeamlesslyWithParent()) - return SeamlessWebAreaRole; + if (cssBox && cssBox->isRenderView()) return WebAreaRole; - } - if (cssBox && cssBox->isTextField()) - return TextFieldRole; + if (cssBox && cssBox->isTextField()) { + if (is<HTMLInputElement>(node)) + return downcast<HTMLInputElement>(*node).isSearchField() ? SearchFieldRole : TextFieldRole; + } if (cssBox && cssBox->isTextArea()) return TextAreaRole; - if (node && isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isCheckbox()) + if (is<HTMLInputElement>(node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isCheckbox()) return CheckBoxRole; - if (input->isRadioButton()) + if (input.isRadioButton()) return RadioButtonRole; - if (input->isTextButton()) + if (input.isTextButton()) return buttonRoleType(); - + // On iOS, the date field and time field are popup buttons. On other platforms they are text fields. +#if PLATFORM(IOS) + if (input.isDateField() || input.isTimeField()) + return PopUpButtonRole; +#endif #if ENABLE(INPUT_TYPE_COLOR) - const AtomicString& type = input->getAttribute(typeAttr); - if (equalIgnoringCase(type, "color")) + if (input.isColorControl()) return ColorWellRole; #endif } - + + if (hasContentEditableAttributeSet()) + return TextAreaRole; + if (isFileUploadButton()) return ButtonRole; @@ -2491,23 +2674,11 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (headingLevel()) return HeadingRole; -#if ENABLE(SVG) - if (m_renderer->isSVGImage()) - return ImageRole; if (m_renderer->isSVGRoot()) return SVGRootRole; - if (node && node->hasTagName(SVGNames::gTag)) - return GroupRole; -#endif - -#if ENABLE(MATHML) - if (node && node->hasTagName(MathMLNames::mathTag)) - return DocumentMathRole; -#endif - // It's not clear which role a platform should choose for a math element. - // Declaring a math element role should give flexibility to platforms to choose. - if (isMathElement()) - return MathElementRole; + + if (isStyleFormatGroup()) + return is<RenderInline>(*m_renderer) ? InlineRole : GroupRole; if (node && node->hasTagName(ddTag)) return DescriptionListDetailRole; @@ -2518,19 +2689,27 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(dlTag)) return DescriptionListRole; - if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) - return AnnotationRole; - + // Check for Ruby elements + if (m_renderer->isRubyText()) + return RubyTextRole; + if (m_renderer->isRubyBase()) + return RubyBaseRole; + if (m_renderer->isRubyRun()) + return RubyRunRole; + if (m_renderer->isRubyBlock()) + return RubyBlockRole; + if (m_renderer->isRubyInline()) + return RubyInlineRole; + + // This return value is what will be used if AccessibilityTableCell determines + // the cell should not be treated as a cell (e.g. because it is a layout table. + // In ATK, there is a distinction between generic text block elements and other + // generic containers; AX API does not make this distinction. + if (is<RenderTableCell>(m_renderer)) #if PLATFORM(GTK) - // Gtk ATs expect all tables, data and layout, to be exposed as tables. - if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) - return CellRole; - - if (node && node->hasTagName(trTag)) - return RowRole; - - if (node && isHTMLTableElement(node)) - return TableRole; + return DivRole; +#else + return GroupRole; #endif // Table sections should be ignored. @@ -2543,7 +2722,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(pTag)) return ParagraphRole; - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return LabelRole; if (node && node->hasTagName(dfnTag)) @@ -2552,7 +2731,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(divTag)) return DivRole; - if (node && isHTMLFormElement(node)) + if (is<HTMLFormElement>(node)) return FormRole; if (node && node->hasTagName(articleTag)) @@ -2567,16 +2746,41 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(asideTag)) return LandmarkComplementaryRole; + // The default role attribute value for the section element, region, became a landmark in ARIA 1.1. + // The HTML AAM spec says it is "strongly recommended" that ATs only convey and provide navigation + // for section elements which have names. if (node && node->hasTagName(sectionTag)) - return DocumentRegionRole; + return hasAttribute(aria_labelAttr) || hasAttribute(aria_labelledbyAttr) ? LandmarkRegionRole : GroupRole; if (node && node->hasTagName(addressTag)) return LandmarkContentInfoRole; + if (node && node->hasTagName(blockquoteTag)) + return BlockquoteRole; + + if (node && node->hasTagName(captionTag)) + return CaptionRole; + + if (node && node->hasTagName(markTag)) + return MarkRole; + + if (node && node->hasTagName(preTag)) + return PreRole; + + if (is<HTMLDetailsElement>(node)) + return DetailsRole; + if (is<HTMLSummaryElement>(node)) + return SummaryRole; + + // http://rawgit.com/w3c/aria/master/html-aam/html-aam.html + // Output elements should be mapped to status role. + if (isOutput()) + return ApplicationStatusRole; + #if ENABLE(VIDEO) - if (node && isHTMLVideoElement(node)) + if (is<HTMLVideoElement>(node)) return VideoRole; - if (node && isHTMLAudioElement(node)) + if (is<HTMLAudioElement>(node)) return AudioRole; #endif @@ -2590,25 +2794,55 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() return LandmarkBannerRole; if (node && node->hasTagName(footerTag) && !isDescendantOfElementType(articleTag) && !isDescendantOfElementType(sectionTag)) return FooterRole; - - if (m_renderer->isRenderBlockFlow()) - return GroupRole; + + // menu tags with toolbar type should have Toolbar role. + if (node && node->hasTagName(menuTag) && equalLettersIgnoringASCIICase(getAttribute(typeAttr), "toolbar")) + return ToolbarRole; // If the element does not have role, but it has ARIA attributes, or accepts tab focus, accessibility should fallback to exposing it as a group. if (supportsARIAAttributes() || canSetFocusAttribute()) return GroupRole; + + if (m_renderer->isRenderBlockFlow()) { +#if PLATFORM(GTK) + // For ATK, GroupRole maps to ATK_ROLE_PANEL. Panels are most commonly found (and hence + // expected) in UI elements; not text blocks. + return m_renderer->isAnonymousBlock() ? DivRole : GroupRole; +#else + return GroupRole; +#endif + } + // InlineRole is the final fallback before assigning UnknownRole to an object. It makes it + // possible to distinguish truly unknown objects from non-focusable inline text elements + // which have an event handler or attribute suggesting possible inclusion by the platform. + if (is<RenderInline>(*m_renderer) + && (hasAttributesRequiredForInclusion() + || (node && node->hasEventListeners()) + || (supportsDatetimeAttribute() && !getAttribute(datetimeAttr).isEmpty()))) + return InlineRole; + return UnknownRole; } AccessibilityOrientation AccessibilityRenderObject::orientation() const { const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); - if (equalIgnoringCase(ariaOrientation, "horizontal")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "horizontal")) return AccessibilityOrientationHorizontal; - if (equalIgnoringCase(ariaOrientation, "vertical")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "vertical")) + return AccessibilityOrientationVertical; + if (equalLettersIgnoringASCIICase(ariaOrientation, "undefined")) + return AccessibilityOrientationUndefined; + + // ARIA 1.1 Implicit defaults are defined on some roles. + // http://www.w3.org/TR/wai-aria-1.1/#aria-orientation + if (isScrollbar() || isComboBox() || isListBox() || isMenu() || isTree()) return AccessibilityOrientationVertical; + if (isMenuBar() || isSplitter() || isTabList() || isToolbar()) + return AccessibilityOrientationHorizontal; + return AccessibilityObject::orientation(); } @@ -2621,18 +2855,25 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const // ARIA spec says that when a parent object is presentational, and it has required child elements, // those child elements are also presentational. For example, <li> becomes presentational from <ul>. // http://www.w3.org/WAI/PF/aria/complete#presentation - DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, listItemParents, ()); + static NeverDestroyed<HashSet<QualifiedName>> listItemParents; + static NeverDestroyed<HashSet<QualifiedName>> tableCellParents; - HashSet<QualifiedName>* possibleParentTagNames = 0; + HashSet<QualifiedName>* possibleParentTagNames = nullptr; switch (roleValue()) { case ListItemRole: case ListMarkerRole: - if (listItemParents.isEmpty()) { - listItemParents.add(ulTag); - listItemParents.add(olTag); - listItemParents.add(dlTag); + if (listItemParents.get().isEmpty()) { + listItemParents.get().add(ulTag); + listItemParents.get().add(olTag); + listItemParents.get().add(dlTag); } - possibleParentTagNames = &listItemParents; + possibleParentTagNames = &listItemParents.get(); + break; + case GridCellRole: + case CellRole: + if (tableCellParents.get().isEmpty()) + tableCellParents.get().add(tableTag); + possibleParentTagNames = &tableCellParents.get(); break; default: break; @@ -2643,16 +2884,16 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const return false; for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (!parent->isAccessibilityRenderObject()) + if (!is<AccessibilityRenderObject>(*parent)) continue; - Node* elementNode = toAccessibilityRenderObject(parent)->node(); - if (!elementNode || !elementNode->isElementNode()) + Node* node = downcast<AccessibilityRenderObject>(*parent).node(); + if (!is<Element>(node)) continue; // If native tag of the parent element matches an acceptable name, then return // based on its presentational status. - if (possibleParentTagNames->contains(toElement(elementNode)->tagQName())) + if (possibleParentTagNames->contains(downcast<Element>(node)->tagQName())) return parent->roleValue() == PresentationalRole; } @@ -2686,37 +2927,12 @@ bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const bool AccessibilityRenderObject::canSetExpandedAttribute() const { + if (roleValue() == DetailsRole) + return true; + // An object can be expanded if it aria-expanded is true or false. const AtomicString& ariaExpanded = getAttribute(aria_expandedAttr); - return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); -} - -bool AccessibilityRenderObject::canSetValueAttribute() const -{ - - // In the event of a (Boolean)@readonly and (True/False/Undefined)@aria-readonly - // value mismatch, the host language native attribute value wins. - if (isNativeTextControl()) - return !isReadOnly(); - - if (isMeter()) - return false; - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true")) - return false; - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "false")) - return true; - - if (isProgressIndicator() || isSlider()) - return true; - - if (isTextControl() && !isNativeTextControl()) - return true; - - // Any node could be contenteditable, so isReadOnly should be relied upon - // for this information for all elements. - return !isReadOnly(); + return equalLettersIgnoringASCIICase(ariaExpanded, "true") || equalLettersIgnoringASCIICase(ariaExpanded, "false"); } bool AccessibilityRenderObject::canSetTextRangeAttributes() const @@ -2729,15 +2945,18 @@ void AccessibilityRenderObject::textChanged() // If this element supports ARIA live regions, or is part of a region with an ARIA editable role, // then notify the AT of changes. AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) { AccessibilityObject* parent = cache->get(renderParent); if (!parent) continue; if (parent->supportsARIALiveRegion()) - cache->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged); + cache->postLiveRegionChangeNotification(parent); - if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->hasEditableStyle()) + if (parent->isNonNativeTextControl()) cache->postNotification(renderParent, AXObjectCache::AXValueChanged); } } @@ -2751,10 +2970,10 @@ void AccessibilityRenderObject::clearChildren() void AccessibilityRenderObject::addImageMapChildren() { RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (!cssBox || !cssBox->isRenderImage()) + if (!is<RenderImage>(cssBox)) return; - HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); + HTMLMapElement* map = downcast<RenderImage>(*cssBox).imageMap(); if (!map) return; @@ -2762,14 +2981,14 @@ void AccessibilityRenderObject::addImageMapChildren() // add an <area> element for this child if it has a link if (!area.isLink()) continue; - AccessibilityImageMapLink* areaObject = toAccessibilityImageMapLink(axObjectCache()->getOrCreate(ImageMapLinkRole)); - areaObject->setHTMLAreaElement(&area); - areaObject->setHTMLMapElement(map); - areaObject->setParent(this); - if (!areaObject->accessibilityIsIgnored()) - m_children.append(areaObject); + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + areaObject.setHTMLAreaElement(&area); + areaObject.setHTMLMapElement(map); + areaObject.setParent(this); + if (!areaObject.accessibilityIsIgnored()) + m_children.append(&areaObject); else - axObjectCache()->remove(areaObject->axObjectID()); + axObjectCache()->remove(areaObject.axObjectID()); } } @@ -2784,56 +3003,59 @@ void AccessibilityRenderObject::updateChildrenIfNecessary() void AccessibilityRenderObject::addTextFieldChildren() { Node* node = this->node(); - if (!node || !isHTMLInputElement(node)) + if (!is<HTMLInputElement>(node)) return; - HTMLInputElement* input = toHTMLInputElement(node); - HTMLElement* spinButtonElement = input->innerSpinButtonElement(); - if (!spinButtonElement || !spinButtonElement->isSpinButtonElement()) + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (HTMLElement* autoFillElement = input.autoFillButtonElement()) { + if (AccessibilityObject* axAutoFill = axObjectCache()->getOrCreate(autoFillElement)) + m_children.append(axAutoFill); + } + + HTMLElement* spinButtonElement = input.innerSpinButtonElement(); + if (!is<SpinButtonElement>(spinButtonElement)) return; - AccessibilitySpinButton* axSpinButton = toAccessibilitySpinButton(axObjectCache()->getOrCreate(SpinButtonRole)); - axSpinButton->setSpinButtonElement(static_cast<SpinButtonElement*>(spinButtonElement)); - axSpinButton->setParent(this); - m_children.append(axSpinButton); + auto& axSpinButton = downcast<AccessibilitySpinButton>(*axObjectCache()->getOrCreate(SpinButtonRole)); + axSpinButton.setSpinButtonElement(downcast<SpinButtonElement>(spinButtonElement)); + axSpinButton.setParent(this); + m_children.append(&axSpinButton); } bool AccessibilityRenderObject::isSVGImage() const { - return remoteSVGRootElement(); + return remoteSVGRootElement(Create); } void AccessibilityRenderObject::detachRemoteSVGRoot() { - if (AccessibilitySVGRoot* root = remoteSVGRootElement()) - root->setParent(0); + if (AccessibilitySVGRoot* root = remoteSVGRootElement(Retrieve)) + root->setParent(nullptr); } -AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement() const +AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement(CreationChoice createIfNecessary) const { -#if ENABLE(SVG) - if (!m_renderer || !m_renderer->isRenderImage()) + if (!is<RenderImage>(m_renderer)) return nullptr; - CachedImage* cachedImage = toRenderImage(m_renderer)->cachedImage(); + CachedImage* cachedImage = downcast<RenderImage>(*m_renderer).cachedImage(); if (!cachedImage) return nullptr; Image* image = cachedImage->image(); - if (!image || !image->isSVGImage()) + if (!is<SVGImage>(image)) return nullptr; - SVGImage* svgImage = static_cast<SVGImage*>(image); - FrameView* frameView = svgImage->frameView(); + FrameView* frameView = downcast<SVGImage>(*image).frameView(); if (!frameView) return nullptr; Frame& frame = frameView->frame(); - Document* doc = frame.document(); - if (!doc || !doc->isSVGDocument()) + Document* document = frame.document(); + if (!is<SVGDocument>(document)) return nullptr; - SVGSVGElement* rootElement = toSVGDocument(doc)->rootElement(); + SVGSVGElement* rootElement = SVGDocument::rootElement(*document); if (!rootElement) return nullptr; RenderObject* rendererRoot = rootElement->renderer(); @@ -2843,23 +3065,20 @@ AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement() const AXObjectCache* cache = frame.document()->axObjectCache(); if (!cache) return nullptr; - AccessibilityObject* rootSVGObject = cache->getOrCreate(rendererRoot); + AccessibilityObject* rootSVGObject = createIfNecessary == Create ? cache->getOrCreate(rendererRoot) : cache->get(rendererRoot); // In order to connect the AX hierarchy from the SVG root element from the loaded resource // the parent must be set, because there's no other way to get back to who created the image. - ASSERT(rootSVGObject && rootSVGObject->isAccessibilitySVGRoot()); - if (!rootSVGObject->isAccessibilitySVGRoot()) + ASSERT(!createIfNecessary || rootSVGObject); + if (!is<AccessibilitySVGRoot>(rootSVGObject)) return nullptr; - return toAccessibilitySVGRoot(rootSVGObject); -#else - return nullptr; -#endif + return downcast<AccessibilitySVGRoot>(rootSVGObject); } void AccessibilityRenderObject::addRemoteSVGChildren() { - AccessibilitySVGRoot* root = remoteSVGRootElement(); + AccessibilitySVGRoot* root = remoteSVGRootElement(Create); if (!root) return; @@ -2901,7 +3120,7 @@ void AccessibilityRenderObject::addAttachmentChildren() m_children.append(axWidget); } -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void AccessibilityRenderObject::updateAttachmentViewParents() { // Only the unignored parent should set the attachment parent, because that's what is reflected in the AX @@ -2949,7 +3168,7 @@ void AccessibilityRenderObject::addHiddenChildren() if (children.size()) childObject = children.last().get(); else - childObject = 0; + childObject = nullptr; } if (childObject) @@ -2969,6 +3188,24 @@ void AccessibilityRenderObject::addHiddenChildren() } } +void AccessibilityRenderObject::updateRoleAfterChildrenCreation() +{ + // If a menu does not have valid menuitem children, it should not be exposed as a menu. + if (roleValue() == MenuRole) { + // Elements marked as menus must have at least one menu item child. + size_t menuItemCount = 0; + for (const auto& child : children()) { + if (child->isMenuItem()) { + menuItemCount++; + break; + } + } + + if (!menuItemCount) + m_role = GroupRole; + } +} + void AccessibilityRenderObject::addChildren() { // If the need to add more children in addition to existing children arises, @@ -2990,9 +3227,11 @@ void AccessibilityRenderObject::addChildren() addCanvasChildren(); addRemoteSVGChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) updateAttachmentViewParents(); #endif + + updateRoleAfterChildrenCreation(); } bool AccessibilityRenderObject::canHaveChildren() const @@ -3003,36 +3242,19 @@ bool AccessibilityRenderObject::canHaveChildren() const return AccessibilityNodeObject::canHaveChildren(); } -const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const +const String AccessibilityRenderObject::ariaLiveRegionStatus() const { - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off", AtomicString::ConstructFromLiteral)); - const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); // These roles have implicit live region status. - if (liveRegionStatus.isEmpty()) { - switch (roleValue()) { - case ApplicationAlertDialogRole: - case ApplicationAlertRole: - return liveRegionStatusAssertive; - case ApplicationLogRole: - case ApplicationStatusRole: - return liveRegionStatusPolite; - case ApplicationTimerRole: - case ApplicationMarqueeRole: - return liveRegionStatusOff; - default: - break; - } - } + if (liveRegionStatus.isEmpty()) + return defaultLiveRegionStatusForRole(roleValue()); return liveRegionStatus; } const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const { - DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text", AtomicString::ConstructFromLiteral)); + static NeverDestroyed<const AtomicString> defaultLiveRegionRelevant("additions text", AtomicString::ConstructFromLiteral); const AtomicString& relevant = getAttribute(aria_relevantAttr); // Default aria-relevant = "additions text". @@ -3044,23 +3266,49 @@ const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const bool AccessibilityRenderObject::ariaLiveRegionAtomic() const { - return elementAttributeValue(aria_atomicAttr); + const AtomicString& atomic = getAttribute(aria_atomicAttr); + if (equalLettersIgnoringASCIICase(atomic, "true")) + return true; + if (equalLettersIgnoringASCIICase(atomic, "false")) + return false; + + // WAI-ARIA "alert" and "status" roles have an implicit aria-atomic value of true. + switch (roleValue()) { + case ApplicationAlertRole: + case ApplicationStatusRole: + return true; + default: + return false; + } } -bool AccessibilityRenderObject::ariaLiveRegionBusy() const +bool AccessibilityRenderObject::isBusy() const { return elementAttributeValue(aria_busyAttr); } + +bool AccessibilityRenderObject::canHaveSelectedChildren() const +{ + switch (roleValue()) { + // These roles are containers whose children support aria-selected: + case GridRole: + case ListBoxRole: + case TabListRole: + case TreeRole: + case TreeGridRole: + // These roles are containers whose children are treated as selected by assistive + // technologies. We can get the "selected" item via aria-activedescendant or the + // focused element. + case MenuRole: + case MenuBarRole: + return true; + default: + return false; + } +} void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) { - // Get all the rows. - AccessibilityChildrenVector allRows; - if (isTree()) - ariaTreeRows(allRows); - else if (isAccessibilityTable() && toAccessibilityTable(this)->supportsSelectedRows()) - allRows = toAccessibilityTable(this)->rows(); - // Determine which rows are selected. bool isMulti = isMultiSelectable(); @@ -3072,12 +3320,24 @@ void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& re return; } - for (const auto& row : allRows) { - if (row->isSelected()) { - result.append(row); - if (!isMulti) - break; + // Get all the rows. + auto rowsIteration = [&](auto& rows) { + for (auto& row : rows) { + if (row->isSelected()) { + result.append(row); + if (!isMulti) + break; + } } + }; + if (isTree()) { + AccessibilityChildrenVector allRows; + ariaTreeRows(allRows); + rowsIteration(allRows); + } else if (is<AccessibilityTable>(*this)) { + auto& thisTable = downcast<AccessibilityTable>(*this); + if (thisTable.isExposableThroughAccessibility() && thisTable.supportsSelectedRows()) + rowsIteration(thisTable.rows()); } } @@ -3099,12 +3359,36 @@ void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& re { ASSERT(result.isEmpty()); - // only listboxes should be asked for their selected children. - AccessibilityRole role = roleValue(); - if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes + if (!canHaveSelectedChildren()) + return; + + switch (roleValue()) { + case ListBoxRole: + // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes ariaListboxSelectedChildren(result); - else if (role == TreeRole || role == TreeGridRole || role == TableRole) + return; + case GridRole: + case TreeRole: ariaSelectedRows(result); + return; + case TabListRole: + if (AccessibilityObject* selectedTab = selectedTabItem()) + result.append(selectedTab); + return; + case MenuRole: + case MenuBarRole: + if (AccessibilityObject* descendant = activeDescendant()) { + result.append(descendant); + return; + } + if (AccessibilityObject* focusedElement = focusedUIElement()) { + result.append(focusedElement); + return; + } + return; + default: + ASSERT_NOT_REACHED(); + } } void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) @@ -3144,12 +3428,12 @@ const String& AccessibilityRenderObject::actionVerb() const { #if !PLATFORM(IOS) // FIXME: Need to add verbs for select elements. - DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); - DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); + static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); + static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); + static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); + static NeverDestroyed<const String> checkedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); switch (roleValue()) { case ButtonRole: @@ -3179,15 +3463,15 @@ void AccessibilityRenderObject::setAccessibleName(const AtomicString& name) if (!m_renderer) return; - Node* domNode = 0; + Node* node = nullptr; // For web areas, set the aria-label on the HTML element. if (isWebArea()) - domNode = m_renderer->document().documentElement(); + node = m_renderer->document().documentElement(); else - domNode = m_renderer->node(); + node = m_renderer->node(); - if (domNode && domNode->isElementNode()) - toElement(domNode)->setAttribute(aria_labelAttr, name); + if (is<Element>(node)) + downcast<Element>(*node).setAttribute(aria_labelAttr, name); } static bool isLinkable(const AccessibilityRenderObject& object) @@ -3204,8 +3488,8 @@ String AccessibilityRenderObject::stringValueForMSAA() const { if (isLinkable(*this)) { Element* anchor = anchorElement(); - if (anchor && isHTMLAnchorElement(anchor)) - return toHTMLAnchorElement(anchor)->href(); + if (is<HTMLAnchorElement>(anchor)) + return downcast<HTMLAnchorElement>(*anchor).href(); } return stringValue(); @@ -3217,10 +3501,10 @@ bool AccessibilityRenderObject::isLinked() const return false; Element* anchor = anchorElement(); - if (!anchor || !isHTMLAnchorElement(anchor)) + if (!is<HTMLAnchorElement>(anchor)) return false; - return !toHTMLAnchorElement(anchor)->href().isEmpty(); + return !downcast<HTMLAnchorElement>(*anchor).href().isEmpty(); } bool AccessibilityRenderObject::hasBoldFont() const @@ -3293,12 +3577,16 @@ String AccessibilityRenderObject::nameForMSAA() const static bool shouldReturnTagNameAsRoleForMSAA(const Element& element) { - // See "document structure", - // https://wiki.mozilla.org/Accessibility/AT-Windows-API - // FIXME: Add the other tag names that should be returned as the role. - return element.hasTagName(h1Tag) || element.hasTagName(h2Tag) + return element.hasTagName(abbrTag) || element.hasTagName(acronymTag) + || element.hasTagName(blockquoteTag) || element.hasTagName(ddTag) + || element.hasTagName(dlTag) || element.hasTagName(dtTag) + || element.hasTagName(formTag) || element.hasTagName(frameTag) + || element.hasTagName(h1Tag) || element.hasTagName(h2Tag) || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) - || element.hasTagName(h5Tag) || element.hasTagName(h6Tag); + || element.hasTagName(h5Tag) || element.hasTagName(h6Tag) + || element.hasTagName(iframeTag) || element.hasTagName(qTag) + || element.hasTagName(tbodyTag) || element.hasTagName(tfootTag) + || element.hasTagName(theadTag); } String AccessibilityRenderObject::stringRoleForMSAA() const @@ -3307,14 +3595,14 @@ String AccessibilityRenderObject::stringRoleForMSAA() const return String(); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return String(); - Element* element = toElement(node); - if (!shouldReturnTagNameAsRoleForMSAA(*element)) + Element& element = downcast<Element>(*node); + if (!shouldReturnTagNameAsRoleForMSAA(element)) return String(); - return element->tagName(); + return element.tagName(); } String AccessibilityRenderObject::positionalDescriptionForMSAA() const @@ -3352,10 +3640,10 @@ static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer) if (!renderer) return UnknownRole; - if (renderer->isText()) + if (is<RenderText>(*renderer)) return EditableTextRole; - if (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListItem()) + if (is<RenderListItem>(*renderer)) return ListItemRole; return UnknownRole; @@ -3380,397 +3668,52 @@ String AccessibilityRenderObject::passwordFieldValue() const // Look for the RenderText object in the RenderObject tree for this input field. RenderObject* renderer = node()->renderer(); - while (renderer && !renderer->isText()) - renderer = toRenderElement(renderer)->firstChild(); + while (renderer && !is<RenderText>(renderer)) + renderer = downcast<RenderElement>(*renderer).firstChild(); - if (!renderer || !renderer->isText()) + if (!is<RenderText>(renderer)) return String(); // Return the text that is actually being rendered in the input field. - return toRenderText(renderer)->textWithoutConvertingBackslashToYenSymbol(); + return downcast<RenderText>(*renderer).textWithoutConvertingBackslashToYenSymbol(); } ScrollableArea* AccessibilityRenderObject::getScrollableAreaIfScrollable() const { // If the parent is a scroll view, then this object isn't really scrollable, the parent ScrollView should handle the scrolling. if (parentObject() && parentObject()->isAccessibilityScrollView()) - return 0; + return nullptr; - if (!m_renderer || !m_renderer->isBox()) - return 0; + if (!is<RenderBox>(m_renderer)) + return nullptr; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) - return 0; + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) + return nullptr; - return box->layer(); + return box.layer(); } void AccessibilityRenderObject::scrollTo(const IntPoint& point) const { - if (!m_renderer || !m_renderer->isBox()) + if (!is<RenderBox>(m_renderer)) return; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) return; - RenderLayer* layer = box->layer(); - layer->scrollToOffset(toIntSize(point), RenderLayer::ScrollOffsetClamped); + // FIXME: is point a ScrollOffset or ScrollPosition? Test in RTL overflow. + box.layer()->scrollToOffset(point, RenderLayer::ScrollOffsetClamped); } #if ENABLE(MATHML) -bool AccessibilityRenderObject::isMathElement() const -{ - Node* node = this->node(); - if (!m_renderer || !node) - return false; - - return node->isMathMLElement(); -} - -bool AccessibilityRenderObject::isMathFraction() const -{ - return m_renderer && m_renderer->isRenderMathMLFraction(); -} - -bool AccessibilityRenderObject::isMathFenced() const -{ - return m_renderer && m_renderer->isRenderMathMLFenced(); -} - -bool AccessibilityRenderObject::isMathSubscriptSuperscript() const -{ - return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript(); -} - -bool AccessibilityRenderObject::isMathRow() const -{ - return m_renderer && m_renderer->isRenderMathMLRow(); -} - -bool AccessibilityRenderObject::isMathUnderOver() const -{ - return m_renderer && m_renderer->isRenderMathMLUnderOver(); -} - -bool AccessibilityRenderObject::isMathSquareRoot() const -{ - return m_renderer && m_renderer->isRenderMathMLSquareRoot(); -} - -bool AccessibilityRenderObject::isMathRoot() const -{ - return m_renderer && m_renderer->isRenderMathMLRoot(); -} - -bool AccessibilityRenderObject::isMathOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - // Ensure that this is actually a render MathML operator because - // MathML will create MathMLBlocks and use the original node as the node - // of this new block that is not tied to the DOM. - return isMathElement() && node()->hasTagName(MathMLNames::moTag); -} - -bool AccessibilityRenderObject::isMathFenceOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - return toRenderMathMLOperator(*m_renderer).operatorType() == RenderMathMLOperator::Fence; -} - -bool AccessibilityRenderObject::isMathSeparatorOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - return toRenderMathMLOperator(*m_renderer).operatorType() == RenderMathMLOperator::Separator; -} - -bool AccessibilityRenderObject::isMathText() const -{ - return node() && node()->hasTagName(MathMLNames::mtextTag); -} - -bool AccessibilityRenderObject::isMathNumber() const -{ - return node() && node()->hasTagName(MathMLNames::mnTag); -} - -bool AccessibilityRenderObject::isMathIdentifier() const -{ - return node() && node()->hasTagName(MathMLNames::miTag); -} - -bool AccessibilityRenderObject::isMathMultiscript() const -{ - return node() && node()->hasTagName(MathMLNames::mmultiscriptsTag); -} - -bool AccessibilityRenderObject::isMathTable() const -{ - return node() && node()->hasTagName(MathMLNames::mtableTag); -} - -bool AccessibilityRenderObject::isMathTableRow() const -{ - return node() && node()->hasTagName(MathMLNames::mtrTag); -} - -bool AccessibilityRenderObject::isMathTableCell() const -{ - return node() && node()->hasTagName(MathMLNames::mtdTag); -} - bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const { - if (!m_renderer) - return true; - - // Ignore items that were created for layout purposes only. - if (m_renderer->isRenderMathMLBlock() && toRenderMathMLBlock(m_renderer)->ignoreInAccessibilityTree()) - return true; - - // Ignore anonymous renderers inside math blocks. - if (m_renderer->isAnonymous()) { - for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (parent->isMathElement()) - return true; - } - } - - // Only math elements that we explicitly recognize should be included - // We don't want things like <mstyle> to appear in the tree. - if (isMathElement()) { - if (isMathFraction() || isMathFenced() || isMathSubscriptSuperscript() || isMathRow() - || isMathUnderOver() || isMathRoot() || isMathText() || isMathNumber() - || isMathOperator() || isMathFenceOperator() || isMathSeparatorOperator() - || isMathIdentifier() || isMathTable() || isMathTableRow() || isMathTableCell() || isMathMultiscript()) - return false; - return true; - } - - return false; -} - -AccessibilityObject* AccessibilityRenderObject::mathRadicandObject() -{ - if (!isMathRoot()) - return 0; - - const auto& children = this->children(); - if (children.size() < 1) - return 0; - - // The radicand is the value being rooted and must be listed first. - return children[0].get(); + // We ignore anonymous boxes inserted into RenderMathMLBlocks to honor CSS rules. + // See https://www.w3.org/TR/css3-box/#block-level0 + return m_renderer && m_renderer->isAnonymous() && m_renderer->parent() && is<RenderMathMLBlock>(m_renderer->parent()); } - -AccessibilityObject* AccessibilityRenderObject::mathRootIndexObject() -{ - if (!isMathRoot()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - // The index in a root is the value which determines if it's a square, cube, etc, root - // and must be listed second. - return children[1].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathNumeratorObject() -{ - if (!isMathFraction()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - return children[0].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathDenominatorObject() -{ - if (!isMathFraction()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - return children[1].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathUnderObject() -{ - if (!isMathUnderOver() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag)) - return children[1].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathOverObject() -{ - if (!isMathUnderOver() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::moverTag)) - return children[1].get(); - if (node()->hasTagName(MathMLNames::munderoverTag)) - return children[2].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathBaseObject() -{ - if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript()) - return 0; - - const auto& children = this->children(); - // The base object in question is always the first child. - if (children.size() > 0) - return children[0].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathSubscriptObject() -{ - if (!isMathSubscriptSuperscript() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag)) - return children[1].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathSuperscriptObject() -{ - if (!isMathSubscriptSuperscript() || !node()) - return 0; - - const auto& children = this->children(); - unsigned count = children.size(); - - if (count >= 2 && node()->hasTagName(MathMLNames::msupTag)) - return children[1].get(); - - if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag)) - return children[2].get(); - - return 0; -} - -String AccessibilityRenderObject::mathFencedOpenString() const -{ - if (!isMathFenced()) - return String(); - - return getAttribute(MathMLNames::openAttr); -} - -String AccessibilityRenderObject::mathFencedCloseString() const -{ - if (!isMathFenced()) - return String(); - - return getAttribute(MathMLNames::closeAttr); -} - -void AccessibilityRenderObject::mathPrescripts(AccessibilityMathMultiscriptPairs& prescripts) -{ - if (!isMathMultiscript() || !node()) - return; - - bool foundPrescript = false; - std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair; - for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { - if (foundPrescript) { - AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); - if (axChild && axChild->isMathElement()) { - if (!prescriptPair.first) - prescriptPair.first = axChild; - else { - prescriptPair.second = axChild; - prescripts.append(prescriptPair); - prescriptPair.first = 0; - prescriptPair.second = 0; - } - } - } else if (child->hasTagName(MathMLNames::mprescriptsTag)) - foundPrescript = true; - } - - // Handle the odd number of pre scripts case. - if (prescriptPair.first) - prescripts.append(prescriptPair); -} - -void AccessibilityRenderObject::mathPostscripts(AccessibilityMathMultiscriptPairs& postscripts) -{ - if (!isMathMultiscript() || !node()) - return; - - // In Multiscripts, the post-script elements start after the first element (which is the base) - // and continue until a <mprescripts> tag is found - std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair; - bool foundBaseElement = false; - for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { - if (child->hasTagName(MathMLNames::mprescriptsTag)) - break; - - AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); - if (axChild && axChild->isMathElement()) { - if (!foundBaseElement) - foundBaseElement = true; - else if (!postscriptPair.first) - postscriptPair.first = axChild; - else { - postscriptPair.second = axChild; - postscripts.append(postscriptPair); - postscriptPair.first = 0; - postscriptPair.second = 0; - } - } - } - - // Handle the odd number of post scripts case. - if (postscriptPair.first) - postscripts.append(postscriptPair); -} - -int AccessibilityRenderObject::mathLineThickness() const -{ - if (!isMathFraction()) - return -1; - - return toRenderMathMLFraction(m_renderer)->lineThickness(); -} - #endif } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.h b/Source/WebCore/accessibility/AccessibilityRenderObject.h index 3902fb8c6..cccb39ebe 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.h +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityRenderObject_h -#define AccessibilityRenderObject_h +#pragma once #include "AccessibilityNodeObject.h" #include "LayoutRect.h" @@ -38,222 +37,219 @@ namespace WebCore { class AccessibilitySVGRoot; class AXObjectCache; class Element; -class Frame; class FrameView; -class HitTestResult; -class HTMLAnchorElement; class HTMLAreaElement; class HTMLElement; class HTMLLabelElement; class HTMLMapElement; -class HTMLSelectElement; class IntPoint; class IntSize; class Node; -class RenderListBox; class RenderTextControl; class RenderView; class VisibleSelection; class Widget; class AccessibilityRenderObject : public AccessibilityNodeObject { -protected: - explicit AccessibilityRenderObject(RenderObject*); public: - static PassRefPtr<AccessibilityRenderObject> create(RenderObject*); + static Ref<AccessibilityRenderObject> create(RenderObject*); virtual ~AccessibilityRenderObject(); - - virtual bool isAccessibilityRenderObject() const override { return true; } - virtual void init() override; + void init() override; - virtual bool isAttachment() const override; - virtual bool isFileUploadButton() const override; + bool isAttachment() const override; + bool isFileUploadButton() const override; - virtual bool isSelected() const override; - virtual bool isFocused() const override; - virtual bool isLoaded() const override; - virtual bool isOffScreen() const override; - virtual bool isReadOnly() const override; - virtual bool isUnvisited() const override; - virtual bool isVisited() const override; - virtual bool isLinked() const override; - virtual bool hasBoldFont() const override; - virtual bool hasItalicFont() const override; - virtual bool hasPlainText() const override; - virtual bool hasSameFont(RenderObject*) const override; - virtual bool hasSameFontColor(RenderObject*) const override; - virtual bool hasSameStyle(RenderObject*) const override; - virtual bool hasUnderline() const override; + bool isSelected() const override; + bool isFocused() const override; + bool isLoaded() const override; + bool isOffScreen() const override; + bool isUnvisited() const override; + bool isVisited() const override; + bool isLinked() const override; + bool hasBoldFont() const override; + bool hasItalicFont() const override; + bool hasPlainText() const override; + bool hasSameFont(RenderObject*) const override; + bool hasSameFontColor(RenderObject*) const override; + bool hasSameStyle(RenderObject*) const override; + bool hasUnderline() const override; - virtual bool canSetTextRangeAttributes() const override; - virtual bool canSetValueAttribute() const override; - virtual bool canSetExpandedAttribute() const override; + bool canSetTextRangeAttributes() const override; + bool canSetExpandedAttribute() const override; - virtual void setAccessibleName(const AtomicString&) override; + void setAccessibleName(const AtomicString&) override; // Provides common logic used by all elements when determining isIgnored. - virtual AccessibilityObjectInclusion defaultObjectInclusion() const override; + AccessibilityObjectInclusion defaultObjectInclusion() const override; - virtual int layoutCount() const override; - virtual double estimatedLoadingProgress() const override; + int layoutCount() const override; + double estimatedLoadingProgress() const override; - virtual AccessibilityObject* firstChild() const override; - virtual AccessibilityObject* lastChild() const override; - virtual AccessibilityObject* previousSibling() const override; - virtual AccessibilityObject* nextSibling() const override; - virtual AccessibilityObject* parentObject() const override; - virtual AccessibilityObject* parentObjectIfExists() const override; - virtual AccessibilityObject* observableObject() const override; - virtual void linkedUIElements(AccessibilityChildrenVector&) const override; - virtual bool exposesTitleUIElement() const override; - virtual AccessibilityObject* titleUIElement() const override; - virtual AccessibilityObject* correspondingControlForLabelElement() const override; - virtual AccessibilityObject* correspondingLabelForControlElement() const override; + AccessibilityObject* firstChild() const override; + AccessibilityObject* lastChild() const override; + AccessibilityObject* previousSibling() const override; + AccessibilityObject* nextSibling() const override; + AccessibilityObject* parentObject() const override; + AccessibilityObject* parentObjectIfExists() const override; + AccessibilityObject* observableObject() const override; + void linkedUIElements(AccessibilityChildrenVector&) const override; + bool exposesTitleUIElement() const override; + AccessibilityObject* titleUIElement() const override; + AccessibilityObject* correspondingControlForLabelElement() const override; + AccessibilityObject* correspondingLabelForControlElement() const override; - virtual void ariaOwnsElements(AccessibilityChildrenVector&) const override; - virtual bool supportsARIAOwns() const override; - virtual bool isPresentationalChildOfAriaRole() const override; - virtual bool ariaRoleHasPresentationalChildren() const override; + bool supportsARIAOwns() const override; + bool isPresentationalChildOfAriaRole() const override; + bool ariaRoleHasPresentationalChildren() const override; // Should be called on the root accessibility object to kick off a hit test. - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; + AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; - virtual Element* anchorElement() const override; + Element* anchorElement() const override; - virtual LayoutRect boundingBoxRect() const override; - virtual LayoutRect elementRect() const override; - virtual IntPoint clickPoint() override; + LayoutRect boundingBoxRect() const override; + LayoutRect elementRect() const override; + IntPoint clickPoint() override; void setRenderer(RenderObject*); - virtual RenderObject* renderer() const override { return m_renderer; } + RenderObject* renderer() const override { return m_renderer; } RenderBoxModelObject* renderBoxModelObject() const; - virtual Node* node() const override; + Node* node() const override; - virtual Document* document() const override; + Document* document() const override; RenderView* topRenderer() const; RenderTextControl* textControl() const; HTMLLabelElement* labelElementContainer() const; - virtual URL url() const override; - virtual PlainTextRange selectedTextRange() const override; - virtual VisibleSelection selection() const override; - virtual String stringValue() const override; - virtual String helpText() const override; - virtual String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; - virtual String text() const override; - virtual int textLength() const override; - virtual String selectedText() const override; - virtual const AtomicString& accessKey() const override; + URL url() const override; + PlainTextRange selectedTextRange() const override; + VisibleSelection selection() const override; + String stringValue() const override; + String helpText() const override; + String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; + String text() const override; + int textLength() const override; + String selectedText() const override; + const AtomicString& accessKey() const override; virtual const String& actionVerb() const; - virtual Widget* widget() const override; - virtual Widget* widgetForAttachmentView() const override; + Widget* widget() const override; + Widget* widgetForAttachmentView() const override; virtual void getDocumentLinks(AccessibilityChildrenVector&); - virtual FrameView* documentFrameView() const override; + FrameView* documentFrameView() const override; - virtual void clearChildren() override; - virtual void updateChildrenIfNecessary() override; + void clearChildren() override; + void updateChildrenIfNecessary() override; - virtual void setFocused(bool) override; - virtual void setSelectedTextRange(const PlainTextRange&) override; - virtual void setValue(const String&) override; - virtual void setSelectedRows(AccessibilityChildrenVector&) override; - virtual AccessibilityOrientation orientation() const override; + void setFocused(bool) override; + void setSelectedTextRange(const PlainTextRange&) override; + void setValue(const String&) override; + void setSelectedRows(AccessibilityChildrenVector&) override; + AccessibilityOrientation orientation() const override; - virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; - virtual void textChanged() override; - virtual void addChildren() override; - virtual bool canHaveChildren() const override; - virtual void selectedChildren(AccessibilityChildrenVector&) override; - virtual void visibleChildren(AccessibilityChildrenVector&) override; - virtual void tabChildren(AccessibilityChildrenVector&) override; - virtual bool shouldFocusActiveDescendant() const override; + void detach(AccessibilityDetachmentType, AXObjectCache*) override; + void textChanged() override; + void addChildren() override; + bool canHaveChildren() const override; + bool canHaveSelectedChildren() const override; + void selectedChildren(AccessibilityChildrenVector&) override; + void visibleChildren(AccessibilityChildrenVector&) override; + void tabChildren(AccessibilityChildrenVector&) override; + bool shouldFocusActiveDescendant() const override; bool shouldNotifyActiveDescendant() const; - virtual AccessibilityObject* activeDescendant() const override; - virtual void handleActiveDescendantChanged() override; - virtual void handleAriaExpandedChanged() override; + AccessibilityObject* activeDescendant() const override; + void handleActiveDescendantChanged() override; + void handleAriaExpandedChanged() override; - virtual VisiblePositionRange visiblePositionRange() const override; - virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const override; - virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const override; - virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const override; - virtual bool supportsARIAFlowTo() const override; - virtual void ariaFlowToElements(AccessibilityChildrenVector&) const override; - virtual bool supportsARIADescribedBy() const override; - virtual void ariaDescribedByElements(AccessibilityChildrenVector&) const override; - virtual bool ariaHasPopup() const override; + VisiblePositionRange visiblePositionRange() const override; + VisiblePositionRange visiblePositionRangeForLine(unsigned) const override; + IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const override; + IntRect boundsForRange(const RefPtr<Range>) const override; + IntRect boundsForRects(LayoutRect&, LayoutRect&, RefPtr<Range>) const; + void setSelectedVisiblePositionRange(const VisiblePositionRange&) const override; + bool isVisiblePositionRangeInDifferentDocument(const VisiblePositionRange&) const; + bool ariaHasPopup() const override; - virtual bool supportsARIADropping() const override; - virtual bool supportsARIADragging() const override; - virtual bool isARIAGrabbed() override; - virtual void determineARIADropEffects(Vector<String>&) override; + bool supportsARIADropping() const override; + bool supportsARIADragging() const override; + bool isARIAGrabbed() override; + void determineARIADropEffects(Vector<String>&) override; - virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const override; - virtual VisiblePosition visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const override; - virtual int index(const VisiblePosition&) const override; + VisiblePosition visiblePositionForPoint(const IntPoint&) const override; + VisiblePosition visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const override; + int index(const VisiblePosition&) const override; - virtual VisiblePosition visiblePositionForIndex(int) const override; - virtual int indexForVisiblePosition(const VisiblePosition&) const override; + VisiblePosition visiblePositionForIndex(int) const override; + int indexForVisiblePosition(const VisiblePosition&) const override; - virtual void lineBreaks(Vector<int>&) const override; - virtual PlainTextRange doAXRangeForLine(unsigned) const override; - virtual PlainTextRange doAXRangeForIndex(unsigned) const override; + void lineBreaks(Vector<int>&) const override; + PlainTextRange doAXRangeForLine(unsigned) const override; + PlainTextRange doAXRangeForIndex(unsigned) const override; - virtual String doAXStringForRange(const PlainTextRange&) const override; - virtual IntRect doAXBoundsForRange(const PlainTextRange&) const override; + String doAXStringForRange(const PlainTextRange&) const override; + IntRect doAXBoundsForRange(const PlainTextRange&) const override; + IntRect doAXBoundsForRangeUsingCharacterOffset(const PlainTextRange&) const override; - virtual String stringValueForMSAA() const override; - virtual String stringRoleForMSAA() const override; - virtual String nameForMSAA() const override; - virtual String descriptionForMSAA() const override; - virtual AccessibilityRole roleValueForMSAA() const override; + String stringValueForMSAA() const override; + String stringRoleForMSAA() const override; + String nameForMSAA() const override; + String descriptionForMSAA() const override; + AccessibilityRole roleValueForMSAA() const override; - virtual String passwordFieldValue() const override; + String passwordFieldValue() const override; protected: - RenderObject* m_renderer; - + explicit AccessibilityRenderObject(RenderObject*); void setRenderObject(RenderObject* renderer) { m_renderer = renderer; } bool needsToUpdateChildren() const { return m_childrenDirty; } - virtual ScrollableArea* getScrollableAreaIfScrollable() const override; - virtual void scrollTo(const IntPoint&) const override; + ScrollableArea* getScrollableAreaIfScrollable() const override; + void scrollTo(const IntPoint&) const override; - virtual bool isDetached() const override { return !m_renderer; } + bool isDetached() const override { return !m_renderer; } - virtual AccessibilityRole determineAccessibilityRole() override; - virtual bool computeAccessibilityIsIgnored() const override; + AccessibilityRole determineAccessibilityRole() override; + bool computeAccessibilityIsIgnored() const override; + +#if ENABLE(MATHML) + virtual bool isIgnoredElementWithinMathTree() const; +#endif + + RenderObject* m_renderer; private: + bool isAccessibilityRenderObject() const final { return true; } void ariaListboxSelectedChildren(AccessibilityChildrenVector&); void ariaListboxVisibleChildren(AccessibilityChildrenVector&); bool isAllowedChildOfTree() const; bool hasTextAlternative() const; String positionalDescriptionForMSAA() const; - PlainTextRange ariaSelectedTextRange() const; + PlainTextRange documentBasedSelectedTextRange() const; Element* rootEditableElementForPosition(const Position&) const; bool nodeIsTextControl(const Node*) const; - virtual void setNeedsToUpdateChildren() override { m_childrenDirty = true; } - virtual Path elementPath() const override; + void setNeedsToUpdateChildren() override { m_childrenDirty = true; } + Path elementPath() const override; bool isTabItemSelected() const; LayoutRect checkboxOrRadioRect() const; void addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const; + void addRadioButtonGroupChildren(AccessibilityObject*, AccessibilityChildrenVector&) const; AccessibilityObject* internalLinkElement() const; AccessibilityObject* accessibilityImageMapHitTest(HTMLAreaElement*, const IntPoint&) const; AccessibilityObject* accessibilityParentForImageMap(HTMLMapElement*) const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; + AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; - bool renderObjectIsObservable(RenderObject*) const; + bool renderObjectIsObservable(RenderObject&) const; RenderObject* renderParentObject() const; bool isDescendantOfElementType(const QualifiedName& tagName) const; bool isSVGImage() const; void detachRemoteSVGRoot(); - AccessibilitySVGRoot* remoteSVGRootElement() const; + enum CreationChoice { Create, Retrieve }; + AccessibilitySVGRoot* remoteSVGRootElement(CreationChoice createIfNecessary) const; AccessibilityObject* remoteSVGElementHitTest(const IntPoint&) const; void offsetBoundingBoxForRemoteSVGElement(LayoutRect&) const; - virtual bool supportsPath() const override; + bool supportsPath() const override; void addHiddenChildren(); void addTextFieldChildren(); @@ -261,79 +257,30 @@ private: void addCanvasChildren(); void addAttachmentChildren(); void addRemoteSVGChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void updateAttachmentViewParents(); #endif - + String expandedTextValue() const override; + bool supportsExpandedTextValue() const override; + void updateRoleAfterChildrenCreation(); + void ariaSelectedRows(AccessibilityChildrenVector&); bool elementAttributeValue(const QualifiedName&) const; void setElementAttributeValue(const QualifiedName&, bool); - virtual ESpeak speakProperty() const override; - - virtual const AtomicString& ariaLiveRegionStatus() const override; - virtual const AtomicString& ariaLiveRegionRelevant() const override; - virtual bool ariaLiveRegionAtomic() const override; - virtual bool ariaLiveRegionBusy() const override; - - bool inheritsPresentationalRole() const; - -#if ENABLE(MATHML) - // All math elements return true for isMathElement(). - virtual bool isMathElement() const override; - virtual bool isMathFraction() const override; - virtual bool isMathFenced() const override; - virtual bool isMathSubscriptSuperscript() const override; - virtual bool isMathRow() const override; - virtual bool isMathUnderOver() const override; - virtual bool isMathRoot() const override; - virtual bool isMathSquareRoot() const override; - virtual bool isMathText() const override; - virtual bool isMathNumber() const override; - virtual bool isMathOperator() const override; - virtual bool isMathFenceOperator() const override; - virtual bool isMathSeparatorOperator() const override; - virtual bool isMathIdentifier() const override; - virtual bool isMathTable() const override; - virtual bool isMathTableRow() const override; - virtual bool isMathTableCell() const override; - virtual bool isMathMultiscript() const override; - - // Generic components. - virtual AccessibilityObject* mathBaseObject() override; + ESpeak speakProperty() const override; - // Root components. - virtual AccessibilityObject* mathRadicandObject() override; - virtual AccessibilityObject* mathRootIndexObject() override; - - // Fraction components. - virtual AccessibilityObject* mathNumeratorObject() override; - virtual AccessibilityObject* mathDenominatorObject() override; + const String ariaLiveRegionStatus() const override; + const AtomicString& ariaLiveRegionRelevant() const override; + bool ariaLiveRegionAtomic() const override; + bool isBusy() const override; - // Under over components. - virtual AccessibilityObject* mathUnderObject() override; - virtual AccessibilityObject* mathOverObject() override; - - // Subscript/superscript components. - virtual AccessibilityObject* mathSubscriptObject() override; - virtual AccessibilityObject* mathSuperscriptObject() override; - - // Fenced components. - virtual String mathFencedOpenString() const override; - virtual String mathFencedCloseString() const override; - virtual int mathLineThickness() const override; + bool inheritsPresentationalRole() const override; - // Multiscripts components. - virtual void mathPrescripts(AccessibilityMathMultiscriptPairs&) override; - virtual void mathPostscripts(AccessibilityMathMultiscriptPairs&) override; - - bool isIgnoredElementWithinMathTree() const; -#endif + bool shouldGetTextFromNode(AccessibilityTextUnderElementMode) const; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityRenderObject, isAccessibilityRenderObject()) - } // namespace WebCore -#endif // AccessibilityRenderObject_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityRenderObject, isAccessibilityRenderObject()) diff --git a/Source/WebCore/accessibility/AccessibilitySVGElement.cpp b/Source/WebCore/accessibility/AccessibilitySVGElement.cpp new file mode 100644 index 000000000..24c2a79d7 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilitySVGElement.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2016 Igalia, S.L. + * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilitySVGElement.h" + +#include "AXObjectCache.h" +#include "ElementIterator.h" +#include "HTMLNames.h" +#include "Language.h" +#include "RenderIterator.h" +#include "RenderText.h" +#include "SVGAElement.h" +#include "SVGDescElement.h" +#include "SVGGElement.h" +#include "SVGTitleElement.h" +#include "SVGUseElement.h" +#include "XLinkNames.h" + +namespace WebCore { + +AccessibilitySVGElement::AccessibilitySVGElement(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilitySVGElement::~AccessibilitySVGElement() +{ +} + +Ref<AccessibilitySVGElement> AccessibilitySVGElement::create(RenderObject* renderer) +{ + return adoptRef(*new AccessibilitySVGElement(renderer)); +} + +AccessibilityObject* AccessibilitySVGElement::targetForUseElement() const +{ + if (!is<SVGUseElement>(element())) + return nullptr; + + SVGUseElement& use = downcast<SVGUseElement>(*element()); + String href = use.href(); + if (href.isEmpty()) + href = getAttribute(HTMLNames::hrefAttr); + + Element* target = SVGURIReference::targetElementFromIRIString(href, use.document()); + if (target) + return axObjectCache()->getOrCreate(target); + + return nullptr; +} + +template <typename ChildrenType> +Element* AccessibilitySVGElement::childElementWithMatchingLanguage(ChildrenType& children) const +{ + String languageCode = language(); + if (languageCode.isEmpty()) + languageCode = defaultLanguage(); + + // The best match for a group of child SVG2 'title' or 'desc' elements may be the one + // which lacks a 'lang' attribute value. However, indexOfBestMatchingLanguageInList() + // currently bases its decision on non-empty strings. Furthermore, we cannot count on + // that child element having a given position. So we'll look for such an element while + // building the language list and save it as our fallback. + + Element* fallback = nullptr; + Vector<String> childLanguageCodes; + Vector<Element*> elements; + for (auto& child : children) { + String lang = child.attributeWithoutSynchronization(SVGNames::langAttr); + childLanguageCodes.append(lang); + elements.append(&child); + + // The current draft of the SVG2 spec states if there are multiple equally-valid + // matches, the first match should be used. + if (lang.isEmpty() && !fallback) + fallback = &child; + } + + bool exactMatch; + size_t index = indexOfBestMatchingLanguageInList(languageCode, childLanguageCodes, exactMatch); + if (index < childLanguageCodes.size()) + return elements[index]; + + return fallback; +} + +void AccessibilitySVGElement::accessibilityText(Vector<AccessibilityText>& textOrder) +{ + String description = accessibilityDescription(); + if (!description.isEmpty()) + textOrder.append(AccessibilityText(description, AlternativeText)); + + String helptext = helpText(); + if (!helptext.isEmpty()) + textOrder.append(AccessibilityText(helptext, HelpText)); +} + +String AccessibilitySVGElement::accessibilityDescription() const +{ + // According to the SVG Accessibility API Mappings spec, the order of priority is: + // 1. aria-labelledby + // 2. aria-label + // 3. a direct child title element (selected according to language) + // 4. xlink:title attribute + // 5. for a use element, the accessible name calculated for the re-used content + // 6. for text container elements, the text content + + String ariaDescription = ariaAccessibilityDescription(); + if (!ariaDescription.isEmpty()) + return ariaDescription; + + auto titleElements = childrenOfType<SVGTitleElement>(*element()); + if (auto titleChild = childElementWithMatchingLanguage(titleElements)) + return titleChild->textContent(); + + if (is<SVGAElement>(element())) { + String xlinkTitle = element()->attributeWithoutSynchronization(XLinkNames::titleAttr); + if (!xlinkTitle.isEmpty()) + return xlinkTitle; + } + + if (m_renderer->isSVGText()) { + AccessibilityTextUnderElementMode mode; + String text = textUnderElement(mode); + if (!text.isEmpty()) + return text; + } + + if (is<SVGUseElement>(element())) { + if (AccessibilityObject* target = targetForUseElement()) + return target->accessibilityDescription(); + } + + // FIXME: This is here to not break the svg-image.html test. But 'alt' is not + // listed as a supported attribute of the 'image' element in the SVG spec: + // https://www.w3.org/TR/SVG/struct.html#ImageElement + if (m_renderer->isSVGImage()) { + const AtomicString& alt = getAttribute(HTMLNames::altAttr); + if (!alt.isNull()) + return alt; + } + + return String(); +} + +String AccessibilitySVGElement::helpText() const +{ + // According to the SVG Accessibility API Mappings spec, the order of priority is: + // 1. aria-describedby + // 2. a direct child desc element + // 3. for a use element, the accessible description calculated for the re-used content + // 4. for text container elements, the text content, if not used for the name + // 5. a direct child title element that provides a tooltip, if not used for the name + + String describedBy = ariaDescribedByAttribute(); + if (!describedBy.isEmpty()) + return describedBy; + + auto descriptionElements = childrenOfType<SVGDescElement>(*element()); + if (auto descriptionChild = childElementWithMatchingLanguage(descriptionElements)) + return descriptionChild->textContent(); + + if (is<SVGUseElement>(element())) { + AccessibilityObject* target = targetForUseElement(); + if (target) + return target->helpText(); + } + + String description = accessibilityDescription(); + + if (m_renderer->isSVGText()) { + AccessibilityTextUnderElementMode mode; + String text = textUnderElement(mode); + if (!text.isEmpty() && text != description) + return text; + } + + auto titleElements = childrenOfType<SVGTitleElement>(*element()); + if (auto titleChild = childElementWithMatchingLanguage(titleElements)) { + if (titleChild->textContent() != description) + return titleChild->textContent(); + } + + return String(); +} + +bool AccessibilitySVGElement::computeAccessibilityIsIgnored() const +{ + // According to the SVG Accessibility API Mappings spec, items should be excluded if: + // * They would be excluded according to the Core Accessibility API Mappings. + // * They are neither perceivable nor interactive. + // * Their first mappable role is presentational, unless they have a global ARIA + // attribute (covered by Core AAM) or at least one 'title' or 'desc' child element. + // * They have an ancestor with Children Presentational: True (covered by Core AAM) + + AccessibilityObjectInclusion decision = defaultObjectInclusion(); + if (decision == IgnoreObject) + return true; + + if (m_renderer->isSVGHiddenContainer()) + return true; + + // The SVG AAM states objects with at least one 'title' or 'desc' element MUST be included. + // At this time, the presence of a matching 'lang' attribute is not mentioned in the spec. + for (const auto& child : childrenOfType<SVGElement>(*element())) { + if ((is<SVGTitleElement>(child) || is<SVGDescElement>(child))) + return false; + } + + if (roleValue() == PresentationalRole || inheritsPresentationalRole()) + return true; + + if (ariaRoleAttribute() != UnknownRole) + return false; + + // The SVG AAM states text elements should also be included, if they have content. + if (m_renderer->isSVGText() || m_renderer->isSVGTextPath()) { + for (auto& child : childrenOfType<RenderText>(downcast<RenderElement>(*m_renderer))) { + if (!child.isAllCollapsibleWhitespace()) + return false; + } + } + + // SVG shapes should not be included unless there's a concrete reason for inclusion. + // https://rawgit.com/w3c/aria/master/svg-aam/svg-aam.html#exclude_elements + if (m_renderer->isSVGShape()) + return !(hasAttributesRequiredForInclusion() || canSetFocusAttribute() || element()->hasEventListeners()); + + return AccessibilityRenderObject::computeAccessibilityIsIgnored(); +} + +bool AccessibilitySVGElement::inheritsPresentationalRole() const +{ + if (canSetFocusAttribute()) + return false; + + AccessibilityRole role = roleValue(); + if (role != SVGTextPathRole && role != SVGTSpanRole) + return false; + + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (is<AccessibilityRenderObject>(*parent) && parent->element()->hasTagName(SVGNames::textTag)) + return parent->roleValue() == PresentationalRole; + } + + return false; +} + +AccessibilityRole AccessibilitySVGElement::determineAriaRoleAttribute() const +{ + AccessibilityRole role = AccessibilityRenderObject::determineAriaRoleAttribute(); + if (role != PresentationalRole) + return role; + + // The presence of a 'title' or 'desc' child element trumps PresentationalRole. + // https://lists.w3.org/Archives/Public/public-svg-a11y/2016Apr/0016.html + // At this time, the presence of a matching 'lang' attribute is not mentioned. + for (const auto& child : childrenOfType<SVGElement>(*element())) { + if ((is<SVGTitleElement>(child) || is<SVGDescElement>(child))) + return UnknownRole; + } + + return role; +} + +AccessibilityRole AccessibilitySVGElement::determineAccessibilityRole() +{ + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; + + Element* svgElement = element(); + + if (m_renderer->isSVGShape() || m_renderer->isSVGPath() || m_renderer->isSVGImage() || is<SVGUseElement>(svgElement)) + return ImageRole; + if (m_renderer->isSVGForeignObject() || is<SVGGElement>(svgElement)) + return GroupRole; + if (m_renderer->isSVGText()) + return SVGTextRole; + if (m_renderer->isSVGTextPath()) + return SVGTextPathRole; + if (m_renderer->isSVGTSpan()) + return SVGTSpanRole; + if (is<SVGAElement>(svgElement)) + return WebCoreLinkRole; + + return AccessibilityRenderObject::determineAccessibilityRole(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilitySVGElement.h b/Source/WebCore/accessibility/AccessibilitySVGElement.h new file mode 100644 index 000000000..0d1d28ff1 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilitySVGElement.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 Igalia, S.L. + * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilitySVGElement : public AccessibilityRenderObject { + +public: + static Ref<AccessibilitySVGElement> create(RenderObject*); + virtual ~AccessibilitySVGElement(); + + String accessibilityDescription() const final; + String helpText() const final; + +protected: + explicit AccessibilitySVGElement(RenderObject*); + +private: + void accessibilityText(Vector<AccessibilityText>&) final; + AccessibilityRole determineAccessibilityRole() final; + AccessibilityRole determineAriaRoleAttribute() const final; + bool inheritsPresentationalRole() const final; + bool isAccessibilitySVGElement() const final { return true; } + bool computeAccessibilityIsIgnored() const final; + + AccessibilityObject* targetForUseElement() const; + + template <typename ChildrenType> + Element* childElementWithMatchingLanguage(ChildrenType&) const; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySVGElement, isAccessibilitySVGElement()) diff --git a/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp b/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp index 08a2d589d..3dab320cb 100644 --- a/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp +++ b/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -34,8 +34,8 @@ namespace WebCore { AccessibilitySVGRoot::AccessibilitySVGRoot(RenderObject* renderer) - : AccessibilityRenderObject(renderer) - , m_parent(0) + : AccessibilitySVGElement(renderer) + , m_parent(nullptr) { } @@ -43,9 +43,9 @@ AccessibilitySVGRoot::~AccessibilitySVGRoot() { } -PassRefPtr<AccessibilitySVGRoot> AccessibilitySVGRoot::create(RenderObject* renderer) +Ref<AccessibilitySVGRoot> AccessibilitySVGRoot::create(RenderObject* renderer) { - return adoptRef(new AccessibilitySVGRoot(renderer)); + return adoptRef(*new AccessibilitySVGRoot(renderer)); } AccessibilityObject* AccessibilitySVGRoot::parentObject() const @@ -55,7 +55,7 @@ AccessibilityObject* AccessibilitySVGRoot::parentObject() const if (m_parent) return m_parent; - return AccessibilityRenderObject::parentObject(); + return AccessibilitySVGElement::parentObject(); } diff --git a/Source/WebCore/accessibility/AccessibilitySVGRoot.h b/Source/WebCore/accessibility/AccessibilitySVGRoot.h index f4d092420..7511c81c7 100644 --- a/Source/WebCore/accessibility/AccessibilitySVGRoot.h +++ b/Source/WebCore/accessibility/AccessibilitySVGRoot.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,32 +26,29 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilitySVGRoot_h -#define AccessibilitySVGRoot_h +#pragma once -#include "AccessibilityRenderObject.h" +#include "AccessibilitySVGElement.h" namespace WebCore { -class AccessibilitySVGRoot : public AccessibilityRenderObject { - -protected: - explicit AccessibilitySVGRoot(RenderObject*); +class AccessibilitySVGRoot final : public AccessibilitySVGElement { public: - static PassRefPtr<AccessibilitySVGRoot> create(RenderObject*); + static Ref<AccessibilitySVGRoot> create(RenderObject*); virtual ~AccessibilitySVGRoot(); void setParent(AccessibilityObject* parent) { m_parent = parent; } - + private: - AccessibilityObject* m_parent; + explicit AccessibilitySVGRoot(RenderObject*); - virtual AccessibilityObject* parentObject() const override; - virtual bool isAccessibilitySVGRoot() const override { return true; } -}; + AccessibilityObject* parentObject() const override; + bool isAccessibilitySVGRoot() const override { return true; } -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilitySVGRoot, isAccessibilitySVGRoot()) + AccessibilityObject* m_parent; + AccessibilityRole roleValue() const override { return GroupRole; } +}; } // namespace WebCore -#endif // AccessibilitySVGRoot_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySVGRoot, isAccessibilitySVGRoot()) diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.cpp b/Source/WebCore/accessibility/AccessibilityScrollView.cpp index f158506b3..ed5aa5844 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollView.cpp +++ b/Source/WebCore/accessibility/AccessibilityScrollView.cpp @@ -51,12 +51,12 @@ AccessibilityScrollView::~AccessibilityScrollView() void AccessibilityScrollView::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { AccessibilityObject::detach(detachmentType, cache); - m_scrollView = 0; + m_scrollView = nullptr; } -PassRefPtr<AccessibilityScrollView> AccessibilityScrollView::create(ScrollView* view) +Ref<AccessibilityScrollView> AccessibilityScrollView::create(ScrollView* view) { - return adoptRef(new AccessibilityScrollView(view)); + return adoptRef(*new AccessibilityScrollView(view)); } AccessibilityObject* AccessibilityScrollView::scrollBar(AccessibilityOrientation orientation) @@ -64,13 +64,15 @@ AccessibilityObject* AccessibilityScrollView::scrollBar(AccessibilityOrientation updateScrollbars(); switch (orientation) { + // ARIA 1.1 Elements with the role scrollbar have an implicit aria-orientation value of vertical. + case AccessibilityOrientationUndefined: case AccessibilityOrientationVertical: - return m_verticalScrollbar ? m_verticalScrollbar.get() : 0; + return m_verticalScrollbar ? m_verticalScrollbar.get() : nullptr; case AccessibilityOrientationHorizontal: - return m_horizontalScrollbar ? m_horizontalScrollbar.get() : 0; + return m_horizontalScrollbar ? m_horizontalScrollbar.get() : nullptr; } - return 0; + return nullptr; } // If this is WebKit1 then the native scroll view needs to return the @@ -106,13 +108,11 @@ void AccessibilityScrollView::setFocused(bool focused) void AccessibilityScrollView::updateChildrenIfNecessary() { - if (m_childrenDirty) - clearChildren(); - - if (!m_haveChildren) - addChildren(); - - updateScrollbars(); + // Always update our children when asked for them so that we don't inadvertently cache them after + // a new web area has been created for this scroll view (like when moving back and forth through history). + // Since a ScrollViews children will always be relatively small and limited this should not be a performance problem. + clearChildren(); + addChildren(); } void AccessibilityScrollView::updateScrollbars() @@ -124,14 +124,14 @@ void AccessibilityScrollView::updateScrollbars() m_horizontalScrollbar = addChildScrollbar(m_scrollView->horizontalScrollbar()); else if (!m_scrollView->horizontalScrollbar() && m_horizontalScrollbar) { removeChildScrollbar(m_horizontalScrollbar.get()); - m_horizontalScrollbar = 0; + m_horizontalScrollbar = nullptr; } if (m_scrollView->verticalScrollbar() && !m_verticalScrollbar) m_verticalScrollbar = addChildScrollbar(m_scrollView->verticalScrollbar()); else if (!m_scrollView->verticalScrollbar() && m_verticalScrollbar) { removeChildScrollbar(m_verticalScrollbar.get()); - m_verticalScrollbar = 0; + m_verticalScrollbar = nullptr; } } @@ -147,19 +147,23 @@ void AccessibilityScrollView::removeChildScrollbar(AccessibilityObject* scrollba AccessibilityScrollbar* AccessibilityScrollView::addChildScrollbar(Scrollbar* scrollbar) { if (!scrollbar) - return 0; + return nullptr; - AccessibilityScrollbar* scrollBarObject = toAccessibilityScrollbar(axObjectCache()->getOrCreate(scrollbar)); - scrollBarObject->setParent(this); - m_children.append(scrollBarObject); - return scrollBarObject; + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + auto& scrollBarObject = downcast<AccessibilityScrollbar>(*cache->getOrCreate(scrollbar)); + scrollBarObject.setParent(this); + m_children.append(&scrollBarObject); + return &scrollBarObject; } void AccessibilityScrollView::clearChildren() { AccessibilityObject::clearChildren(); - m_verticalScrollbar = 0; - m_horizontalScrollbar = 0; + m_verticalScrollbar = nullptr; + m_horizontalScrollbar = nullptr; } bool AccessibilityScrollView::computeAccessibilityIsIgnored() const @@ -185,21 +189,24 @@ void AccessibilityScrollView::addChildren() AccessibilityObject* AccessibilityScrollView::webAreaObject() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - Document* doc = toFrameView(m_scrollView)->frame().document(); - if (!doc || !doc->hasLivingRenderTree()) - return 0; + Document* document = downcast<FrameView>(*m_scrollView).frame().document(); + if (!document || !document->hasLivingRenderTree()) + return nullptr; - return axObjectCache()->getOrCreate(doc); + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(document); + + return nullptr; } AccessibilityObject* AccessibilityScrollView::accessibilityHitTest(const IntPoint& point) const { AccessibilityObject* webArea = webAreaObject(); if (!webArea) - return 0; + return nullptr; if (m_horizontalScrollbar && m_horizontalScrollbar->elementRect().contains(point)) return m_horizontalScrollbar.get(); @@ -214,39 +221,52 @@ LayoutRect AccessibilityScrollView::elementRect() const if (!m_scrollView) return LayoutRect(); - return m_scrollView->frameRect(); + LayoutRect rect = m_scrollView->frameRect(); + float topContentInset = m_scrollView->topContentInset(); + // Top content inset pushes the frame down and shrinks it. + rect.move(0, topContentInset); + rect.contract(0, topContentInset); + return rect; } FrameView* AccessibilityScrollView::documentFrameView() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - return toFrameView(m_scrollView); + return downcast<FrameView>(m_scrollView); } AccessibilityObject* AccessibilityScrollView::parentObject() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; - - HTMLFrameOwnerElement* owner = toFrameView(m_scrollView)->frame().ownerElement(); + if (!is<FrameView>(m_scrollView)) + return nullptr; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + HTMLFrameOwnerElement* owner = downcast<FrameView>(*m_scrollView).frame().ownerElement(); if (owner && owner->renderer()) - return axObjectCache()->getOrCreate(owner); + return cache->getOrCreate(owner); - return 0; + return nullptr; } AccessibilityObject* AccessibilityScrollView::parentObjectIfExists() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - HTMLFrameOwnerElement* owner = toFrameView(m_scrollView)->frame().ownerElement(); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + HTMLFrameOwnerElement* owner = downcast<FrameView>(m_scrollView)->frame().ownerElement(); if (owner && owner->renderer()) - return axObjectCache()->get(owner); + return cache->get(owner); - return 0; + return nullptr; } ScrollableArea* AccessibilityScrollView::getScrollableAreaIfScrollable() const diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.h b/Source/WebCore/accessibility/AccessibilityScrollView.h index 56be1a07e..0d76f664a 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollView.h +++ b/Source/WebCore/accessibility/AccessibilityScrollView.h @@ -23,8 +23,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityScrollView_h -#define AccessibilityScrollView_h +#pragma once #include "AccessibilityObject.h" @@ -34,47 +33,46 @@ class AccessibilityScrollbar; class Scrollbar; class ScrollView; -class AccessibilityScrollView : public AccessibilityObject { +class AccessibilityScrollView final : public AccessibilityObject { public: - static PassRefPtr<AccessibilityScrollView> create(ScrollView*); - virtual AccessibilityRole roleValue() const override { return ScrollAreaRole; } + static Ref<AccessibilityScrollView> create(ScrollView*); + AccessibilityRole roleValue() const override { return ScrollAreaRole; } ScrollView* scrollView() const { return m_scrollView; } virtual ~AccessibilityScrollView(); - virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; + void detach(AccessibilityDetachmentType, AXObjectCache*) override; + + AccessibilityObject* webAreaObject() const; -protected: - virtual ScrollableArea* getScrollableAreaIfScrollable() const override; - virtual void scrollTo(const IntPoint&) const override; - private: explicit AccessibilityScrollView(ScrollView*); - virtual bool computeAccessibilityIsIgnored() const override; - virtual bool isAccessibilityScrollView() const override { return true; } - virtual bool isEnabled() const override { return true; } + ScrollableArea* getScrollableAreaIfScrollable() const override; + void scrollTo(const IntPoint&) const override; + bool computeAccessibilityIsIgnored() const override; + bool isAccessibilityScrollView() const override { return true; } + bool isEnabled() const override { return true; } - virtual bool isAttachment() const override; - virtual Widget* widgetForAttachmentView() const override; + bool isAttachment() const override; + Widget* widgetForAttachmentView() const override; - virtual AccessibilityObject* scrollBar(AccessibilityOrientation) override; - virtual void addChildren() override; - virtual void clearChildren() override; - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; - virtual void updateChildrenIfNecessary() override; - virtual void setNeedsToUpdateChildren() override { m_childrenDirty = true; } + AccessibilityObject* scrollBar(AccessibilityOrientation) override; + void addChildren() override; + void clearChildren() override; + AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; + void updateChildrenIfNecessary() override; + void setNeedsToUpdateChildren() override { m_childrenDirty = true; } void updateScrollbars(); - virtual void setFocused(bool) override; - virtual bool canSetFocusAttribute() const override; - virtual bool isFocused() const override; + void setFocused(bool) override; + bool canSetFocusAttribute() const override; + bool isFocused() const override; - virtual FrameView* documentFrameView() const override; - virtual LayoutRect elementRect() const override; - virtual AccessibilityObject* parentObject() const override; - virtual AccessibilityObject* parentObjectIfExists() const override; + FrameView* documentFrameView() const override; + LayoutRect elementRect() const override; + AccessibilityObject* parentObject() const override; + AccessibilityObject* parentObjectIfExists() const override; - AccessibilityObject* webAreaObject() const; - virtual AccessibilityObject* firstChild() const override { return webAreaObject(); } + AccessibilityObject* firstChild() const override { return webAreaObject(); } AccessibilityScrollbar* addChildScrollbar(Scrollbar*); void removeChildScrollbar(AccessibilityObject*); @@ -83,9 +81,7 @@ private: RefPtr<AccessibilityObject> m_verticalScrollbar; bool m_childrenDirty; }; - -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityScrollView, isAccessibilityScrollView()) } // namespace WebCore -#endif // AccessibilityScrollView_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityScrollView, isAccessibilityScrollView()) diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.cpp b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp index bf6d042d7..a46d1b21f 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollbar.cpp +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -42,9 +42,9 @@ AccessibilityScrollbar::AccessibilityScrollbar(Scrollbar* scrollbar) ASSERT(scrollbar); } -PassRefPtr<AccessibilityScrollbar> AccessibilityScrollbar::create(Scrollbar* scrollbar) +Ref<AccessibilityScrollbar> AccessibilityScrollbar::create(Scrollbar* scrollbar) { - return adoptRef(new AccessibilityScrollbar(scrollbar)); + return adoptRef(*new AccessibilityScrollbar(scrollbar)); } LayoutRect AccessibilityScrollbar::elementRect() const @@ -59,21 +59,22 @@ Document* AccessibilityScrollbar::document() const { AccessibilityObject* parent = parentObject(); if (!parent) - return 0; + return nullptr; return parent->document(); } AccessibilityOrientation AccessibilityScrollbar::orientation() const { + // ARIA 1.1 Elements with the role scrollbar have an implicit aria-orientation value of vertical. if (!m_scrollbar) - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationVertical; if (m_scrollbar->orientation() == HorizontalScrollbar) return AccessibilityOrientationHorizontal; if (m_scrollbar->orientation() == VerticalScrollbar) return AccessibilityOrientationVertical; - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationVertical; } bool AccessibilityScrollbar::isEnabled() const @@ -96,11 +97,8 @@ void AccessibilityScrollbar::setValue(float value) if (!m_scrollbar) return; - if (!m_scrollbar->scrollableArea()) - return; - float newValue = value * m_scrollbar->maximum(); - m_scrollbar->scrollableArea()->scrollToOffsetWithoutAnimation(m_scrollbar->orientation(), newValue); + m_scrollbar->scrollableArea().scrollToOffsetWithoutAnimation(m_scrollbar->orientation(), newValue); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.h b/Source/WebCore/accessibility/AccessibilityScrollbar.h index c086a4162..3cc431a0d 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollbar.h +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityScrollbar_h -#define AccessibilityScrollbar_h +#pragma once #include "AccessibilityMockObject.h" @@ -35,35 +34,33 @@ namespace WebCore { class Scrollbar; -class AccessibilityScrollbar : public AccessibilityMockObject { +class AccessibilityScrollbar final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityScrollbar> create(Scrollbar*); + static Ref<AccessibilityScrollbar> create(Scrollbar*); Scrollbar* scrollbar() const { return m_scrollbar.get(); } private: explicit AccessibilityScrollbar(Scrollbar*); - virtual bool canSetValueAttribute() const override { return true; } - virtual bool canSetNumericValue() const override { return true; } + bool canSetValueAttribute() const override { return true; } + bool canSetNumericValue() const override { return true; } - virtual bool isAccessibilityScrollbar() const override { return true; } - virtual LayoutRect elementRect() const override; + bool isAccessibilityScrollbar() const override { return true; } + LayoutRect elementRect() const override; - virtual AccessibilityRole roleValue() const override { return ScrollBarRole; } - virtual AccessibilityOrientation orientation() const override; - virtual Document* document() const override; - virtual bool isEnabled() const override; + AccessibilityRole roleValue() const override { return ScrollBarRole; } + AccessibilityOrientation orientation() const override; + Document* document() const override; + bool isEnabled() const override; // Assumes float [0..1] - virtual void setValue(float) override; - virtual float valueForRange() const override; + void setValue(float) override; + float valueForRange() const override; RefPtr<Scrollbar> m_scrollbar; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityScrollbar, isAccessibilityScrollbar()) - } // namespace WebCore -#endif // AccessibilityScrollbar_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityScrollbar, isAccessibilityScrollbar()) diff --git a/Source/WebCore/accessibility/AccessibilitySlider.cpp b/Source/WebCore/accessibility/AccessibilitySlider.cpp index 6450bfdec..d7ba9e43d 100644 --- a/Source/WebCore/accessibility/AccessibilitySlider.cpp +++ b/Source/WebCore/accessibility/AccessibilitySlider.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -45,9 +45,9 @@ AccessibilitySlider::AccessibilitySlider(RenderObject* renderer) { } -PassRefPtr<AccessibilitySlider> AccessibilitySlider::create(RenderObject* renderer) +Ref<AccessibilitySlider> AccessibilitySlider::create(RenderObject* renderer) { - return adoptRef(new AccessibilitySlider(renderer)); + return adoptRef(*new AccessibilitySlider(renderer)); } AccessibilityOrientation AccessibilitySlider::orientation() const @@ -84,15 +84,15 @@ void AccessibilitySlider::addChildren() AXObjectCache* cache = m_renderer->document().axObjectCache(); - AccessibilitySliderThumb* thumb = toAccessibilitySliderThumb(cache->getOrCreate(SliderThumbRole)); - thumb->setParent(this); + auto& thumb = downcast<AccessibilitySliderThumb>(*cache->getOrCreate(SliderThumbRole)); + thumb.setParent(this); // Before actually adding the value indicator to the hierarchy, // allow the platform to make a final decision about it. - if (thumb->accessibilityIsIgnored()) - cache->remove(thumb->axObjectID()); + if (thumb.accessibilityIsIgnored()) + cache->remove(thumb.axObjectID()); else - m_children.append(thumb); + m_children.append(&thumb); } const AtomicString& AccessibilitySlider::getAttribute(const QualifiedName& attribute) const @@ -133,15 +133,12 @@ void AccessibilitySlider::setValue(const String& value) if (input->value() == value) return; - input->setValue(value); - - // Fire change event manually, as RenderSlider::setValueForPosition does. - input->dispatchFormControlChangeEvent(); + input->setValue(value, DispatchChangeEvent); } HTMLInputElement* AccessibilitySlider::inputElement() const { - return toHTMLInputElement(m_renderer->node()); + return downcast<HTMLInputElement>(m_renderer->node()); } @@ -149,9 +146,9 @@ AccessibilitySliderThumb::AccessibilitySliderThumb() { } -PassRefPtr<AccessibilitySliderThumb> AccessibilitySliderThumb::create() +Ref<AccessibilitySliderThumb> AccessibilitySliderThumb::create() { - return adoptRef(new AccessibilitySliderThumb()); + return adoptRef(*new AccessibilitySliderThumb()); } LayoutRect AccessibilitySliderThumb::elementRect() const @@ -162,7 +159,9 @@ LayoutRect AccessibilitySliderThumb::elementRect() const RenderObject* sliderRenderer = m_parent->renderer(); if (!sliderRenderer || !sliderRenderer->isSlider()) return LayoutRect(); - return toHTMLInputElement(sliderRenderer->node())->sliderThumbElement()->boundingBox(); + if (auto* thumbRenderer = downcast<RenderSlider>(*sliderRenderer).element().sliderThumbElement()->renderer()) + return thumbRenderer->absoluteBoundingBoxRect(); + return LayoutRect(); } bool AccessibilitySliderThumb::computeAccessibilityIsIgnored() const diff --git a/Source/WebCore/accessibility/AccessibilitySlider.h b/Source/WebCore/accessibility/AccessibilitySlider.h index d9529903d..4b20f7939 100644 --- a/Source/WebCore/accessibility/AccessibilitySlider.h +++ b/Source/WebCore/accessibility/AccessibilitySlider.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilitySlider_h -#define AccessibilitySlider_h +#pragma once #include "AccessibilityMockObject.h" #include "AccessibilityRenderObject.h" @@ -37,9 +36,8 @@ namespace WebCore { class HTMLInputElement; class AccessibilitySlider : public AccessibilityRenderObject { - public: - static PassRefPtr<AccessibilitySlider> create(RenderObject*); + static Ref<AccessibilitySlider> create(RenderObject*); virtual ~AccessibilitySlider() { } protected: @@ -47,45 +45,40 @@ protected: private: HTMLInputElement* inputElement() const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; + AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; - virtual AccessibilityRole roleValue() const override { return SliderRole; } - virtual bool isSlider() const override { return true; } - virtual bool isInputSlider() const override { return true; } - virtual bool isControl() const override { return true; } + AccessibilityRole roleValue() const override { return SliderRole; } + bool isSlider() const final { return true; } + bool isInputSlider() const override { return true; } + bool isControl() const override { return true; } - virtual void addChildren() override; + void addChildren() override; - virtual bool canSetValueAttribute() const override { return true; } + bool canSetValueAttribute() const override { return true; } const AtomicString& getAttribute(const QualifiedName& attribute) const; - virtual void setValue(const String&) override; - virtual float valueForRange() const override; - virtual float maxValueForRange() const override; - virtual float minValueForRange() const override; - virtual AccessibilityOrientation orientation() const override; + void setValue(const String&) override; + float valueForRange() const override; + float maxValueForRange() const override; + float minValueForRange() const override; + AccessibilityOrientation orientation() const override; }; -class AccessibilitySliderThumb : public AccessibilityMockObject { - +class AccessibilitySliderThumb final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySliderThumb> create(); + static Ref<AccessibilitySliderThumb> create(); virtual ~AccessibilitySliderThumb() { } - virtual bool isSliderThumb() const override final { return true; } - - virtual AccessibilityRole roleValue() const override { return SliderThumbRole; } - - virtual LayoutRect elementRect() const override; + AccessibilityRole roleValue() const override { return SliderThumbRole; } + LayoutRect elementRect() const override; private: AccessibilitySliderThumb(); - virtual bool computeAccessibilityIsIgnored() const override; + bool isSliderThumb() const override { return true; } + bool computeAccessibilityIsIgnored() const override; }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilitySliderThumb, isSliderThumb()) - } // namespace WebCore -#endif // AccessibilitySlider_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySliderThumb, isSliderThumb()) diff --git a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp index 955079483..11be8a916 100644 --- a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp +++ b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp @@ -31,13 +31,13 @@ namespace WebCore { -PassRefPtr<AccessibilitySpinButton> AccessibilitySpinButton::create() +Ref<AccessibilitySpinButton> AccessibilitySpinButton::create() { - return adoptRef(new AccessibilitySpinButton); + return adoptRef(*new AccessibilitySpinButton); } AccessibilitySpinButton::AccessibilitySpinButton() - : m_spinButtonElement(0) + : m_spinButtonElement(nullptr) { } @@ -49,6 +49,8 @@ AccessibilityObject* AccessibilitySpinButton::incrementButton() { if (!m_haveChildren) addChildren(); + if (!m_haveChildren) + return nullptr; ASSERT(m_children.size() == 2); @@ -59,6 +61,8 @@ AccessibilityObject* AccessibilitySpinButton::decrementButton() { if (!m_haveChildren) addChildren(); + if (!m_haveChildren) + return nullptr; ASSERT(m_children.size() == 2); @@ -80,17 +84,21 @@ LayoutRect AccessibilitySpinButton::elementRect() const void AccessibilitySpinButton::addChildren() { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + m_haveChildren = true; - AccessibilitySpinButtonPart* incrementor = toAccessibilitySpinButtonPart(axObjectCache()->getOrCreate(SpinButtonPartRole)); - incrementor->setIsIncrementor(true); - incrementor->setParent(this); - m_children.append(incrementor); + auto& incrementor = downcast<AccessibilitySpinButtonPart>(*cache->getOrCreate(SpinButtonPartRole)); + incrementor.setIsIncrementor(true); + incrementor.setParent(this); + m_children.append(&incrementor); - AccessibilitySpinButtonPart* decrementor = toAccessibilitySpinButtonPart(axObjectCache()->getOrCreate(SpinButtonPartRole)); - decrementor->setIsIncrementor(false); - decrementor->setParent(this); - m_children.append(decrementor); + auto& decrementor = downcast<AccessibilitySpinButtonPart>(*cache->getOrCreate(SpinButtonPartRole)); + decrementor.setIsIncrementor(false); + decrementor.setParent(this); + m_children.append(&decrementor); } void AccessibilitySpinButton::step(int amount) @@ -109,9 +117,9 @@ AccessibilitySpinButtonPart::AccessibilitySpinButtonPart() { } -PassRefPtr<AccessibilitySpinButtonPart> AccessibilitySpinButtonPart::create() +Ref<AccessibilitySpinButtonPart> AccessibilitySpinButtonPart::create() { - return adoptRef(new AccessibilitySpinButtonPart); + return adoptRef(*new AccessibilitySpinButtonPart); } LayoutRect AccessibilitySpinButtonPart::elementRect() const @@ -130,16 +138,16 @@ LayoutRect AccessibilitySpinButtonPart::elementRect() const return parentRect; } -bool AccessibilitySpinButtonPart::press() const +bool AccessibilitySpinButtonPart::press() { - if (!m_parent || !m_parent->isSpinButton()) + if (!is<AccessibilitySpinButton>(m_parent)) return false; - AccessibilitySpinButton* spinButton = toAccessibilitySpinButton(parentObject()); + auto& spinButton = downcast<AccessibilitySpinButton>(*m_parent); if (m_isIncrementor) - spinButton->step(1); + spinButton.step(1); else - spinButton->step(-1); + spinButton.step(-1); return true; } diff --git a/Source/WebCore/accessibility/AccessibilitySpinButton.h b/Source/WebCore/accessibility/AccessibilitySpinButton.h index c3956954f..bc043be85 100644 --- a/Source/WebCore/accessibility/AccessibilitySpinButton.h +++ b/Source/WebCore/accessibility/AccessibilitySpinButton.h @@ -23,18 +23,16 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilitySpinButton_h -#define AccessibilitySpinButton_h +#pragma once #include "AccessibilityMockObject.h" - #include "SpinButtonElement.h" namespace WebCore { -class AccessibilitySpinButton : public AccessibilityMockObject { +class AccessibilitySpinButton final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySpinButton> create(); + static Ref<AccessibilitySpinButton> create(); virtual ~AccessibilitySpinButton(); void setSpinButtonElement(SpinButtonElement* spinButton) { m_spinButtonElement = spinButton; } @@ -47,18 +45,18 @@ public: private: AccessibilitySpinButton(); - virtual AccessibilityRole roleValue() const override { return SpinButtonRole; } - virtual bool isSpinButton() const override { return true; } - virtual bool isNativeSpinButton() const override { return true; } - virtual void addChildren() override; - virtual LayoutRect elementRect() const override; + AccessibilityRole roleValue() const override { return SpinButtonRole; } + bool isSpinButton() const override { return true; } + bool isNativeSpinButton() const override { return true; } + void addChildren() override; + LayoutRect elementRect() const override; SpinButtonElement* m_spinButtonElement; }; -class AccessibilitySpinButtonPart : public AccessibilityMockObject { +class AccessibilitySpinButtonPart final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySpinButtonPart> create(); + static Ref<AccessibilitySpinButtonPart> create(); virtual ~AccessibilitySpinButtonPart() { } bool isIncrementor() const { return m_isIncrementor; } @@ -66,17 +64,16 @@ public: private: AccessibilitySpinButtonPart(); - bool m_isIncrementor : 1; - virtual bool press() const override; - virtual AccessibilityRole roleValue() const override { return ButtonRole; } - virtual bool isSpinButtonPart() const override { return true; } - virtual LayoutRect elementRect() const override; -}; + bool press() override; + AccessibilityRole roleValue() const override { return ButtonRole; } + bool isSpinButtonPart() const override { return true; } + LayoutRect elementRect() const override; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilitySpinButton, isNativeSpinButton()) -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilitySpinButtonPart, isSpinButtonPart()) + unsigned m_isIncrementor : 1; +}; -} // namespace WebCore +} // namespace WebCore -#endif // AccessibilitySpinButton_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySpinButton, isNativeSpinButton()) +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySpinButtonPart, isSpinButtonPart()) diff --git a/Source/WebCore/accessibility/AccessibilityTable.cpp b/Source/WebCore/accessibility/AccessibilityTable.cpp index d67783672..e5bb16978 100644 --- a/Source/WebCore/accessibility/AccessibilityTable.cpp +++ b/Source/WebCore/accessibility/AccessibilityTable.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -44,14 +44,16 @@ #include "RenderTableCell.h" #include "RenderTableSection.h" +#include <wtf/Deque.h> + namespace WebCore { using namespace HTMLNames; AccessibilityTable::AccessibilityTable(RenderObject* renderer) : AccessibilityRenderObject(renderer) - , m_headerContainer(0) - , m_isAccessibilityTable(true) + , m_headerContainer(nullptr) + , m_isExposableThroughAccessibility(true) { } @@ -62,12 +64,12 @@ AccessibilityTable::~AccessibilityTable() void AccessibilityTable::init() { AccessibilityRenderObject::init(); - m_isAccessibilityTable = isTableExposableThroughAccessibility(); + m_isExposableThroughAccessibility = computeIsTableExposableThroughAccessibility(); } -PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) +Ref<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTable(renderer)); + return adoptRef(*new AccessibilityTable(renderer)); } bool AccessibilityTable::hasARIARole() const @@ -82,14 +84,34 @@ bool AccessibilityTable::hasARIARole() const return false; } -bool AccessibilityTable::isAccessibilityTable() const +bool AccessibilityTable::isExposableThroughAccessibility() const { if (!m_renderer) return false; - return m_isAccessibilityTable; + return m_isExposableThroughAccessibility; } +HTMLTableElement* AccessibilityTable::tableElement() const +{ + if (!is<RenderTable>(*m_renderer)) + return nullptr; + + RenderTable& table = downcast<RenderTable>(*m_renderer); + if (is<HTMLTableElement>(table.element())) + return downcast<HTMLTableElement>(table.element()); + + table.forceSectionsRecalc(); + + // If the table has a display:table-row-group, then the RenderTable does not have a pointer to it's HTMLTableElement. + // We can instead find it by asking the firstSection for its parent. + RenderTableSection* firstBody = table.firstBody(); + if (!firstBody || !firstBody->element()) + return nullptr; + + return ancestorsOfType<HTMLTableElement>(*(firstBody->element())).first(); +} + bool AccessibilityTable::isDataTable() const { if (!m_renderer) @@ -105,34 +127,38 @@ bool AccessibilityTable::isDataTable() const if (node() && node()->hasEditableStyle()) return true; + if (!is<RenderTable>(*m_renderer)) + return false; + // This employs a heuristic to determine if this table should appear. // Only "data" tables should be exposed as tables. // Unfortunately, there is no good way to determine the difference // between a "layout" table and a "data" table. - - RenderTable* table = toRenderTable(m_renderer); - if (!table->element() || !isHTMLTableElement(table->element())) - return false; - - // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table - HTMLTableElement* tableElement = toHTMLTableElement(table->element()); - if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) - return true; - - // if someone used "rules" attribute than the table should appear - if (!tableElement->rules().isEmpty()) - return true; - - // if there's a colgroup or col element, it's probably a data table. - for (const auto& child : childrenOfType<Element>(*tableElement)) { - if (child.hasTagName(colTag) || child.hasTagName(colgroupTag)) + if (HTMLTableElement* tableElement = this->tableElement()) { + // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table. + if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) return true; + + // If someone used "rules" attribute than the table should appear. + if (!tableElement->rules().isEmpty()) + return true; + + // If there's a colgroup or col element, it's probably a data table. + for (const auto& child : childrenOfType<HTMLElement>(*tableElement)) { + if (child.hasTagName(colTag) || child.hasTagName(colgroupTag)) + return true; + } } + // The following checks should only apply if this is a real <table> element. + if (!hasTagName(tableTag)) + return false; + + RenderTable& table = downcast<RenderTable>(*m_renderer); // go through the cell's and check for tell-tale signs of "data" table status // cells have borders, or use attributes like headers, abbr, scope or axis - table->recalcSectionsIfNeeded(); - RenderTableSection* firstBody = table->firstBody(); + table.recalcSectionsIfNeeded(); + RenderTableSection* firstBody = table.firstBody(); if (!firstBody) return false; @@ -148,7 +174,7 @@ bool AccessibilityTable::isDataTable() const return true; // Store the background color of the table to check against cell's background colors. - const RenderStyle& tableStyle = table->style(); + const RenderStyle& tableStyle = table.style(); Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor); // check enough of the cells to find if the table matches our criteria @@ -183,22 +209,22 @@ bool AccessibilityTable::isDataTable() const if (cell->width() < 1 || cell->height() < 1) continue; - validCellCount++; + ++validCellCount; bool isTHCell = cellElement->hasTagName(thTag); // If the first row is comprised of all <th> tags, assume it is a data table. if (!row && isTHCell) - headersInFirstRowCount++; + ++headersInFirstRowCount; // If the first column is comprised of all <th> tags, assume it is a data table. if (!col && isTHCell) - headersInFirstColumnCount++; + ++headersInFirstColumnCount; // In this case, the developer explicitly assigned a "data" table attribute. - if (cellElement->hasTagName(tdTag) || cellElement->hasTagName(thTag)) { - HTMLTableCellElement* tableCellElement = toHTMLTableCellElement(cellElement); - if (!tableCellElement->headers().isEmpty() || !tableCellElement->abbr().isEmpty() - || !tableCellElement->axis().isEmpty() || !tableCellElement->scope().isEmpty()) + if (is<HTMLTableCellElement>(*cellElement)) { + HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement); + if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty() + || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty()) return true; } const RenderStyle& renderStyle = cell->style(); @@ -210,25 +236,25 @@ bool AccessibilityTable::isDataTable() const // If a cell has matching bordered sides, call it a (fully) bordered cell. if ((cell->borderTop() > 0 && cell->borderBottom() > 0) || (cell->borderLeft() > 0 && cell->borderRight() > 0)) - borderedCellCount++; + ++borderedCellCount; // Also keep track of each individual border, so we can catch tables where most // cells have a bottom border, for example. if (cell->borderTop() > 0) - cellsWithTopBorder++; + ++cellsWithTopBorder; if (cell->borderBottom() > 0) - cellsWithBottomBorder++; + ++cellsWithBottomBorder; if (cell->borderLeft() > 0) - cellsWithLeftBorder++; + ++cellsWithLeftBorder; if (cell->borderRight() > 0) - cellsWithRightBorder++; + ++cellsWithRightBorder; // If the cell has a different color from the table and there is cell spacing, // then it is probably a data table cell (spacing and colors take the place of borders). Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor); - if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 + if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0 && tableBGColor != cellColor && cellColor.alpha() != 1) - backgroundDifferenceCellCount++; + ++backgroundDifferenceCellCount; // If we've found 10 "good" cells, we don't need to keep searching. if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) @@ -236,13 +262,13 @@ bool AccessibilityTable::isDataTable() const // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows. if (row < 5 && row == alternatingRowColorCount) { - RenderObject* renderRow = cell->parent(); - if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow()) + RenderElement* renderRow = cell->parent(); + if (!is<RenderTableRow>(renderRow)) continue; const RenderStyle& rowRenderStyle = renderRow->style(); Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor); alternatingRowColors[alternatingRowColorCount] = rowColor; - alternatingRowColorCount++; + ++alternatingRowColorCount; } } @@ -287,7 +313,7 @@ bool AccessibilityTable::isDataTable() const return false; } -bool AccessibilityTable::isTableExposableThroughAccessibility() const +bool AccessibilityTable::computeIsTableExposableThroughAccessibility() const { // The following is a heuristic used to determine if a // <table> should be exposed as an AXTable. The goal @@ -302,12 +328,6 @@ bool AccessibilityTable::isTableExposableThroughAccessibility() const if (hasARIARole()) return false; - // Gtk+ ATs expect all tables to be exposed as tables. -#if PLATFORM(GTK) || PLATFORM(EFL) - Element* tableNode = toRenderTable(m_renderer)->element(); - return tableNode && isHTMLTableElement(tableNode); -#endif - return isDataTable(); } @@ -319,13 +339,13 @@ void AccessibilityTable::clearChildren() if (m_headerContainer) { m_headerContainer->detachFromParent(); - m_headerContainer = 0; + m_headerContainer = nullptr; } } void AccessibilityTable::addChildren() { - if (!isAccessibilityTable()) { + if (!isExposableThroughAccessibility()) { AccessibilityRenderObject::addChildren(); return; } @@ -333,68 +353,120 @@ void AccessibilityTable::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_renderer || !m_renderer->isTable()) + if (!is<RenderTable>(m_renderer)) return; - RenderTable* table = toRenderTable(m_renderer); - AXObjectCache* axCache = m_renderer->document().axObjectCache(); - + RenderTable& table = downcast<RenderTable>(*m_renderer); // Go through all the available sections to pull out the rows and add them as children. - table->recalcSectionsIfNeeded(); - RenderTableSection* tableSection = table->topSection(); - if (!tableSection) - return; + table.recalcSectionsIfNeeded(); - unsigned maxColumnCount = 0; - while (tableSection) { - - HashSet<AccessibilityObject*> appendedRows; - unsigned numRows = tableSection->numRows(); - for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { - - RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex); - if (!renderRow) - continue; - - AccessibilityObject* rowObject = axCache->getOrCreate(renderRow); - if (!rowObject->isTableRow()) - continue; - - AccessibilityTableRow* row = toAccessibilityTableRow(rowObject); - // We need to check every cell for a new row, because cell spans - // can cause us to miss rows if we just check the first column. - if (appendedRows.contains(row)) - continue; - - row->setRowIndex(static_cast<int>(m_rows.size())); - m_rows.append(row); - if (!row->accessibilityIsIgnored()) - m_children.append(row); -#if PLATFORM(GTK) || PLATFORM(EFL) - else - m_children.appendVector(row->children()); -#endif - appendedRows.add(row); + if (HTMLTableElement* tableElement = this->tableElement()) { + if (HTMLTableCaptionElement* caption = tableElement->caption()) { + AccessibilityObject* axCaption = axObjectCache()->getOrCreate(caption); + if (axCaption && !axCaption->accessibilityIsIgnored()) + m_children.append(axCaption); } + } + + unsigned maxColumnCount = 0; + RenderTableSection* footer = table.footer(); - maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount); - tableSection = table->sectionBelow(tableSection, SkipEmptySections); + for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) { + if (tableSection == footer) + continue; + addChildrenFromSection(tableSection, maxColumnCount); } + // Process the footer last, in case it was ordered earlier in the DOM. + if (footer) + addChildrenFromSection(footer, maxColumnCount); + + AXObjectCache* axCache = m_renderer->document().axObjectCache(); // make the columns based on the number of columns in the first body unsigned length = maxColumnCount; for (unsigned i = 0; i < length; ++i) { - AccessibilityTableColumn* column = toAccessibilityTableColumn(axCache->getOrCreate(ColumnRole)); - column->setColumnIndex((int)i); - column->setParent(this); - m_columns.append(column); - if (!column->accessibilityIsIgnored()) - m_children.append(column); + auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole)); + column.setColumnIndex((int)i); + column.setParent(this); + m_columns.append(&column); + if (!column.accessibilityIsIgnored()) + m_children.append(&column); } AccessibilityObject* headerContainerObject = headerContainer(); if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) m_children.append(headerContainerObject); + + // Sometimes the cell gets the wrong role initially because it is created before the parent + // determines whether it is an accessibility table. Iterate all the cells and allow them to + // update their roles now that the table knows its status. + // see bug: https://bugs.webkit.org/show_bug.cgi?id=147001 + for (const auto& row : m_rows) { + for (const auto& cell : row->children()) + cell->updateAccessibilityRole(); + } + +} + +void AccessibilityTable::addTableCellChild(AccessibilityObject* rowObject, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount) +{ + if (!rowObject || !is<AccessibilityTableRow>(*rowObject)) + return; + + auto& row = downcast<AccessibilityTableRow>(*rowObject); + // We need to check every cell for a new row, because cell spans + // can cause us to miss rows if we just check the first column. + if (appendedRows.contains(&row)) + return; + + row.setRowIndex(static_cast<int>(m_rows.size())); + m_rows.append(&row); + if (!row.accessibilityIsIgnored()) + m_children.append(&row); + appendedRows.add(&row); + + // store the maximum number of columns + unsigned rowCellCount = row.children().size(); + if (rowCellCount > columnCount) + columnCount = rowCellCount; +} + +void AccessibilityTable::addChildrenFromSection(RenderTableSection* tableSection, unsigned& maxColumnCount) +{ + ASSERT(tableSection); + if (!tableSection) + return; + + AXObjectCache* axCache = m_renderer->document().axObjectCache(); + HashSet<AccessibilityObject*> appendedRows; + unsigned numRows = tableSection->numRows(); + for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { + + RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex); + if (!renderRow) + continue; + + AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow); + + // If the row is anonymous, we should dive deeper into the descendants to try to find a valid row. + if (renderRow->isAnonymous()) { + Deque<AccessibilityObject*> queue; + queue.append(&rowObject); + + while (!queue.isEmpty()) { + AccessibilityObject* obj = queue.takeFirst(); + if (obj->node() && is<AccessibilityTableRow>(*obj)) { + addTableCellChild(obj, appendedRows, maxColumnCount); + continue; + } + for (auto* child = obj->firstChild(); child; child = child->nextSibling()) + queue.append(child); + } + } else + addTableCellChild(&rowObject, appendedRows, maxColumnCount); + } + + maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount); } AccessibilityObject* AccessibilityTable::headerContainer() @@ -402,10 +474,10 @@ AccessibilityObject* AccessibilityTable::headerContainer() if (m_headerContainer) return m_headerContainer.get(); - AccessibilityMockObject* tableHeader = toAccessibilityMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole)); - tableHeader->setParent(this); + auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(TableHeaderContainerRole)); + tableHeader.setParent(this); - m_headerContainer = tableHeader; + m_headerContainer = &tableHeader; return m_headerContainer.get(); } @@ -430,8 +502,11 @@ void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers) updateChildrenIfNecessary(); - for (const auto& column : m_columns) { - if (AccessibilityObject* header = toAccessibilityTableColumn(column.get())->headerObject()) + // Sometimes m_columns can be reset during the iteration, we cache it here to be safe. + AccessibilityChildrenVector columnsCopy = m_columns; + + for (const auto& column : columnsCopy) { + if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject()) headers.append(header); } } @@ -443,8 +518,11 @@ void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers) updateChildrenIfNecessary(); - for (const auto& row : m_rows) { - if (AccessibilityObject* header = toAccessibilityTableRow(row.get())->headerObject()) + // Sometimes m_rows can be reset during the iteration, we cache it here to be safe. + AccessibilityChildrenVector rowsCopy = m_rows; + + for (const auto& row : rowsCopy) { + if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject()) headers.append(header); } } @@ -491,7 +569,7 @@ int AccessibilityTable::tableLevel() const { int level = 0; for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) { - if (obj->isAccessibilityTable()) + if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility()) ++level; } @@ -502,7 +580,7 @@ AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, { updateChildrenIfNecessary(); if (column >= columnCount() || row >= rowCount()) - return 0; + return nullptr; // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row. for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) { @@ -513,29 +591,33 @@ AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) { unsigned colIndex = colIndexCounter - 1; AccessibilityObject* child = children[colIndex].get(); - ASSERT(child->isTableCell()); - if (!child->isTableCell()) + ASSERT(is<AccessibilityTableCell>(*child)); + if (!is<AccessibilityTableCell>(*child)) continue; std::pair<unsigned, unsigned> columnRange; std::pair<unsigned, unsigned> rowRange; - AccessibilityTableCell* tableCellChild = toAccessibilityTableCell(child); - tableCellChild->columnIndexRange(columnRange); - tableCellChild->rowIndexRange(rowRange); + auto& tableCellChild = downcast<AccessibilityTableCell>(*child); + tableCellChild.columnIndexRange(columnRange); + tableCellChild.rowIndexRange(rowRange); if ((column >= columnRange.first && column < (columnRange.first + columnRange.second)) && (row >= rowRange.first && row < (rowRange.first + rowRange.second))) - return tableCellChild; + return &tableCellChild; } } - return 0; + return nullptr; } AccessibilityRole AccessibilityTable::roleValue() const { - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::roleValue(); + + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == GridRole || ariaRole == TreeGridRole) + return GridRole; return TableRole; } @@ -548,7 +630,7 @@ bool AccessibilityTable::computeAccessibilityIsIgnored() const if (decision == IgnoreObject) return true; - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::computeAccessibilityIsIgnored(); return false; @@ -563,7 +645,7 @@ void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) String AccessibilityTable::title() const { - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::title(); String title; @@ -572,9 +654,8 @@ String AccessibilityTable::title() const // see if there is a caption Node* tableElement = m_renderer->node(); - if (tableElement && isHTMLTableElement(tableElement)) { - HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption(); - if (caption) + if (is<HTMLTableElement>(tableElement)) { + if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption()) title = caption->innerText(); } @@ -585,4 +666,30 @@ String AccessibilityTable::title() const return title; } +int AccessibilityTable::ariaColumnCount() const +{ + const AtomicString& colCountValue = getAttribute(aria_colcountAttr); + + int colCountInt = colCountValue.toInt(); + // If only a portion of the columns is present in the DOM at a given moment, this attribute is needed to + // provide an explicit indication of the number of columns in the full table. + if (colCountInt > (int)m_columns.size()) + return colCountInt; + + return -1; +} + +int AccessibilityTable::ariaRowCount() const +{ + const AtomicString& rowCountValue = getAttribute(aria_rowcountAttr); + + int rowCountInt = rowCountValue.toInt(); + // If only a portion of the rows is present in the DOM at a given moment, this attribute is needed to + // provide an explicit indication of the number of rows in the full table. + if (rowCountInt > (int)m_rows.size()) + return rowCountInt; + + return -1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTable.h b/Source/WebCore/accessibility/AccessibilityTable.h index 3b447d41a..8511b8ecd 100644 --- a/Source/WebCore/accessibility/AccessibilityTable.h +++ b/Source/WebCore/accessibility/AccessibilityTable.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityTable_h -#define AccessibilityTable_h +#pragma once #include "AccessibilityRenderObject.h" #include <wtf/Forward.h> @@ -35,23 +34,21 @@ namespace WebCore { class AccessibilityTableCell; +class HTMLTableElement; class RenderTableSection; class AccessibilityTable : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTable(RenderObject*); public: - static PassRefPtr<AccessibilityTable> create(RenderObject*); + static Ref<AccessibilityTable> create(RenderObject*); virtual ~AccessibilityTable(); - virtual void init() override; + void init() final; - virtual AccessibilityRole roleValue() const override; + AccessibilityRole roleValue() const final; virtual bool isAriaTable() const { return false; } - virtual void addChildren() override; - virtual void clearChildren() override; + void addChildren() override; + void clearChildren() final; const AccessibilityChildrenVector& columns(); const AccessibilityChildrenVector& rows(); @@ -59,9 +56,9 @@ public: virtual bool supportsSelectedRows() { return false; } unsigned columnCount(); unsigned rowCount(); - virtual int tableLevel() const override; + int tableLevel() const final; - virtual String title() const override; + String title() const final; // all the cells in the table void cells(AccessibilityChildrenVector&); @@ -74,32 +71,38 @@ public: // an object that contains, as children, all the objects that act as headers AccessibilityObject* headerContainer(); + // isExposableThroughAccessibility() is whether it is exposed as an AccessibilityTable to the platform. + bool isExposableThroughAccessibility() const; + + int ariaColumnCount() const; + int ariaRowCount() const; + protected: + explicit AccessibilityTable(RenderObject*); + AccessibilityChildrenVector m_rows; AccessibilityChildrenVector m_columns; RefPtr<AccessibilityObject> m_headerContainer; - bool m_isAccessibilityTable; + bool m_isExposableThroughAccessibility; bool hasARIARole() const; // isTable is whether it's an AccessibilityTable object. - virtual bool isTable() const override { return true; } - // isAccessibilityTable is whether it is exposed as an AccessibilityTable to the platform. - virtual bool isAccessibilityTable() const override; + bool isTable() const final { return true; } // isDataTable is whether it is exposed as an AccessibilityTable because the heuristic // think this "looks" like a data-based table (instead of a table used for layout). - virtual bool isDataTable() const override; - - virtual bool isTableExposableThroughAccessibility() const; - virtual bool computeAccessibilityIsIgnored() const override; + bool isDataTable() const final; + bool computeAccessibilityIsIgnored() const final; private: - virtual void titleElementText(Vector<AccessibilityText>&) const override; + virtual bool computeIsTableExposableThroughAccessibility() const; + void titleElementText(Vector<AccessibilityText>&) const final; + HTMLTableElement* tableElement() const; + void addChildrenFromSection(RenderTableSection*, unsigned& maxColumnCount); + void addTableCellChild(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityTable, isTable()) - } // namespace WebCore -#endif // AccessibilityTable_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTable, isTable()) diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.cpp b/Source/WebCore/accessibility/AccessibilityTableCell.cpp index 876a9c2ca..287997b08 100644 --- a/Source/WebCore/accessibility/AccessibilityTableCell.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableCell.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -32,6 +32,8 @@ #include "AXObjectCache.h" #include "AccessibilityTable.h" #include "AccessibilityTableRow.h" +#include "ElementIterator.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "RenderObject.h" #include "RenderTableCell.h" @@ -42,6 +44,7 @@ using namespace HTMLNames; AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer) : AccessibilityRenderObject(renderer) + , m_ariaColIndexFromRow(-1) { } @@ -49,9 +52,9 @@ AccessibilityTableCell::~AccessibilityTableCell() { } -PassRefPtr<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer) +Ref<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTableCell(renderer)); + return adoptRef(*new AccessibilityTableCell(renderer)); } bool AccessibilityTableCell::computeAccessibilityIsIgnored() const @@ -62,6 +65,12 @@ bool AccessibilityTableCell::computeAccessibilityIsIgnored() const if (decision == IgnoreObject) return true; + // Ignore anonymous table cells as long as they're not in a table (ie. when display:table is used). + RenderObject* renderTable = is<RenderTableCell>(m_renderer) ? downcast<RenderTableCell>(*m_renderer).table() : nullptr; + bool inTable = renderTable && renderTable->node() && (renderTable->node()->hasTagName(tableTag) || nodeHasRole(renderTable->node(), "grid")); + if (!node() && !inTable) + return true; + if (!isTableCell()) return AccessibilityRenderObject::computeAccessibilityIsIgnored(); @@ -70,39 +79,67 @@ bool AccessibilityTableCell::computeAccessibilityIsIgnored() const AccessibilityTable* AccessibilityTableCell::parentTable() const { - if (!m_renderer || !m_renderer->isTableCell()) - return 0; + if (!is<RenderTableCell>(m_renderer)) + return nullptr; // If the document no longer exists, we might not have an axObjectCache. if (!axObjectCache()) - return 0; + return nullptr; // Do not use getOrCreate. parentTable() can be called while the render tree is being modified // by javascript, and creating a table element may try to access the render tree while in a bad state. // By using only get() implies that the AXTable must be created before AXTableCells. This should // always be the case when AT clients access a table. - // https://bugs.webkit.org/show_bug.cgi?id=42652 - return toAccessibilityTable(axObjectCache()->get(toRenderTableCell(m_renderer)->table())); + // https://bugs.webkit.org/show_bug.cgi?id=42652 + AccessibilityObject* parentTable = axObjectCache()->get(downcast<RenderTableCell>(*m_renderer).table()); + if (!is<AccessibilityTable>(parentTable)) + return nullptr; + + // The RenderTableCell's table() object might be anonymous sometimes. We should handle it gracefully + // by finding the right table. + if (!parentTable->node()) { + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + // If this is a non-anonymous table object, but not an accessibility table, we should stop because + // we don't want to choose another ancestor table as this cell's table. + if (is<AccessibilityTable>(*parent)) { + auto& parentTable = downcast<AccessibilityTable>(*parent); + if (parentTable.isExposableThroughAccessibility()) + return &parentTable; + if (parentTable.node()) + break; + } + } + return nullptr; + } + + return downcast<AccessibilityTable>(parentTable); } bool AccessibilityTableCell::isTableCell() const { - AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isTableRow()) - return false; - - return true; + // If the parent table is an accessibility table, then we are a table cell. + // This used to check if the unignoredParent was a row, but that exploded performance if + // this was in nested tables. This check should be just as good. + AccessibilityObject* parentTable = this->parentTable(); + return is<AccessibilityTable>(parentTable) && downcast<AccessibilityTable>(*parentTable).isExposableThroughAccessibility(); } AccessibilityRole AccessibilityTableCell::determineAccessibilityRole() { - // Always call determineAccessibleRole so that the ARIA role is set. - // Even though this object reports a Cell role, the ARIA role will be used - // to determine if it's a column header. + // AccessibilityRenderObject::determineAccessibleRole provides any ARIA-supplied + // role, falling back on the role to be used if we determine here that the element + // should not be exposed as a cell. Thus if we already know it's a cell, return that. AccessibilityRole defaultRole = AccessibilityRenderObject::determineAccessibilityRole(); + if (defaultRole == ColumnHeaderRole || defaultRole == RowHeaderRole || defaultRole == CellRole || defaultRole == GridCellRole) + return defaultRole; + if (!isTableCell()) return defaultRole; - + if (isColumnHeaderCell()) + return ColumnHeaderRole; + if (isRowHeaderCell()) + return RowHeaderRole; + return CellRole; } @@ -111,6 +148,62 @@ bool AccessibilityTableCell::isTableHeaderCell() const return node() && node()->hasTagName(thTag); } +bool AccessibilityTableCell::isColumnHeaderCell() const +{ + const AtomicString& scope = getAttribute(scopeAttr); + if (scope == "col" || scope == "colgroup") + return true; + if (scope == "row" || scope == "rowgroup") + return false; + if (!isTableHeaderCell()) + return false; + + // We are in a situation after checking the scope attribute. + // It is an attempt to resolve the type of th element without support in the specification. + // Checking tableTag and tbodyTag allows to check the case of direct row placement in the table and lets stop the loop at the table level. + for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(theadTag)) + return true; + if (parentNode->hasTagName(tfootTag)) + return false; + if (parentNode->hasTagName(tableTag) || parentNode->hasTagName(tbodyTag)) { + std::pair<unsigned, unsigned> rowRange; + rowIndexRange(rowRange); + if (!rowRange.first) + return true; + return false; + } + } + return false; +} + +bool AccessibilityTableCell::isRowHeaderCell() const +{ + const AtomicString& scope = getAttribute(scopeAttr); + if (scope == "row" || scope == "rowgroup") + return true; + if (scope == "col" || scope == "colgroup") + return false; + if (!isTableHeaderCell()) + return false; + + // We are in a situation after checking the scope attribute. + // It is an attempt to resolve the type of th element without support in the specification. + // Checking tableTag allows to check the case of direct row placement in the table and lets stop the loop at the table level. + for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(tfootTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tableTag)) { + std::pair<unsigned, unsigned> colRange; + columnIndexRange(colRange); + if (!colRange.first) + return true; + return false; + } + if (parentNode->hasTagName(theadTag)) + return false; + } + return false; +} + bool AccessibilityTableCell::isTableCellInSameRowGroup(AccessibilityTableCell* otherTableCell) { Node* parentNode = node(); @@ -142,12 +235,28 @@ bool AccessibilityTableCell::isTableCellInSameColGroup(AccessibilityTableCell* t return false; } +String AccessibilityTableCell::expandedTextValue() const +{ + return getAttribute(abbrAttr); +} + +bool AccessibilityTableCell::supportsExpandedTextValue() const +{ + return isTableHeaderCell() && hasAttribute(abbrAttr); +} + void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers) { AccessibilityTable* parent = parentTable(); if (!parent) return; + // Choose columnHeaders as the place where the "headers" attribute is reported. + ariaElementsFromAttribute(headers, headersAttr); + // If the headers attribute returned valid values, then do not further search for column headers. + if (!headers.isEmpty()) + return; + std::pair<unsigned, unsigned> rowRange; rowIndexRange(rowRange); @@ -156,16 +265,16 @@ void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers) for (unsigned row = 0; row < rowRange.first; row++) { AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(colRange.first, row); - if (tableCell == this || headers.contains(tableCell)) + if (!tableCell || tableCell == this || headers.contains(tableCell)) continue; std::pair<unsigned, unsigned> childRowRange; tableCell->rowIndexRange(childRowRange); const AtomicString& scope = tableCell->getAttribute(scopeAttr); - if (scope == "col" || tableCell->isTableHeaderCell()) + if (scope == "colgroup" && isTableCellInSameColGroup(tableCell)) headers.append(tableCell); - else if (scope == "colgroup" && isTableCellInSameColGroup(tableCell)) + else if (tableCell->isColumnHeaderCell()) headers.append(tableCell); } } @@ -184,52 +293,45 @@ void AccessibilityTableCell::rowHeaders(AccessibilityChildrenVector& headers) for (unsigned column = 0; column < colRange.first; column++) { AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(column, rowRange.first); - if (tableCell == this || headers.contains(tableCell)) + if (!tableCell || tableCell == this || headers.contains(tableCell)) continue; const AtomicString& scope = tableCell->getAttribute(scopeAttr); - if (scope == "row") + if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell)) headers.append(tableCell); - else if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell)) + else if (tableCell->isRowHeaderCell()) headers.append(tableCell); } } - -void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) + +AccessibilityTableRow* AccessibilityTableCell::parentRow() const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!is<AccessibilityTableRow>(*parent)) + return nullptr; + return downcast<AccessibilityTableRow>(parent); +} + +void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const { - if (!m_renderer || !m_renderer->isTableCell()) + if (!is<RenderTableCell>(m_renderer)) return; - RenderTableCell* renderCell = toRenderTableCell(m_renderer); - rowRange.first = renderCell->rowIndex(); - rowRange.second = renderCell->rowSpan(); + RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer); + rowRange.second = renderCell.rowSpan(); - // since our table might have multiple sections, we have to offset our row appropriately - RenderTableSection* section = renderCell->section(); - RenderTable* table = renderCell->table(); - if (!table || !section) - return; - - RenderTableSection* tableSection = table->topSection(); - unsigned rowOffset = 0; - while (tableSection) { - if (tableSection == section) - break; - rowOffset += tableSection->numRows(); - tableSection = table->sectionBelow(tableSection, SkipEmptySections); - } - - rowRange.first += rowOffset; + if (AccessibilityTableRow* parentRow = this->parentRow()) + rowRange.first = parentRow->rowIndex(); } -void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) +void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const { - if (!m_renderer || !m_renderer->isTableCell()) + if (!is<RenderTableCell>(m_renderer)) return; - RenderTableCell* renderCell = toRenderTableCell(m_renderer); - columnRange.first = renderCell->col(); - columnRange.second = renderCell->colSpan(); + const RenderTableCell& cell = downcast<RenderTableCell>(*m_renderer); + columnRange.first = cell.table()->colToEffCol(cell.col()); + columnRange.second = cell.table()->colToEffCol(cell.col() + cell.colSpan()) - columnRange.first; } AccessibilityObject* AccessibilityTableCell::titleUIElement() const @@ -237,36 +339,90 @@ AccessibilityObject* AccessibilityTableCell::titleUIElement() const // Try to find if the first cell in this row is a <th>. If it is, // then it can act as the title ui element. (This is only in the // case when the table is not appearing as an AXTable.) - if (isTableCell() || !m_renderer || !m_renderer->isTableCell()) - return 0; + if (isTableCell() || !is<RenderTableCell>(m_renderer)) + return nullptr; // Table cells that are th cannot have title ui elements, since by definition // they are title ui elements Node* node = m_renderer->node(); if (node && node->hasTagName(thTag)) - return 0; + return nullptr; - RenderTableCell* renderCell = toRenderTableCell(m_renderer); + RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer); // If this cell is in the first column, there is no need to continue. - int col = renderCell->col(); + int col = renderCell.col(); if (!col) - return 0; + return nullptr; - int row = renderCell->rowIndex(); + int row = renderCell.rowIndex(); - RenderTableSection* section = renderCell->section(); + RenderTableSection* section = renderCell.section(); if (!section) - return 0; + return nullptr; RenderTableCell* headerCell = section->primaryCellAt(row, 0); - if (!headerCell || headerCell == renderCell) - return 0; + if (!headerCell || headerCell == &renderCell) + return nullptr; if (!headerCell->element() || !headerCell->element()->hasTagName(thTag)) - return 0; + return nullptr; return axObjectCache()->getOrCreate(headerCell); } +int AccessibilityTableCell::ariaColumnIndex() const +{ + const AtomicString& colIndexValue = getAttribute(aria_colindexAttr); + if (colIndexValue.toInt() >= 1) + return colIndexValue.toInt(); + + // "ARIA 1.1: If the set of columns which is present in the DOM is contiguous, and if there are no cells which span more than one row + // or column in that set, then authors may place aria-colindex on each row, setting the value to the index of the first column of the set." + // Here, we let its parent row to set its index beforehand, so we don't have to go through the siblings to calculate the index. + AccessibilityTableRow* parentRow = this->parentRow(); + if (parentRow && m_ariaColIndexFromRow != -1) + return m_ariaColIndexFromRow; + + return -1; +} + +int AccessibilityTableCell::ariaRowIndex() const +{ + // ARIA 1.1: Authors should place aria-rowindex on each row. Authors may also place + // aria-rowindex on all of the children or owned elements of each row. + const AtomicString& rowIndexValue = getAttribute(aria_rowindexAttr); + if (rowIndexValue.toInt() >= 1) + return rowIndexValue.toInt(); + + if (AccessibilityTableRow* parentRow = this->parentRow()) + return parentRow->ariaRowIndex(); + + return -1; +} + +unsigned AccessibilityTableCell::ariaColumnSpan() const +{ + const AtomicString& colSpanValue = getAttribute(aria_colspanAttr); + // ARIA 1.1: Authors must set the value of aria-colspan to an integer greater than or equal to 1. + if (colSpanValue.toInt() >= 1) + return colSpanValue.toInt(); + + return 1; +} + +unsigned AccessibilityTableCell::ariaRowSpan() const +{ + const AtomicString& rowSpanValue = getAttribute(aria_rowspanAttr); + + // ARIA 1.1: Authors must set the value of aria-rowspan to an integer greater than or equal to 0. + // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group. + if (rowSpanValue == "0") + return 0; + if (rowSpanValue.toInt() >= 1) + return rowSpanValue.toInt(); + + return 1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.h b/Source/WebCore/accessibility/AccessibilityTableCell.h index b45d238be..78be9e254 100644 --- a/Source/WebCore/accessibility/AccessibilityTableCell.h +++ b/Source/WebCore/accessibility/AccessibilityTableCell.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,51 +26,61 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityTableCell_h -#define AccessibilityTableCell_h +#pragma once #include "AccessibilityRenderObject.h" namespace WebCore { class AccessibilityTable; - +class AccessibilityTableRow; + class AccessibilityTableCell : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTableCell(RenderObject*); public: - static PassRefPtr<AccessibilityTableCell> create(RenderObject*); + static Ref<AccessibilityTableCell> create(RenderObject*); virtual ~AccessibilityTableCell(); - virtual bool isTableCell() const override; + bool isTableCell() const final; bool isTableHeaderCell() const; + bool isColumnHeaderCell() const; + bool isRowHeaderCell() const; // fills in the start location and row span of cell - virtual void rowIndexRange(std::pair<unsigned, unsigned>& rowRange); + virtual void rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const; // fills in the start location and column span of cell - virtual void columnIndexRange(std::pair<unsigned, unsigned>& columnRange); + virtual void columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const; void columnHeaders(AccessibilityChildrenVector&); void rowHeaders(AccessibilityChildrenVector&); + + int ariaColumnIndex() const; + int ariaRowIndex() const; + unsigned ariaColumnSpan() const; + unsigned ariaRowSpan() const; + void setARIAColIndexFromRow(int index) { m_ariaColIndexFromRow = index; } protected: + explicit AccessibilityTableCell(RenderObject*); + + AccessibilityTableRow* parentRow() const; virtual AccessibilityTable* parentTable() const; + AccessibilityRole determineAccessibilityRole() final; + int m_rowIndex; - virtual AccessibilityRole determineAccessibilityRole() override; + int m_ariaColIndexFromRow; private: // If a table cell is not exposed as a table cell, a TH element can serve as its title UI element. - virtual AccessibilityObject* titleUIElement() const override; - virtual bool exposesTitleUIElement() const override { return true; } - virtual bool computeAccessibilityIsIgnored() const override; - + AccessibilityObject* titleUIElement() const final; + bool exposesTitleUIElement() const final { return true; } + bool computeAccessibilityIsIgnored() const final; + String expandedTextValue() const final; + bool supportsExpandedTextValue() const final; + bool isTableCellInSameRowGroup(AccessibilityTableCell*); bool isTableCellInSameColGroup(AccessibilityTableCell*); }; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityTableCell, isTableCell()) - } // namespace WebCore -#endif // AccessibilityTableCell_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableCell, isTableCell()) diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.cpp b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp index 6791c3320..1669a6fc0 100644 --- a/Source/WebCore/accessibility/AccessibilityTableColumn.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,6 +31,8 @@ #include "AXObjectCache.h" #include "AccessibilityTableCell.h" +#include "HTMLCollection.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "RenderTable.h" #include "RenderTableCell.h" @@ -48,9 +50,9 @@ AccessibilityTableColumn::~AccessibilityTableColumn() { } -PassRefPtr<AccessibilityTableColumn> AccessibilityTableColumn::create() +Ref<AccessibilityTableColumn> AccessibilityTableColumn::create() { - return adoptRef(new AccessibilityTableColumn()); + return adoptRef(*new AccessibilityTableColumn()); } void AccessibilityTableColumn::setParent(AccessibilityObject* parent) @@ -62,88 +64,115 @@ void AccessibilityTableColumn::setParent(AccessibilityObject* parent) LayoutRect AccessibilityTableColumn::elementRect() const { - // this will be filled in when addChildren is called - return m_columnRect; + // This used to be cached during the call to addChildren(), but calling elementRect() + // can invalidate elements, so its better to ask for this on demand. + LayoutRect columnRect; + AccessibilityChildrenVector childrenCopy = m_children; + for (const auto& cell : childrenCopy) + columnRect.unite(cell->elementRect()); + + return columnRect; } AccessibilityObject* AccessibilityTableColumn::headerObject() { if (!m_parent) - return 0; + return nullptr; RenderObject* renderer = m_parent->renderer(); if (!renderer) - return 0; - - if (!m_parent->isAccessibilityTable()) - return 0; + return nullptr; + if (!is<AccessibilityTable>(*m_parent)) + return nullptr; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) + return nullptr; - AccessibilityTable* parentTable = toAccessibilityTable(m_parent); - if (parentTable->isAriaTable()) { + if (parentTable.isAriaTable()) { for (const auto& cell : children()) { if (cell->ariaRoleAttribute() == ColumnHeaderRole) return cell.get(); } - return 0; + return nullptr; } - if (!renderer->isTable()) - return 0; + if (!is<RenderTable>(*renderer)) + return nullptr; - RenderTable* table = toRenderTable(renderer); - - AccessibilityObject* headerObject = 0; - - // try the <thead> section first. this doesn't require th tags - headerObject = headerObjectForSection(table->header(), false); + RenderTable& table = downcast<RenderTable>(*renderer); - if (headerObject) + // try the <thead> section first. this doesn't require th tags + if (auto* headerObject = headerObjectForSection(table.header(), false)) return headerObject; - // now try for <th> tags in the first body - headerObject = headerObjectForSection(table->firstBody(), true); - - return headerObject; + RenderTableSection* bodySection = table.firstBody(); + while (bodySection && bodySection->isAnonymous()) + bodySection = table.sectionBelow(bodySection, SkipEmptySections); + + // now try for <th> tags in the first body. If the first body is + return headerObjectForSection(bodySection, true); } AccessibilityObject* AccessibilityTableColumn::headerObjectForSection(RenderTableSection* section, bool thTagRequired) { if (!section) - return 0; + return nullptr; unsigned numCols = section->numColumns(); if (m_columnIndex >= numCols) - return 0; + return nullptr; if (!section->numRows()) - return 0; + return nullptr; - RenderTableCell* cell = 0; + RenderTableCell* cell = nullptr; // also account for cells that have a span for (int testCol = m_columnIndex; testCol >= 0; --testCol) { - RenderTableCell* testCell = section->primaryCellAt(0, testCol); - if (!testCell) - continue; - // we've reached a cell that doesn't even overlap our column - // it can't be our header - if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex) + // Run down the rows in case initial rows are invalid (like when a <caption> is used). + unsigned rowCount = section->numRows(); + for (unsigned testRow = 0; testRow < rowCount; testRow++) { + RenderTableCell* testCell = section->primaryCellAt(testRow, testCol); + // No cell at this index, keep checking more rows and columns. + if (!testCell) + continue; + + // If we've reached a cell that doesn't even overlap our column it can't be the header. + if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex) + break; + + Node* testCellNode = testCell->element(); + // If the RenderTableCell doesn't have an element because its anonymous, + // try to see if we can find the original cell element to check if it has a <th> tag. + if (!testCellNode && testCell->isAnonymous()) { + if (Element* parentElement = testCell->parent() ? testCell->parent()->element() : nullptr) { + if (parentElement->hasTagName(trTag) && testCol < static_cast<int>(parentElement->countChildNodes())) + testCellNode = parentElement->traverseToChildAt(testCol); + } + } + + if (!testCellNode) + continue; + + // If th is required, but we found an element that doesn't have a th tag, we can stop looking. + if (thTagRequired && !testCellNode->hasTagName(thTag)) + break; + + cell = testCell; break; - - if (!testCell->element()) - continue; - - if (thTagRequired && !testCell->element()->hasTagName(thTag)) - continue; - - cell = testCell; + } } if (!cell) - return 0; + return nullptr; - return axObjectCache()->getOrCreate(cell); + auto* cellObject = axObjectCache()->getOrCreate(cell); + if (!cellObject || cellObject->accessibilityIsIgnored()) + return nullptr; + + return cellObject; } bool AccessibilityTableColumn::computeAccessibilityIsIgnored() const @@ -151,7 +180,7 @@ bool AccessibilityTableColumn::computeAccessibilityIsIgnored() const if (!m_parent) return true; -#if PLATFORM(IOS) || PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(IOS) || PLATFORM(GTK) return true; #endif @@ -163,14 +192,17 @@ void AccessibilityTableColumn::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_parent || !m_parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(m_parent)) + return; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) return; - AccessibilityTable* parentTable = toAccessibilityTable(m_parent); - int numRows = parentTable->rowCount(); + int numRows = parentTable.rowCount(); - for (int i = 0; i < numRows; i++) { - AccessibilityTableCell* cell = parentTable->cellForColumnAndRow(m_columnIndex, i); + for (int i = 0; i < numRows; ++i) { + AccessibilityTableCell* cell = parentTable.cellForColumnAndRow(m_columnIndex, i); if (!cell) continue; @@ -179,7 +211,6 @@ void AccessibilityTableColumn::addChildren() continue; m_children.append(cell); - m_columnRect.unite(cell->elementRect()); } } diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.h b/Source/WebCore/accessibility/AccessibilityTableColumn.h index 65ed020a2..6b27f907c 100644 --- a/Source/WebCore/accessibility/AccessibilityTableColumn.h +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityTableColumn_h -#define AccessibilityTableColumn_h +#pragma once #include "AccessibilityMockObject.h" #include "AccessibilityTable.h" @@ -37,37 +36,33 @@ namespace WebCore { class RenderTableSection; -class AccessibilityTableColumn : public AccessibilityMockObject { - -private: - AccessibilityTableColumn(); +class AccessibilityTableColumn final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityTableColumn> create(); + static Ref<AccessibilityTableColumn> create(); virtual ~AccessibilityTableColumn(); AccessibilityObject* headerObject(); - virtual AccessibilityRole roleValue() const override { return ColumnRole; } - virtual bool isTableColumn() const override { return true; } + AccessibilityRole roleValue() const override { return ColumnRole; } void setColumnIndex(int columnIndex) { m_columnIndex = columnIndex; } int columnIndex() const { return m_columnIndex; } - virtual void addChildren() override; - virtual void setParent(AccessibilityObject*) override; + void addChildren() override; + void setParent(AccessibilityObject*) override; - virtual LayoutRect elementRect() const override; + LayoutRect elementRect() const override; -private: - unsigned m_columnIndex; - LayoutRect m_columnRect; +private: + AccessibilityTableColumn(); AccessibilityObject* headerObjectForSection(RenderTableSection*, bool thTagRequired); - virtual bool computeAccessibilityIsIgnored() const override; -}; + bool computeAccessibilityIsIgnored() const override; + bool isTableColumn() const override { return true; } -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityTableColumn, isTableColumn()) + unsigned m_columnIndex; +}; } // namespace WebCore -#endif // AccessibilityTableColumn_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableColumn, isTableColumn()) diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp index 52a080020..3907e12a4 100644 --- a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -42,9 +42,9 @@ AccessibilityTableHeaderContainer::~AccessibilityTableHeaderContainer() { } -PassRefPtr<AccessibilityTableHeaderContainer> AccessibilityTableHeaderContainer::create() +Ref<AccessibilityTableHeaderContainer> AccessibilityTableHeaderContainer::create() { - return adoptRef(new AccessibilityTableHeaderContainer()); + return adoptRef(*new AccessibilityTableHeaderContainer()); } LayoutRect AccessibilityTableHeaderContainer::elementRect() const @@ -58,7 +58,7 @@ bool AccessibilityTableHeaderContainer::computeAccessibilityIsIgnored() const if (!m_parent) return true; -#if PLATFORM(IOS) || PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(IOS) || PLATFORM(GTK) return true; #endif @@ -70,10 +70,14 @@ void AccessibilityTableHeaderContainer::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_parent || !m_parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(m_parent)) + return; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) return; - toAccessibilityTable(m_parent)->columnHeaders(m_children); + parentTable.columnHeaders(m_children); for (const auto& child : m_children) m_headerRect.unite(child->elementRect()); diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h index 99890d3cd..16da83919 100644 --- a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityTableHeaderContainer_h -#define AccessibilityTableHeaderContainer_h +#pragma once #include "AccessibilityMockObject.h" #include "AccessibilityTable.h" @@ -35,26 +34,23 @@ namespace WebCore { -class AccessibilityTableHeaderContainer : public AccessibilityMockObject { - -private: - AccessibilityTableHeaderContainer(); +class AccessibilityTableHeaderContainer final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityTableHeaderContainer> create(); + static Ref<AccessibilityTableHeaderContainer> create(); virtual ~AccessibilityTableHeaderContainer(); - virtual AccessibilityRole roleValue() const override { return TableHeaderContainerRole; } + AccessibilityRole roleValue() const override { return TableHeaderContainerRole; } - virtual void addChildren() override; + void addChildren() override; - virtual LayoutRect elementRect() const override; + LayoutRect elementRect() const override; private: - LayoutRect m_headerRect; + AccessibilityTableHeaderContainer(); - virtual bool computeAccessibilityIsIgnored() const override; + bool computeAccessibilityIsIgnored() const override; + + LayoutRect m_headerRect; }; } // namespace WebCore - -#endif // AccessibilityTableHeaderContainer_h diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.cpp b/Source/WebCore/accessibility/AccessibilityTableRow.cpp index ab4d0d03b..b51b18c41 100644 --- a/Source/WebCore/accessibility/AccessibilityTableRow.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableRow.cpp @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -51,9 +51,9 @@ AccessibilityTableRow::~AccessibilityTableRow() { } -PassRefPtr<AccessibilityTableRow> AccessibilityTableRow::create(RenderObject* renderer) +Ref<AccessibilityTableRow> AccessibilityTableRow::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTableRow(renderer)); + return adoptRef(*new AccessibilityTableRow(renderer)); } AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() @@ -61,11 +61,8 @@ AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() if (!isTableRow()) return AccessibilityRenderObject::determineAccessibilityRole(); - m_ariaRole = determineAriaRoleAttribute(); - - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; return RowRole; } @@ -73,10 +70,7 @@ AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() bool AccessibilityTableRow::isTableRow() const { AccessibilityObject* table = parentTable(); - if (!table || !table->isAccessibilityTable()) - return false; - - return true; + return is<AccessibilityTable>(table) && downcast<AccessibilityTable>(*table).isExposableThroughAccessibility(); } AccessibilityObject* AccessibilityTableRow::observableObject() const @@ -104,38 +98,81 @@ AccessibilityTable* AccessibilityTableRow::parentTable() const // The parent table might not be the direct ancestor of the row unfortunately. ARIA states that role="grid" should // only have "row" elements, but if not, we still should handle it gracefully by finding the right table. for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - // If this is a table object, but not an accessibility table, we should stop because we don't want to + // If this is a non-anonymous table object, but not an accessibility table, we should stop because we don't want to // choose another ancestor table as this row's table. - if (parent->isTable()) - return parent->isAccessibilityTable() ? toAccessibilityTable(parent) : 0; + if (is<AccessibilityTable>(*parent)) { + auto& parentTable = downcast<AccessibilityTable>(*parent); + if (parentTable.isExposableThroughAccessibility()) + return &parentTable; + if (parentTable.node()) + break; + } } - return 0; + return nullptr; } AccessibilityObject* AccessibilityTableRow::headerObject() { if (!m_renderer || !m_renderer->isTableRow()) - return 0; + return nullptr; const auto& rowChildren = children(); if (!rowChildren.size()) - return 0; + return nullptr; // check the first element in the row to see if it is a TH element AccessibilityObject* cell = rowChildren[0].get(); - if (!cell->isTableCell()) - return 0; + if (!is<AccessibilityTableCell>(*cell)) + return nullptr; - RenderObject* cellRenderer = toAccessibilityTableCell(cell)->renderer(); + RenderObject* cellRenderer = downcast<AccessibilityTableCell>(*cell).renderer(); if (!cellRenderer) - return 0; + return nullptr; Node* cellNode = cellRenderer->node(); if (!cellNode || !cellNode->hasTagName(thTag)) - return 0; + return nullptr; return cell; } +void AccessibilityTableRow::addChildren() +{ + AccessibilityRenderObject::addChildren(); + + // "ARIA 1.1, If the set of columns which is present in the DOM is contiguous, and if there are no cells which span more than one row or + // column in that set, then authors may place aria-colindex on each row, setting the value to the index of the first column of the set." + // Update child cells' ariaColIndex if there's an aria-colindex value set for the row. So the cell doesn't have to go through the siblings + // to calculate the index. + int colIndex = ariaColumnIndex(); + if (colIndex == -1) + return; + + unsigned index = 0; + for (const auto& cell : children()) { + if (is<AccessibilityTableCell>(*cell)) + downcast<AccessibilityTableCell>(*cell).setARIAColIndexFromRow(colIndex + index); + index++; + } +} + +int AccessibilityTableRow::ariaColumnIndex() const +{ + const AtomicString& colIndexValue = getAttribute(aria_colindexAttr); + if (colIndexValue.toInt() >= 1) + return colIndexValue.toInt(); + + return -1; +} + +int AccessibilityTableRow::ariaRowIndex() const +{ + const AtomicString& rowIndexValue = getAttribute(aria_rowindexAttr); + if (rowIndexValue.toInt() >= 1) + return rowIndexValue.toInt(); + + return -1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.h b/Source/WebCore/accessibility/AccessibilityTableRow.h index 3eddfea1f..ed6de91cd 100644 --- a/Source/WebCore/accessibility/AccessibilityTableRow.h +++ b/Source/WebCore/accessibility/AccessibilityTableRow.h @@ -10,7 +10,7 @@ * 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. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,8 +26,7 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilityTableRow_h -#define AccessibilityTableRow_h +#pragma once #include "AccessibilityRenderObject.h" @@ -36,14 +35,9 @@ namespace WebCore { class AccessibilityTable; class AccessibilityTableRow : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTableRow(RenderObject*); public: - static PassRefPtr<AccessibilityTableRow> create(RenderObject*); + static Ref<AccessibilityTableRow> create(RenderObject*); virtual ~AccessibilityTableRow(); - - virtual bool isTableRow() const override; // retrieves the "row" header (a th tag in the rightmost column) virtual AccessibilityObject* headerObject(); @@ -56,18 +50,24 @@ public: // in the row, but their col/row spans overlap into it void appendChild(AccessibilityObject*); + void addChildren() override; + + int ariaColumnIndex() const; + int ariaRowIndex() const; + protected: - virtual AccessibilityRole determineAccessibilityRole() override; + explicit AccessibilityTableRow(RenderObject*); + + AccessibilityRole determineAccessibilityRole() final; private: - int m_rowIndex; - - virtual AccessibilityObject* observableObject() const override; - virtual bool computeAccessibilityIsIgnored() const override; -}; + bool isTableRow() const final; + AccessibilityObject* observableObject() const final; + bool computeAccessibilityIsIgnored() const final; -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityTableRow, isTableRow()) + int m_rowIndex; +}; -} // namespace WebCore +} // namespace WebCore -#endif // AccessibilityTableRow_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableRow, isTableRow()) diff --git a/Source/WebCore/accessibility/AccessibilityTree.cpp b/Source/WebCore/accessibility/AccessibilityTree.cpp new file mode 100644 index 000000000..07f313658 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTree.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AccessibilityTree.h" + +#include "AXObjectCache.h" +#include "AccessibilityTreeItem.h" +#include "Element.h" +#include "HTMLNames.h" + +#include <wtf/Deque.h> + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTree::AccessibilityTree(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityTree::~AccessibilityTree() +{ +} + +Ref<AccessibilityTree> AccessibilityTree::create(RenderObject* renderer) +{ + return adoptRef(*new AccessibilityTree(renderer)); +} + +bool AccessibilityTree::computeAccessibilityIsIgnored() const +{ + return accessibilityIsIgnoredByDefault(); +} + +AccessibilityRole AccessibilityTree::determineAccessibilityRole() +{ + if ((m_ariaRole = determineAriaRoleAttribute()) != TreeRole) + return AccessibilityRenderObject::determineAccessibilityRole(); + + return isTreeValid() ? TreeRole : GroupRole; +} + +bool AccessibilityTree::nodeHasTreeItemChild(Node& node) const +{ + for (auto* child = node.firstChild(); child; child = child->nextSibling()) { + if (nodeHasRole(child, "treeitem")) + return true; + } + return false; +} + +bool AccessibilityTree::isTreeValid() const +{ + // A valid tree can only have treeitem or group of treeitems as a child + // http://www.w3.org/TR/wai-aria/roles#tree + + Node* node = this->node(); + if (!node) + return false; + + Deque<Node*> queue; + for (auto* child = node->firstChild(); child; child = child->nextSibling()) + queue.append(child); + + while (!queue.isEmpty()) { + auto child = queue.takeFirst(); + + if (!is<Element>(*child)) + continue; + if (nodeHasRole(child, "treeitem")) + continue; + if (nodeHasRole(child, "presentation")) { + if (!nodeHasTreeItemChild(*child)) + return false; + continue; + } + if (!nodeHasRole(child, "group")) + return false; + + for (auto* groupChild = child->firstChild(); groupChild; groupChild = groupChild->nextSibling()) + queue.append(groupChild); + } + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilitySearchFieldButtons.h b/Source/WebCore/accessibility/AccessibilityTree.h index a00e70391..35c7f14f7 100644 --- a/Source/WebCore/accessibility/AccessibilitySearchFieldButtons.h +++ b/Source/WebCore/accessibility/AccessibilityTree.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,6 +10,9 @@ * 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -23,29 +26,26 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef AccessibilitySearchFieldButtons_h -#define AccessibilitySearchFieldButtons_h + +#pragma once #include "AccessibilityRenderObject.h" namespace WebCore { - -class AccessibilitySearchFieldCancelButton final : public AccessibilityRenderObject { -public: - static PassRefPtr<AccessibilitySearchFieldCancelButton> create(RenderObject*); - virtual String accessibilityDescription() const override; - virtual void accessibilityText(Vector<AccessibilityText>&) override; - virtual bool press() const override; - virtual AccessibilityRole roleValue() const override { return ButtonRole; } - -protected: - virtual bool computeAccessibilityIsIgnored() const override; +class AccessibilityTree final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityTree> create(RenderObject*); + virtual ~AccessibilityTree(); private: - explicit AccessibilitySearchFieldCancelButton(RenderObject*); + explicit AccessibilityTree(RenderObject*); + bool computeAccessibilityIsIgnored() const override; + AccessibilityRole determineAccessibilityRole() override; + bool isTreeValid() const; + bool nodeHasTreeItemChild(Node&) const; }; - + } // namespace WebCore -#endif // AccessibilitySearchFieldButtons_h +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTree, isTree()) diff --git a/Source/WebCore/accessibility/AccessibilitySearchFieldButtons.cpp b/Source/WebCore/accessibility/AccessibilityTreeItem.cpp index c7c949c2d..cd36c38bd 100644 --- a/Source/WebCore/accessibility/AccessibilitySearchFieldButtons.cpp +++ b/Source/WebCore/accessibility/AccessibilityTreeItem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,6 +10,9 @@ * 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED @@ -24,53 +27,39 @@ */ #include "config.h" -#include "AccessibilitySearchFieldButtons.h" +#include "AccessibilityTreeItem.h" -#include "LocalizedStrings.h" -#include "RenderObject.h" +#include "AXObjectCache.h" +#include "HTMLNames.h" namespace WebCore { - -PassRefPtr<AccessibilitySearchFieldCancelButton> AccessibilitySearchFieldCancelButton::create(RenderObject* renderer) -{ - return adoptRef(new AccessibilitySearchFieldCancelButton(renderer)); -} - -AccessibilitySearchFieldCancelButton::AccessibilitySearchFieldCancelButton(RenderObject* renderer) + +using namespace HTMLNames; + +AccessibilityTreeItem::AccessibilityTreeItem(RenderObject* renderer) : AccessibilityRenderObject(renderer) { } - -String AccessibilitySearchFieldCancelButton::accessibilityDescription() const + +AccessibilityTreeItem::~AccessibilityTreeItem() { -#if PLATFORM(IOS) - return String(); -#else - return AXSearchFieldCancelButtonText(); -#endif } - -void AccessibilitySearchFieldCancelButton::accessibilityText(Vector<AccessibilityText>& textOrder) + +Ref<AccessibilityTreeItem> AccessibilityTreeItem::create(RenderObject* renderer) { - textOrder.append(AccessibilityText(accessibilityDescription(), AlternativeText)); + return adoptRef(*new AccessibilityTreeItem(renderer)); } -bool AccessibilitySearchFieldCancelButton::press() const +AccessibilityRole AccessibilityTreeItem::determineAccessibilityRole() { - Node* node = this->node(); - if (!node || !node->isElementNode()) - return false; - Element* element = toElement(node); - // The default event handler on SearchFieldCancelButtonElement requires hover. - element->setHovered(true); - element->accessKeyAction(true); - return true; -} + // Walk the parent chain looking for a parent that is a tree. A treeitem is + // only considered valid if it is in a tree. + AccessibilityObject* parent = nullptr; + for (parent = parentObject(); parent && !parent->isTree(); parent = parent->parentObject()) { } + m_isTreeItemValid = parent; -bool AccessibilitySearchFieldCancelButton::computeAccessibilityIsIgnored() const -{ - return accessibilityIsIgnoredByDefault(); + return AccessibilityRenderObject::determineAccessibilityRole(); } - + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTreeItem.h b/Source/WebCore/accessibility/AccessibilityTreeItem.h new file mode 100644 index 000000000..94038589a --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTreeItem.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#pragma once + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityTreeItem final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityTreeItem> create(RenderObject*); + virtual ~AccessibilityTreeItem(); + + bool shouldIgnoreAttributeRole() const override { return !m_isTreeItemValid; } + +private: + explicit AccessibilityTreeItem(RenderObject*); + AccessibilityRole determineAccessibilityRole() override; + bool m_isTreeItemValid; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTreeItem, isTreeItem()) diff --git a/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp b/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp index 753f761e4..85e0c64d5 100644 --- a/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp +++ b/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp @@ -30,7 +30,8 @@ #include "Range.h" #include "TextIterator.h" #include "WebKitAccessibleWrapperAtk.h" -#include <wtf/gobject/GRefPtr.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/glib/GRefPtr.h> #include <wtf/text/CString.h> namespace WebCore { @@ -75,7 +76,19 @@ void AXObjectCache::attachWrapper(AccessibilityObject* obj) if (!document || document->childNeedsStyleRecalc()) return; - // Don't emit the signal for objects that we already know won't be exposed directly. + // Don't emit the signal when the actual object being added is not going to be exposed. + if (obj->accessibilityIsIgnoredByDefault()) + return; + + // Don't emit the signal if the object being added is not -- or not yet -- rendered, + // which can occur in nested iframes. In these instances we don't want to ignore the + // child. But if an assistive technology is listening, AT-SPI2 will attempt to create + // and cache the state set for the child upon emission of the signal. If the object + // has not yet been rendered, this will result in a crash. + if (!obj->renderer()) + return; + + // Don't emit the signal for objects whose parents won't be exposed directly. AccessibilityObject* coreParent = obj->parentObjectUnignored(); if (!coreParent || coreParent->accessibilityIsIgnoredByDefault()) return; @@ -119,8 +132,8 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // focused object and its associated list object, as per previous // calls to this function, in order to properly decide whether to // emit some signals or not. - DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldListObject, ()); - DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldFocusedObject, ()); + static NeverDestroyed<RefPtr<AccessibilityObject>> oldListObject; + static NeverDestroyed<RefPtr<AccessibilityObject>> oldFocusedObject; // Only list boxes and menu lists supported so far. if (!object || !(object->isListBox() || object->isMenuList())) @@ -128,21 +141,19 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // Only support HTML select elements so far (ARIA selectors not supported). Node* node = object->node(); - if (!node || !isHTMLSelectElement(node)) + if (!is<HTMLSelectElement>(node)) return; // Emit signal from the listbox's point of view first. g_signal_emit_by_name(object->wrapper(), "selection-changed"); // Find the item where the selection change was triggered from. - HTMLSelectElement* select = toHTMLSelectElement(node); - if (!select) - return; - int changedItemIndex = select->activeSelectionStartListIndex(); + HTMLSelectElement& select = downcast<HTMLSelectElement>(*node); + int changedItemIndex = select.activeSelectionStartListIndex(); AccessibilityObject* listObject = getListObject(object); if (!listObject) { - oldListObject = 0; + oldListObject.get() = nullptr; return; } @@ -154,11 +165,11 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // Ensure the current list object is the same than the old one so // further comparisons make sense. Otherwise, just reset // oldFocusedObject so it won't be taken into account. - if (oldListObject != listObject) - oldFocusedObject = 0; + if (oldListObject.get() != listObject) + oldFocusedObject.get() = nullptr; - AtkObject* axItem = item ? item->wrapper() : 0; - AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0; + AtkObject* axItem = item ? item->wrapper() : nullptr; + AtkObject* axOldFocusedObject = oldFocusedObject.get() ? oldFocusedObject.get()->wrapper() : nullptr; // Old focused object just lost focus, so emit the events. if (axOldFocusedObject && axItem != axOldFocusedObject) { @@ -179,8 +190,8 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) } // Update pointers to the previously involved objects. - oldListObject = listObject; - oldFocusedObject = item; + oldListObject.get() = listObject; + oldFocusedObject.get() = item; } void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification) @@ -191,7 +202,7 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX switch (notification) { case AXCheckedStateChanged: - if (!coreObject->isCheckboxOrRadio()) + if (!coreObject->isCheckboxOrRadio() && !coreObject->isSwitch()) return; atk_object_notify_state_change(axObject, ATK_STATE_CHECKED, coreObject->isChecked()); break; @@ -212,7 +223,13 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX propertyValues.property_name = "accessible-value"; memset(&propertyValues.new_value, 0, sizeof(GValue)); +#if ATK_CHECK_VERSION(2,11,92) + double value; + atk_value_get_value_and_text(ATK_VALUE(axObject), &value, nullptr); + g_value_set_double(g_value_init(&propertyValues.new_value, G_TYPE_DOUBLE), value); +#else atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value); +#endif g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL); } @@ -232,7 +249,7 @@ void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* obje if (!object || text.isEmpty()) return; - AccessibilityObject* parentObject = object->parentObjectUnignored(); + AccessibilityObject* parentObject = object->isNonNativeTextControl() ? object : object->parentObjectUnignored(); if (!parentObject) return; @@ -251,12 +268,15 @@ void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* obje // Select the right signal to be emitted CString detail; switch (textChange) { - case AXObjectCache::AXTextInserted: + case AXTextInserted: detail = "text-insert"; break; - case AXObjectCache::AXTextDeleted: + case AXTextDeleted: detail = "text-remove"; break; + case AXTextAttributesChanged: + detail = "text-attributes-changed"; + break; } String textToEmit = text; diff --git a/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp b/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp index 51e1b787a..238248430 100644 --- a/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp +++ b/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp @@ -21,9 +21,13 @@ #include "config.h" #include "AccessibilityObject.h" -#include "HTMLNames.h" -#include "RenderObject.h" +#include "HTMLSpanElement.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderIterator.h" +#include "RenderTableCell.h" #include "RenderText.h" +#include "TextControlInnerElements.h" #include <glib-object.h> #if HAVE(ACCESSIBILITY) @@ -41,10 +45,11 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO if (!parent) return DefaultBehavior; - AccessibilityRole role = roleValue(); - if (role == HorizontalRuleRole) - return IncludeObject; + // If the author has provided a role, platform-specific inclusion likely doesn't apply. + if (ariaRoleAttribute() != UnknownRole) + return DefaultBehavior; + AccessibilityRole role = roleValue(); // We expose the slider as a whole but not its value indicator. if (role == SliderThumbRole) return IgnoreObject; @@ -59,12 +64,13 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO return IgnoreObject; // Include all tables, even layout tables. The AT can decide what to do with each. - if (role == CellRole || role == TableRole) + if (role == CellRole || role == TableRole || role == ColumnHeaderRole || role == RowHeaderRole) return IncludeObject; // The object containing the text should implement AtkText itself. + // However, WebCore also maps ARIA's "text" role to the StaticTextRole. if (role == StaticTextRole) - return IgnoreObject; + return ariaRoleAttribute() != UnknownRole ? DefaultBehavior : IgnoreObject; // Include all list items, regardless they have or not inline children if (role == ListItemRole) @@ -79,52 +85,72 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO if (role == UnknownRole) return IgnoreObject; + if (role == InlineRole) + return IncludeObject; + // Lines past this point only make sense for AccessibilityRenderObjects. RenderObject* renderObject = renderer(); if (!renderObject) return DefaultBehavior; + // We always want to include paragraphs that have rendered content. + // WebCore Accessibility does so unless there is a RenderBlock child. + if (role == ParagraphRole) { + auto child = childrenOfType<RenderBlock>(downcast<RenderElement>(*renderObject)).first(); + return child ? IncludeObject : DefaultBehavior; + } + + // We always want to include table cells (layout and CSS) that have rendered text content. + if (is<RenderTableCell>(renderObject)) { + for (const auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(*renderObject))) { + if (is<RenderInline>(child) || is<RenderText>(child) || is<HTMLSpanElement>(child.node())) + return IncludeObject; + } + return DefaultBehavior; + } + + if (renderObject->isAnonymousBlock()) { + // The text displayed by an ARIA menu item is exposed through the accessible name. + if (parent->isMenuItem()) + return IgnoreObject; + + // The text displayed in headings is typically exposed in the heading itself. + if (parent->isHeading()) + return IgnoreObject; + + // The text displayed in list items is typically exposed in the list item itself. + if (parent->isListItem()) + return IgnoreObject; + + // The text displayed in links is typically exposed in the link itself. + if (parent->isLink()) + return IgnoreObject; + + // FIXME: This next one needs some further consideration. But paragraphs are not + // typically huge (like divs). And ignoring anonymous block children of paragraphs + // will preserve existing behavior. + if (parent->roleValue() == ParagraphRole) + return IgnoreObject; + + return DefaultBehavior; + } + + Node* node = renderObject->node(); + if (!node) + return DefaultBehavior; + // We don't want <span> elements to show up in the accessibility hierarchy unless // we have good reasons for that (e.g. focusable or visible because of containing // a meaningful accessible name, maybe set through ARIA), so we can use // atk_component_grab_focus() to set the focus to it. - Node* node = renderObject->node(); - if (node && node->hasTagName(HTMLNames::spanTag) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion()) + if (is<HTMLSpanElement>(node) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion() && !supportsARIAAttributes()) return IgnoreObject; - // Given a paragraph or div containing a non-nested anonymous block, WebCore - // ignores the paragraph or div and includes the block. We want the opposite: - // ATs are expecting accessible objects associated with textual elements. They - // usually have no need for the anonymous block. And when the wrong objects - // get included or ignored, needed accessibility signals do not get emitted. - if (role == ParagraphRole || role == DivRole) { - // Don't call textUnderElement() here, because it's slow and it can - // crash when called while we're in the middle of a subtree being deleted. - if (!renderObject->firstChildSlow()) - return DefaultBehavior; - - if (!parent->renderer() || parent->renderer()->isAnonymousBlock()) - return DefaultBehavior; - - for (RenderObject* r = renderObject->firstChildSlow(); r; r = r->nextSibling()) { - if (r->isAnonymousBlock()) - return IncludeObject; - } - } - - // Block spans result in objects of ATK_ROLE_PANEL which are almost always unwanted. - // However, if we ignore block spans whose parent is the body, the child controls - // will become immediate children of the ATK_ROLE_DOCUMENT_FRAME and any text will - // become text within the document frame itself. This ultimately may be what we want - // and would largely be consistent with what we see from Gecko. However, ignoring - // spans whose parent is the body changes the current behavior we see from WebCore. - // Until we have sufficient time to properly analyze these cases, we will defer to - // WebCore. We only check that the parent is not aria because we do not expect - // anonymous blocks which are aria-related to themselves have an aria role, nor - // have we encountered instances where the parent of an anonymous block also lacked - // an aria role but the grandparent had one. - if (renderObject && renderObject->isAnonymousBlock() && !parent->renderer()->isBody() - && parent->ariaRoleAttribute() == UnknownRole) + // If we include TextControlInnerTextElement children, changes to those children + // will result in focus and text notifications that suggest the user is no longer + // in the control. This can be especially problematic for screen reader users with + // key echo enabled when typing in a password input. + if (is<TextControlInnerTextElement>(node)) return IgnoreObject; return DefaultBehavior; @@ -157,7 +183,7 @@ bool AccessibilityObject::allowsTextRanges() const // Check roles as the last fallback mechanism. AccessibilityRole role = roleValue(); - return role == ParagraphRole || role == LabelRole || role == DivRole || role == FormRole; + return role == ParagraphRole || role == LabelRole || role == DivRole || role == FormRole || role == PreRole; } unsigned AccessibilityObject::getLengthForTextRange() const @@ -169,9 +195,9 @@ unsigned AccessibilityObject::getLengthForTextRange() const // Gtk ATs need this for all text objects; not just text controls. Node* node = this->node(); - RenderObject* renderer = node ? node->renderer() : 0; - if (renderer && renderer->isText()) - textLength = toRenderText(*renderer).textLength(); + RenderObject* renderer = node ? node->renderer() : nullptr; + if (is<RenderText>(renderer)) + textLength = downcast<RenderText>(*renderer).textLength(); // Get the text length from the elements under the // accessibility object if the value is still zero. diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp index 0e4a0c8ed..9df03efa9 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp @@ -231,10 +231,10 @@ static gint getRangeLengthForObject(AccessibilityObject* obj, Range* range) return baseLength; RenderObject* renderer = markerObj->renderer(); - if (!renderer || !renderer->isListMarker()) + if (!is<RenderListMarker>(renderer)) return baseLength; - RenderListMarker& marker = toRenderListMarker(*renderer); + auto& marker = downcast<RenderListMarker>(*renderer); return baseLength + marker.text().length() + marker.suffix().length(); } @@ -385,7 +385,7 @@ static void webkitAccessibleHyperlinkClassInit(AtkHyperlinkClass* klass) static void webkitAccessibleHyperlinkInit(AtkHyperlink* link) { WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv = WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(link); - WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = 0; + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = nullptr; } GType webkitAccessibleHyperlinkGetType() diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.h b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.h index 59ca73cea..0533156e7 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.h @@ -17,8 +17,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleHyperlink_h -#define WebKitAccessibleHyperlink_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -60,6 +59,4 @@ WebCore::AccessibilityObject* webkitAccessibleHyperlinkGetAccessibilityObject(We G_END_DECLS -#endif // WebKitAccessibleHyperlink_h - -#endif +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.h index a0de63fd7..5cd75287b 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceAction_h -#define WebKitAccessibleInterfaceAction_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,6 +27,4 @@ void webkitAccessibleActionInterfaceInit(AtkActionIface*); -#endif - -#endif // WebKitAccessibleInterfaceAction_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp index b93d8c39d..e86f23098 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp @@ -85,7 +85,7 @@ static void webkitAccessibleComponentGetExtents(AtkComponent* component, gint* x g_return_if_fail(ATK_IS_COMPONENT(component)); returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(component)); - IntRect rect = pixelSnappedIntRect(core(component)->elementRect()); + IntRect rect = snappedIntRect(core(component)->elementRect()); contentsRelativeToAtkCoordinateType(core(component), coordType, rect, x, y, width, height); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.h index 4ccfd97a4..209d51e7e 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceComponent_h -#define WebKitAccessibleInterfaceComponent_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,6 +27,4 @@ void webkitAccessibleComponentInterfaceInit(AtkComponentIface*); -#endif - -#endif // WebKitAccessibleInterfaceComponent_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp index 5a52f8130..6752d12d3 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp @@ -89,7 +89,7 @@ static AtkAttributeSet* webkitAccessibleDocumentGetAttributes(AtkDocument* docum g_return_val_if_fail(ATK_IS_DOCUMENT(document), 0); returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(document), 0); - AtkAttributeSet* attributeSet = 0; + AtkAttributeSet* attributeSet = nullptr; const gchar* attributes[] = { "DocType", "Encoding", "URI" }; for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) { diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.h index 5ee946962..6fab32ada 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceDocument_h -#define WebKitAccessibleInterfaceDocument_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleDocumentInterfaceInit(AtkDocumentIface*); -#endif -#endif // WebKitAccessibleInterfaceDocument_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.h index da7669130..6414e2d88 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceEditableText_h -#define WebKitAccessibleInterfaceEditableText_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleEditableTextInterfaceInit(AtkEditableTextIface*); -#endif -#endif // WebKitAccessibleInterfaceEditableText_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHyperlinkImpl.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHyperlinkImpl.h index 56a8c67ac..c9eebac5b 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHyperlinkImpl.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHyperlinkImpl.h @@ -17,8 +17,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceHyperlinkImpl_h -#define WebKitAccessibleInterfaceHyperlinkImpl_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -26,5 +25,4 @@ void webkitAccessibleHyperlinkImplInterfaceInit(AtkHyperlinkImplIface*); -#endif -#endif // WebKitAccessibleInterfaceHyperlinkImpl_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp index 75c045c01..db60218df 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp @@ -70,12 +70,9 @@ static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext) returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(hypertext), 0); const AccessibilityObject::AccessibilityChildrenVector& children = core(hypertext)->children(); - if (!children.size()) - return 0; - gint linksFound = 0; - for (size_t i = 0; i < children.size(); i++) { - AccessibilityObject* coreChild = children.at(i).get(); + for (const auto& child : children) { + AccessibilityObject* coreChild = child.get(); if (!coreChild->accessibilityIsIgnored()) { AtkObject* axObject = coreChild->wrapper(); if (axObject && ATK_IS_HYPERLINK_IMPL(axObject)) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.h index 7a3a0892a..da163af23 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.h @@ -17,8 +17,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceHypertext_h -#define WebKitAccessibleInterfaceHypertext_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -26,6 +25,4 @@ void webkitAccessibleHypertextInterfaceInit(AtkHypertextIface*); -#endif - -#endif // WebKitAccessibleInterfaceHypertext_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp index bab75995a..843969b42 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp @@ -54,7 +54,7 @@ static void webkitAccessibleImageGetImagePosition(AtkImage* image, gint* x, gint g_return_if_fail(ATK_IMAGE(image)); returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(image)); - IntRect rect = pixelSnappedIntRect(core(image)->elementRect()); + IntRect rect = snappedIntRect(core(image)->elementRect()); contentsRelativeToAtkCoordinateType(core(image), coordType, rect, x, y); } @@ -71,7 +71,7 @@ static void webkitAccessibleImageGetImageSize(AtkImage* image, gint* width, gint g_return_if_fail(ATK_IMAGE(image)); returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(image)); - IntSize size = core(image)->pixelSnappedSize(); + IntSize size = snappedIntRect(core(image)->elementRect()).size(); if (width) *width = size.width(); diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.h index 45be65541..4f31640c9 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceImage_h -#define WebKitAccessibleInterfaceImage_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleImageInterfaceInit(AtkImageIface*); -#endif -#endif // WebKitAccessibleInterfaceImage_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp index cb7a3ccff..ec7f8caa5 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp @@ -45,7 +45,7 @@ using namespace WebCore; static AccessibilityObject* core(AtkSelection* selection) { if (!WEBKIT_IS_ACCESSIBLE(selection)) - return 0; + return nullptr; return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(selection)); } @@ -56,7 +56,7 @@ static AccessibilityObject* listObjectForSelection(AtkSelection* selection) // Only list boxes and menu lists supported so far. if (!coreSelection->isListBox() && !coreSelection->isMenuList()) - return 0; + return nullptr; // For list boxes the list object is just itself. if (coreSelection->isListBox()) @@ -67,11 +67,11 @@ static AccessibilityObject* listObjectForSelection(AtkSelection* selection) // of items with role MenuListOptionRole. const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children(); if (!children.size()) - return 0; + return nullptr; AccessibilityObject* listObject = children.at(0).get(); if (!listObject->isMenuListPopup()) - return 0; + return nullptr; return listObject; } @@ -80,50 +80,37 @@ static AccessibilityObject* optionFromList(AtkSelection* selection, gint index) { AccessibilityObject* coreSelection = core(selection); if (!coreSelection || index < 0) - return 0; + return nullptr; // Need to select the proper list object depending on the type. AccessibilityObject* listObject = listObjectForSelection(selection); if (!listObject) - return 0; + return nullptr; const AccessibilityObject::AccessibilityChildrenVector& options = listObject->children(); if (index < static_cast<gint>(options.size())) return options.at(index).get(); - return 0; + return nullptr; } static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint index) { - // i is the ith selection as opposed to the ith child. - AccessibilityObject* coreSelection = core(selection); if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || index < 0) - return 0; - - AccessibilityObject::AccessibilityChildrenVector selectedItems; - if (coreSelection->isListBox()) - coreSelection->selectedChildren(selectedItems); - else if (coreSelection->isMenuList()) { - RenderObject* renderer = coreSelection->renderer(); - if (!renderer) - return 0; - - HTMLSelectElement* selectNode = toHTMLSelectElement(renderer->node()); - int selectedIndex = selectNode->selectedIndex(); - const Vector<HTMLElement*> listItems = selectNode->listItems(); - - if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size())) - return 0; - - return optionFromList(selection, selectedIndex); - } - - if (index < static_cast<gint>(selectedItems.size())) - return selectedItems.at(index).get(); + return nullptr; + + // This method provides the functionality expected by atk_selection_ref_selection(). + // According to the ATK documentation for this method, the index is "a gint specifying + // the index in the selection set. (e.g. the ith selection as opposed to the ith child)." + // There is different API, namely atk_object_ref_accessible_child(), when the ith child + // from the set of all children is sought. + AccessibilityObject::AccessibilityChildrenVector options; + coreSelection->selectedChildren(options); + if (index < static_cast<gint>(options.size())) + return options.at(index).get(); - return 0; + return nullptr; } static gboolean webkitAccessibleSelectionAddSelection(AtkSelection* selection, gint index) @@ -154,20 +141,20 @@ static gboolean webkitAccessibleSelectionClearSelection(AtkSelection* selection) return FALSE; AccessibilityObject::AccessibilityChildrenVector selectedItems; - if (coreSelection->isListBox() || coreSelection->isMenuList()) { + if (is<AccessibilityListBox>(*coreSelection)) { // Set the list of selected items to an empty list; then verify that it worked. - AccessibilityListBox* listBox = toAccessibilityListBox(coreSelection); - listBox->setSelectedChildren(selectedItems); - listBox->selectedChildren(selectedItems); - return !selectedItems.size(); + auto& listBox = downcast<AccessibilityListBox>(*coreSelection); + listBox.setSelectedChildren(selectedItems); + listBox.selectedChildren(selectedItems); + return selectedItems.isEmpty(); } return FALSE; } static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, gint index) { - g_return_val_if_fail(ATK_SELECTION(selection), 0); - returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), 0); + g_return_val_if_fail(ATK_SELECTION(selection), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), nullptr); AccessibilityObject* option = optionFromSelection(selection, index); if (option) { @@ -176,7 +163,7 @@ static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, return child; } - return 0; + return nullptr; } static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection) @@ -188,22 +175,18 @@ static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection) if (!coreSelection || !coreSelection->isAccessibilityRenderObject()) return 0; - if (coreSelection->isListBox()) { - AccessibilityObject::AccessibilityChildrenVector selectedItems; - coreSelection->selectedChildren(selectedItems); - return static_cast<gint>(selectedItems.size()); - } - if (coreSelection->isMenuList()) { RenderObject* renderer = coreSelection->renderer(); if (!renderer) return 0; - int selectedIndex = toHTMLSelectElement(renderer->node())->selectedIndex(); - return selectedIndex >= 0 && selectedIndex < static_cast<int>(toHTMLSelectElement(renderer->node())->listItems().size()); + int selectedIndex = downcast<HTMLSelectElement>(renderer->node())->selectedIndex(); + return selectedIndex >= 0 && selectedIndex < static_cast<int>(downcast<HTMLSelectElement>(renderer->node())->listItems().size()); } - return 0; + AccessibilityObject::AccessibilityChildrenVector selectedItems; + coreSelection->selectedChildren(selectedItems); + return static_cast<gint>(selectedItems.size()); } static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection, gint index) @@ -231,7 +214,6 @@ static gboolean webkitAccessibleSelectionRemoveSelection(AtkSelection* selection if (!coreSelection) return FALSE; - // TODO: This is only getting called if i == 0. What is preventing the rest? AccessibilityObject* option = optionFromSelection(selection, index); if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) { option->setSelected(false); @@ -250,12 +232,12 @@ static gboolean webkitAccessibleSelectionSelectAllSelection(AtkSelection* select if (!coreSelection || !coreSelection->isMultiSelectable()) return FALSE; - if (coreSelection->isListBox()) { + if (is<AccessibilityListBox>(*coreSelection)) { const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children(); - AccessibilityListBox* listBox = toAccessibilityListBox(coreSelection); - listBox->setSelectedChildren(children); + AccessibilityListBox& listBox = downcast<AccessibilityListBox>(*coreSelection); + listBox.setSelectedChildren(children); AccessibilityObject::AccessibilityChildrenVector selectedItems; - listBox->selectedChildren(selectedItems); + listBox.selectedChildren(selectedItems); return selectedItems.size() == children.size(); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.h index 6500029d0..e0f50aa82 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceSelection_h -#define WebKitAccessibleInterfaceSelection_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleSelectionInterfaceInit(AtkSelectionIface*); -#endif -#endif // WebKitAccessibleInterfaceSelection_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp index f6ed19c0a..a03443afc 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp @@ -37,7 +37,6 @@ #include "AccessibilityObject.h" #include "AccessibilityTable.h" #include "AccessibilityTableCell.h" -#include "HTMLSelectElement.h" #include "HTMLTableCaptionElement.h" #include "HTMLTableElement.h" #include "RenderElement.h" @@ -50,7 +49,7 @@ using namespace WebCore; static AccessibilityObject* core(AtkTable* table) { if (!WEBKIT_IS_ACCESSIBLE(table)) - return 0; + return nullptr; return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(table)); } @@ -58,9 +57,9 @@ static AccessibilityObject* core(AtkTable* table) static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) { AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return toAccessibilityTable(accTable)->cellForColumnAndRow(column, row); - return 0; + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).cellForColumnAndRow(column, row); + return nullptr; } static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable) @@ -79,15 +78,13 @@ static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTabl static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index) { AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { + if (is<AccessibilityTable>(*accTable)) { AccessibilityObject::AccessibilityChildrenVector allCells; - toAccessibilityTable(accTable)->cells(allCells); - if (0 <= index && static_cast<unsigned>(index) < allCells.size()) { - AccessibilityObject* accCell = allCells.at(index).get(); - return toAccessibilityTableCell(accCell); - } + downcast<AccessibilityTable>(*accTable).cells(allCells); + if (0 <= index && static_cast<unsigned>(index) < allCells.size()) + return downcast<AccessibilityTableCell>(allCells[index].get()); } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableRefAt(AtkTable* table, gint row, gint column) @@ -114,7 +111,7 @@ static gint webkitAccessibleTableGetIndexAt(AtkTable* table, gint row, gint colu returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), -1); AccessibilityTableCell* axCell = cell(table, row, column); - AccessibilityTable* axTable = toAccessibilityTable(core(table)); + AccessibilityTable* axTable = downcast<AccessibilityTable>(core(table)); return cellIndex(axCell, axTable); } @@ -152,8 +149,8 @@ static gint webkitAccessibleTableGetNColumns(AtkTable* table) returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return toAccessibilityTable(accTable)->columnCount(); + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).columnCount(); return 0; } @@ -163,8 +160,8 @@ static gint webkitAccessibleTableGetNRows(AtkTable* table) returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return toAccessibilityTable(accTable)->rowCount(); + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).rowCount(); return 0; } @@ -202,18 +199,18 @@ static AtkObject* webkitAccessibleTableGetColumnHeader(AtkTable* table, gint col returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { + if (is<AccessibilityTable>(*accTable)) { AccessibilityObject::AccessibilityChildrenVector columnHeaders; - toAccessibilityTable(accTable)->columnHeaders(columnHeaders); + downcast<AccessibilityTable>(*accTable).columnHeaders(columnHeaders); for (const auto& columnHeader : columnHeaders) { std::pair<unsigned, unsigned> columnRange; - toAccessibilityTableCell(columnHeader.get())->columnIndexRange(columnRange); + downcast<AccessibilityTableCell>(*columnHeader).columnIndexRange(columnRange); if (columnRange.first <= static_cast<unsigned>(column) && static_cast<unsigned>(column) < columnRange.first + columnRange.second) return columnHeader->wrapper(); } } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableGetRowHeader(AtkTable* table, gint row) @@ -222,35 +219,35 @@ static AtkObject* webkitAccessibleTableGetRowHeader(AtkTable* table, gint row) returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { + if (is<AccessibilityTable>(*accTable)) { AccessibilityObject::AccessibilityChildrenVector rowHeaders; - toAccessibilityTable(accTable)->rowHeaders(rowHeaders); + downcast<AccessibilityTable>(*accTable).rowHeaders(rowHeaders); for (const auto& rowHeader : rowHeaders) { std::pair<unsigned, unsigned> rowRange; - toAccessibilityTableCell(rowHeader.get())->rowIndexRange(rowRange); + downcast<AccessibilityTableCell>(*rowHeader).rowIndexRange(rowRange); if (rowRange.first <= static_cast<unsigned>(row) && static_cast<unsigned>(row) < rowRange.first + rowRange.second) return rowHeader->wrapper(); } } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableGetCaption(AtkTable* table) { - g_return_val_if_fail(ATK_TABLE(table), 0); - returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + g_return_val_if_fail(ATK_TABLE(table), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), nullptr); AccessibilityObject* accTable = core(table); if (accTable->isAccessibilityRenderObject()) { Node* node = accTable->node(); - if (node && isHTMLTableElement(node)) { - HTMLTableCaptionElement* caption = toHTMLTableElement(node)->caption(); + if (is<HTMLTableElement>(node)) { + HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*node).caption(); if (caption) return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->element())->wrapper(); } } - return 0; + return nullptr; } static const gchar* webkitAccessibleTableGetColumnDescription(AtkTable* table, gint column) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.h index 5bddf39c2..eacc4dfea 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceTable_h -#define WebKitAccessibleInterfaceTable_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleTableInterfaceInit(AtkTableIface*); -#endif -#endif // WebKitAccessibleInterfaceTable_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp new file mode 100644 index 000000000..3d1e96211 --- /dev/null +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 Samsung Electronics. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitAccessibleInterfaceTableCell.h" + +#if HAVE(ACCESSIBILITY) +#if ATK_CHECK_VERSION(2,11,90) +#include "AccessibilityObject.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "WebKitAccessibleUtil.h" +#include "WebKitAccessibleWrapperAtk.h" + +using namespace WebCore; + +static GPtrArray* convertToGPtrArray(const AccessibilityObject::AccessibilityChildrenVector& children) +{ + GPtrArray* array = g_ptr_array_new(); + for (const auto& child : children) { + if (AtkObject* atkObject = child->wrapper()) + g_ptr_array_add(array, atkObject); + } + return array; +} + +static AccessibilityObject* core(AtkTableCell* cell) +{ + if (!WEBKIT_IS_ACCESSIBLE(cell)) + return nullptr; + + return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(cell)); +} + +GPtrArray* webkitAccessibleTableCellGetColumnHeaderCells(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return nullptr; + + AccessibilityObject::AccessibilityChildrenVector columnHeaders; + downcast<AccessibilityTableCell>(*axObject).columnHeaders(columnHeaders); + + return convertToGPtrArray(columnHeaders); +} + +GPtrArray* webkitAccessibleTableCellGetRowHeaderCells(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return nullptr; + + AccessibilityObject::AccessibilityChildrenVector rowHeaders; + downcast<AccessibilityTableCell>(*axObject).rowHeaders(rowHeaders); + + return convertToGPtrArray(rowHeaders); +} + +gint webkitAccessibleTableCellGetColumnSpan(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), 0); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return 0; + + std::pair<unsigned, unsigned> columnRange; + downcast<AccessibilityTableCell>(*axObject).columnIndexRange(columnRange); + + return columnRange.second; +} + +gint webkitAccessibleTableCellGetRowSpan(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), 0); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return 0; + + std::pair<unsigned, unsigned> rowRange; + downcast<AccessibilityTableCell>(*axObject).rowIndexRange(rowRange); + + return rowRange.second; +} + +gboolean webkitAccessibleTableCellGetPosition(AtkTableCell* cell, gint* row, gint* column) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), false); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), false); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return false; + + std::pair<unsigned, unsigned> columnRowRange; + if (row) { + downcast<AccessibilityTableCell>(*axObject).rowIndexRange(columnRowRange); + *row = columnRowRange.first; + } + if (column) { + downcast<AccessibilityTableCell>(*axObject).columnIndexRange(columnRowRange); + *column = columnRowRange.first; + } + + return true; +} + +AtkObject* webkitAccessibleTableCellGetTable(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!axObject || !axObject->isTableCell()) + return nullptr; + + AtkObject* table = atk_object_get_parent(axObject->wrapper()); + if (!table || !ATK_IS_TABLE(table)) + return nullptr; + + return ATK_OBJECT(g_object_ref(table)); +} + +void webkitAccessibleTableCellInterfaceInit(AtkTableCellIface* iface) +{ + iface->get_column_header_cells = webkitAccessibleTableCellGetColumnHeaderCells; + iface->get_row_header_cells = webkitAccessibleTableCellGetRowHeaderCells; + iface->get_column_span = webkitAccessibleTableCellGetColumnSpan; + iface->get_row_span = webkitAccessibleTableCellGetRowSpan; + iface->get_position = webkitAccessibleTableCellGetPosition; + iface->get_table = webkitAccessibleTableCellGetTable; +} + +#endif // ATK_CHECK_VERSION(2,11,90) +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h new file mode 100644 index 000000000..d2f58c6fb --- /dev/null +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 Nuanti Ltd. + * Copyright (C) 2009 Jan Alonzo + * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#if HAVE(ACCESSIBILITY) + +#include <atk/atk.h> + +#if ATK_CHECK_VERSION(2,11,90) +void webkitAccessibleTableCellInterfaceInit(AtkTableCellIface*); +#endif + +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp index 8422d7045..5edcc98f3 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp @@ -36,7 +36,7 @@ #include "AccessibilityObject.h" #include "Document.h" -#include "Font.h" +#include "FontCascade.h" #include "FrameView.h" #include "HTMLParserIdioms.h" #include "HostWindow.h" @@ -51,7 +51,7 @@ #include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" #include "htmlediting.h" -#include <wtf/gobject/GUniquePtr.h> +#include <wtf/glib/GUniquePtr.h> #include <wtf/text/CString.h> using namespace WebCore; @@ -84,9 +84,9 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit return 0; RenderObject* renderer = object->renderer(); - RenderStyle* style = &renderer->style(); + auto* style = &renderer->style(); - AtkAttributeSet* result = 0; + AtkAttributeSet* result = nullptr; GUniquePtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get()); @@ -125,19 +125,19 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit } if (!style->textIndent().isUndefined()) { - int indentation = valueForLength(style->textIndent(), object->size().width(), &renderer->view()); + int indentation = valueForLength(style->textIndent(), object->size().width()); buffer.reset(g_strdup_printf("%i", indentation)); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get()); } - String fontFamilyName = style->font().firstFamily(); + String fontFamilyName = style->fontCascade().firstFamily(); if (fontFamilyName.left(8) == "-webkit-") fontFamilyName = fontFamilyName.substring(8); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data()); int fontWeight = -1; - switch (style->font().weight()) { + switch (style->fontCascade().weight()) { case FontWeight100: fontWeight = 100; break; @@ -192,19 +192,19 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & TextDecorationUnderline) ? "single" : "none"); - result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal"); + result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->fontCascade().italic() ? "italic" : "normal"); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & TextDecorationLineThrough) ? "true" : "false"); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false"); - result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true"); + result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->canSetValueAttribute() ? "true" : "false"); String language = object->language(); if (!language.isEmpty()) result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE), language.utf8().data()); - String invalidStatus = object->invalidStatus().string(); + String invalidStatus = object->invalidStatus(); if (invalidStatus != "false") { // Register the custom attribute for 'aria-invalid' if not done yet. if (atkTextAttributeInvalid == ATK_TEXT_ATTR_INVALID) @@ -231,7 +231,7 @@ static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, A AtkAttributeSet* currentSet = attributeSet1; AtkAttributeSet* found; - AtkAttributeSet* toDelete = 0; + AtkAttributeSet* toDelete = nullptr; while (currentSet) { found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute); @@ -270,8 +270,8 @@ static guint accessibilityObjectLength(const AccessibilityObject* object) // for those cases when it's needed to take it into account // separately (as in getAccessibilityObjectForOffset) RenderObject* renderer = object->renderer(); - if (renderer && renderer->isListMarker()) { - RenderListMarker& marker = toRenderListMarker(*renderer); + if (is<RenderListMarker>(renderer)) { + auto& marker = downcast<RenderListMarker>(*renderer); return marker.text().length() + marker.suffix().length(); } @@ -318,7 +318,7 @@ static const AccessibilityObject* getAccessibilityObjectForOffset(const Accessib return result; } -static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) +static AtkAttributeSet* getRunAttributesFromAccessibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) { const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset); if (!child) { @@ -363,8 +363,8 @@ static int offsetAdjustmentForListItem(const AccessibilityObject* object) // We need to adjust the offsets for the list item marker in // Left-To-Right text, since we expose it together with the text. RenderObject* renderer = object->renderer(); - if (renderer && renderer->isListItem() && renderer->style().direction() == LTR) - return toRenderListItem(renderer)->markerTextWithSuffix().length(); + if (is<RenderListItem>(renderer) && renderer->style().direction() == LTR) + return downcast<RenderListItem>(*renderer).markerTextWithSuffix().length(); return 0; } @@ -495,8 +495,8 @@ static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint int actualEndOffset = endOffset == -1 ? ret.length() : endOffset; if (coreObject->roleValue() == ListItemRole) { RenderObject* objRenderer = coreObject->renderer(); - if (objRenderer && objRenderer->isListItem()) { - String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix(); + if (is<RenderListItem>(objRenderer)) { + String markerText = downcast<RenderListItem>(*objRenderer).markerTextWithSuffix(); ret = objRenderer->style().direction() == LTR ? markerText + ret : ret + markerText; if (endOffset == -1) actualEndOffset = ret.length() + markerText.length(); @@ -601,7 +601,7 @@ static VisibleSelection wordAtPositionForAtkBoundary(const AccessibilityObject* // as when at the beginning of a whitespace range between two "real" words, // since that whitespace is considered a "word" as well. And in case we are // already at the beginning of a "real" word we do not need to look backwards. - if (isStartOfWord(position) && isWhitespace(position.characterBefore())) + if (isStartOfWord(position) && deprecatedIsEditingWhitespace(position.characterBefore())) startPosition = position; else startPosition = previousWordPosition(position); @@ -729,7 +729,7 @@ static bool isWhiteSpaceBetweenSentences(const VisiblePosition& position) if (position.isNull()) return false; - if (!isWhitespace(position.characterAfter())) + if (!deprecatedIsEditingWhitespace(position.characterAfter())) return false; VisiblePosition startOfWhiteSpace = startOfWord(position, RightWordIfOnBoundary); @@ -764,12 +764,12 @@ static VisibleSelection sentenceAtPositionForAtkBoundary(const AccessibilityObje // startOfSentence returns a position after any white space previous to // the sentence, so we might need to adjust that offset for this boundary. - if (isWhitespace(startPosition.characterBefore())) + if (deprecatedIsEditingWhitespace(startPosition.characterBefore())) startPosition = startOfWord(startPosition, LeftWordIfOnBoundary); // endOfSentence returns a position after any white space after the // sentence, so we might need to adjust that offset for this boundary. - if (isWhitespace(endPosition.characterBefore())) + if (deprecatedIsEditingWhitespace(endPosition.characterBefore())) endPosition = startOfWord(endPosition, LeftWordIfOnBoundary); // Finally, do some additional adjustments that might be needed if @@ -1055,7 +1055,7 @@ static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint if (offset == -1) offset = atk_text_get_caret_offset(text); - result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset); + result = getRunAttributesFromAccessibilityObject(coreObject, offset, startOffset, endOffset); if (*startOffset < 0) { *startOffset = offset; @@ -1233,29 +1233,9 @@ static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectio static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset) { - g_return_val_if_fail(ATK_TEXT(text), FALSE); - returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE); - - AccessibilityObject* coreObject = core(text); - if (!coreObject->isAccessibilityRenderObject()) - return FALSE; - - // We need to adjust the offsets for the list item marker. - int offsetAdjustment = offsetAdjustmentForListItem(coreObject); - if (offsetAdjustment) { - if (offset < offsetAdjustment) - return FALSE; - - offset = atkOffsetToWebCoreOffset(text, offset); - } - - PlainTextRange textRange(offset, 0); - VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); - if (range.isNull()) - return FALSE; - - coreObject->setSelectedVisiblePositionRange(range); - return TRUE; + // Internally, setting the caret offset is equivalent to set a zero-length + // selection, so delegate in that implementation and void duplicated code. + return webkitAccessibleTextSetSelection(text, 0, offset, offset); } #if ATK_CHECK_VERSION(2, 10, 0) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.h index d7c7376e4..f9f9de071 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceText_h -#define WebKitAccessibleInterfaceText_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -28,5 +27,4 @@ void webkitAccessibleTextInterfaceInit(AtkTextIface*); -#endif -#endif // WebKitAccessibleInterfaceText_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp index 19c6cf003..ac582fa66 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp @@ -26,6 +26,7 @@ #include "HTMLNames.h" #include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" +#include <wtf/text/CString.h> using namespace WebCore; @@ -37,6 +38,83 @@ static AccessibilityObject* core(AtkValue* value) return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(value)); } +static bool webkitAccessibleSetNewValue(AtkValue* coreValue, const gdouble newValue) +{ + AccessibilityObject* coreObject = core(coreValue); + if (!coreObject->canSetValueAttribute()) + return FALSE; + + // Check value against range limits + double value; + value = std::max(static_cast<double>(coreObject->minValueForRange()), newValue); + value = std::min(static_cast<double>(coreObject->maxValueForRange()), newValue); + + coreObject->setValue(String::number(value)); + return TRUE; +} + +static float webkitAccessibleGetIncrementValue(AccessibilityObject* coreObject) +{ + if (!coreObject->getAttribute(HTMLNames::stepAttr).isEmpty()) + return coreObject->stepValueForRange(); + + // If 'step' attribute is not defined, WebCore assumes a 5% of the + // range between minimum and maximum values. Implicit value of step should be one or larger. + float step = (coreObject->maxValueForRange() - coreObject->minValueForRange()) * 0.05; + return step < 1 ? 1 : step; +} + +#if ATK_CHECK_VERSION(2,11,92) +static void webkitAccessibleGetValueAndText(AtkValue* value, gdouble* currentValue, gchar** alternativeText) +{ + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return; + + if (currentValue) + *currentValue = coreObject->valueForRange(); + if (alternativeText) + *alternativeText = g_strdup_printf("%s", coreObject->valueDescription().utf8().data()); +} + +static double webkitAccessibleGetIncrement(AtkValue* value) +{ + g_return_val_if_fail(ATK_VALUE(value), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value), 0); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return 0; + + return webkitAccessibleGetIncrementValue(coreObject); +} + +static void webkitAccessibleSetValue(AtkValue* value, const gdouble newValue) +{ + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + + webkitAccessibleSetNewValue(value, newValue); +} + +static AtkRange* webkitAccessibleGetRange(AtkValue* value) +{ + g_return_val_if_fail(ATK_VALUE(value), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value), nullptr); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return nullptr; + + gdouble minValue = coreObject->minValueForRange(); + gdouble maxValue = coreObject->maxValueForRange(); + gchar* valueDescription = g_strdup_printf("%s", coreObject->valueDescription().utf8().data()); + return atk_range_new(minValue, maxValue, valueDescription); +} +#endif static void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue) { g_return_if_fail(ATK_VALUE(value)); @@ -92,16 +170,7 @@ static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GVal else return FALSE; - AccessibilityObject* coreObject = core(value); - if (!coreObject->canSetValueAttribute()) - return FALSE; - - // Check value against range limits - newValue = std::max(static_cast<double>(coreObject->minValueForRange()), newValue); - newValue = std::min(static_cast<double>(coreObject->maxValueForRange()), newValue); - - coreObject->setValue(String::number(newValue)); - return TRUE; + return webkitAccessibleSetNewValue(value, newValue); } static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gValue) @@ -113,19 +182,17 @@ static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gV g_value_init(gValue, G_TYPE_FLOAT); AccessibilityObject* coreObject = core(value); - if (!coreObject->getAttribute(HTMLNames::stepAttr).isEmpty()) { - g_value_set_float(gValue, coreObject->stepValueForRange()); - return; - } - - // If 'step' attribute is not defined, WebCore assumes a 5% of the - // range between minimum and maximum values. Implicit value of step should be one or larger. - float step = (coreObject->maxValueForRange() - coreObject->minValueForRange()) * 0.05; - g_value_set_float(gValue, step < 1 ? 1 : step); + g_value_set_float(gValue, webkitAccessibleGetIncrementValue(coreObject)); } void webkitAccessibleValueInterfaceInit(AtkValueIface* iface) { +#if ATK_CHECK_VERSION(2,11,92) + iface->get_value_and_text = webkitAccessibleGetValueAndText; + iface->get_increment = webkitAccessibleGetIncrement; + iface->set_value = webkitAccessibleSetValue; + iface->get_range = webkitAccessibleGetRange; +#endif iface->get_current_value = webkitAccessibleValueGetCurrentValue; iface->get_maximum_value = webkitAccessibleValueGetMaximumValue; iface->get_minimum_value = webkitAccessibleValueGetMinimumValue; diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.h index 6e53551ec..f1b49ebeb 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.h @@ -17,8 +17,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleInterfaceValue_h -#define WebKitAccessibleInterfaceValue_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -26,5 +25,4 @@ void webkitAccessibleValueInterfaceInit(AtkValueIface*); -#endif -#endif // WebKitAccessibleInterfaceValue_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp index 58147fa8c..001f9d06f 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp @@ -159,11 +159,14 @@ bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& // AND that the selection is not just "touching" one of the // boundaries for the selected node. We want to check whether the // node is actually inside the region, at least partially. - Node* node = coreObject->node(); - Node* lastDescendant = node->lastDescendant(); - return (range->intersectsNode(node, IGNORE_EXCEPTION) - && (range->endContainer() != node || range->endOffset()) - && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant))); + auto& node = *coreObject->node(); + auto* lastDescendant = node.lastDescendant(); + unsigned lastOffset = lastOffsetInNode(lastDescendant); + auto intersectsResult = range->intersectsNode(node); + return !intersectsResult.hasException() + && intersectsResult.releaseReturnValue() + && (&range->endContainer() != &node || range->endOffset()) + && (&range->startContainer() != lastDescendant || range->startOffset() != lastOffset); } #endif diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h index d0312dcd1..a946001f4 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h @@ -19,8 +19,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleUtil_h -#define WebKitAccessibleUtil_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -61,7 +60,7 @@ class VisibleSelection; AtkAttributeSet* addToAtkAttributeSet(AtkAttributeSet*, const char* name, const char* value); -void contentsRelativeToAtkCoordinateType(WebCore::AccessibilityObject*, AtkCoordType, WebCore::IntRect, gint* x, gint* y, gint* width = 0, gint* height = 0); +void contentsRelativeToAtkCoordinateType(WebCore::AccessibilityObject*, AtkCoordType, WebCore::IntRect, gint* x, gint* y, gint* width = nullptr, gint* height = nullptr); String accessibilityTitle(WebCore::AccessibilityObject*); @@ -69,5 +68,4 @@ String accessibilityDescription(WebCore::AccessibilityObject*); bool selectionBelongsToObject(WebCore::AccessibilityObject*, WebCore::VisibleSelection&); -#endif -#endif // WebKitAccessibleUtil_h +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp index 79eb46d35..86cee4e25 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp @@ -37,13 +37,17 @@ #include "AXObjectCache.h" #include "AccessibilityList.h" #include "AccessibilityListBoxOption.h" +#include "AccessibilityTable.h" #include "Document.h" #include "Frame.h" #include "FrameView.h" #include "HTMLNames.h" #include "HTMLTableElement.h" #include "HostWindow.h" +#include "RenderAncestorIterator.h" +#include "RenderFieldset.h" #include "RenderObject.h" +#include "SVGElement.h" #include "Settings.h" #include "TextIterator.h" #include "VisibleUnits.h" @@ -57,6 +61,7 @@ #include "WebKitAccessibleInterfaceImage.h" #include "WebKitAccessibleInterfaceSelection.h" #include "WebKitAccessibleInterfaceTable.h" +#include "WebKitAccessibleInterfaceTableCell.h" #include "WebKitAccessibleInterfaceText.h" #include "WebKitAccessibleInterfaceValue.h" #include "WebKitAccessibleUtil.h" @@ -64,10 +69,6 @@ #include <glib/gprintf.h> #include <wtf/text/CString.h> -#if PLATFORM(GTK) -#include <gtk/gtk.h> -#endif - using namespace WebCore; struct _WebKitAccessiblePrivate { @@ -93,7 +94,7 @@ struct _WebKitAccessiblePrivate { static AccessibilityObject* fallbackObject() { - static AccessibilityObject* object = AccessibilityListBoxOption::create().leakRef(); + static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef(); return object; } @@ -110,58 +111,24 @@ static const gchar* webkitAccessibleGetName(AtkObject* object) g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); - AccessibilityObject* coreObject = core(object); - - if (!coreObject->isAccessibilityRenderObject()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); + Vector<AccessibilityText> textOrder; + core(object)->accessibilityText(textOrder); - if (coreObject->isFieldset()) { - AccessibilityObject* label = coreObject->titleUIElement(); - if (label) { - AtkObject* atkObject = label->wrapper(); - if (ATK_IS_TEXT(atkObject)) - return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); - } - } - - if (coreObject->isControl()) { - AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); - if (label) { - AtkObject* atkObject = label->wrapper(); - if (ATK_IS_TEXT(atkObject)) - return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); - } - - // Try text under the node. - String textUnder = coreObject->textUnderElement(); - if (textUnder.length()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, textUnder); - } - - if (coreObject->isImage() || coreObject->isInputImage()) { - Node* node = coreObject->node(); - if (node && node->isHTMLElement()) { - // Get the attribute rather than altText String so as not to fall back on title. - String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr); - if (!alt.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, alt); - } - } + for (const auto& text : textOrder) { + // FIXME: This check is here because AccessibilityNodeObject::titleElementText() + // appends an empty String for the LabelByElementText source when there is a + // titleUIElement(). Removing this check makes some fieldsets lose their name. + if (text.text.isEmpty()) + continue; - // Fallback for the webArea object: just return the document's title. - if (coreObject->isWebArea()) { - Document* document = coreObject->document(); - if (document) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, document->title()); + // WebCore Accessibility should provide us with the text alternative computation + // in the order defined by that spec. So take the first thing that our platform + // does not expose via the AtkObject description. + if (text.textSource != HelpText && text.textSource != SummaryText) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, text.text); } - // Nothing worked so far, try with the AccessibilityObject's - // title() before going ahead with stringValue(). - String axTitle = accessibilityTitle(coreObject); - if (!axTitle.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, axTitle); - - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, ""); } static const gchar* webkitAccessibleGetDescription(AtkObject* object) @@ -169,27 +136,26 @@ static const gchar* webkitAccessibleGetDescription(AtkObject* object) g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); - AccessibilityObject* coreObject = core(object); - Node* node = 0; - if (coreObject->isAccessibilityRenderObject()) - node = coreObject->node(); - if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole || coreObject->isImage()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); - - // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. - if (coreObject->roleValue() == TableRole) { - String summary = toHTMLTableElement(node)->summary(); - if (!summary.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, summary); - } + Vector<AccessibilityText> textOrder; + core(object)->accessibilityText(textOrder); + + bool nameTextAvailable = false; + for (const auto& text : textOrder) { + // WebCore Accessibility should provide us with the text alternative computation + // in the order defined by that spec. So take the first thing that our platform + // does not expose via the AtkObject name. + if (text.textSource == HelpText || text.textSource == SummaryText) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text); - // The title attribute should be reliably available as the object's descripton. - // We do not want to fall back on other attributes in its absence. See bug 25524. - String title = toHTMLElement(node)->title(); - if (!title.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, title); + // If there is no other text alternative, the title tag contents will have been + // used for the AtkObject name. We don't want to duplicate it here. + if (text.textSource == TitleTagText && nameTextAvailable) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text); - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); + nameTextAvailable = true; + } + + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, ""); } static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType relationType) @@ -206,57 +172,56 @@ static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) { - if (coreObject->isFieldset()) { - AccessibilityObject* label = coreObject->titleUIElement(); - if (label) { - removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); - atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); - } - return; - } - - if (coreObject->roleValue() == LegendRole) { - for (AccessibilityObject* parent = coreObject->parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) { - if (parent->isFieldset()) { - atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, parent->wrapper()); - break; - } - } - return; - } + // FIXME: We're not implementing all the relation types, most notably the inverse/reciprocal + // types. Filed as bug 155494. + // Elements with aria-labelledby should have the labelled-by relation as per the ARIA AAM spec. + // Controls with a label element and fieldsets with a legend element should also use this relation + // as per the HTML AAM spec. The reciprocal label-for relation should also be used. + removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); if (coreObject->isControl()) { - AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); - if (label) { - removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); + if (AccessibilityObject* label = coreObject->correspondingLabelForControlElement()) atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } else if (coreObject->isFieldset()) { + if (AccessibilityObject* label = coreObject->titleUIElement()) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } else if (coreObject->roleValue() == LegendRole) { + if (RenderFieldset* renderFieldset = ancestorsOfType<RenderFieldset>(*coreObject->renderer()).first()) { + AccessibilityObject* fieldset = coreObject->axObjectCache()->getOrCreate(renderFieldset); + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, fieldset->wrapper()); } + } else if (AccessibilityObject* control = coreObject->correspondingControlForLabelElement()) { + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); } else { - AccessibilityObject* control = coreObject->correspondingControlForLabelElement(); - if (control) - atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); + AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements; + coreObject->ariaLabelledByElements(ariaLabelledByElements); + for (const auto& accessibilityObject : ariaLabelledByElements) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, accessibilityObject->wrapper()); } - // Check whether object supports aria-flowto - if (coreObject->supportsARIAFlowTo()) { - removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO); - AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements; - coreObject->ariaFlowToElements(ariaFlowToElements); - for (const auto& accessibilityObject : ariaFlowToElements) - atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, accessibilityObject->wrapper()); - } - - // Check whether object supports aria-describedby. It provides an additional information for the user. - if (coreObject->supportsARIADescribedBy()) { - removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY); - AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements; - coreObject->ariaDescribedByElements(ariaDescribedByElements); - for (const auto& accessibilityObject : ariaDescribedByElements) - atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, accessibilityObject->wrapper()); - } + // Elements with aria-flowto should have the flows-to relation as per the ARIA AAM spec. + removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO); + AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements; + coreObject->ariaFlowToElements(ariaFlowToElements); + for (const auto& accessibilityObject : ariaFlowToElements) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, accessibilityObject->wrapper()); + + // Elements with aria-describedby should have the described-by relation as per the ARIA AAM spec. + removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY); + AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements; + coreObject->ariaDescribedByElements(ariaDescribedByElements); + for (const auto& accessibilityObject : ariaDescribedByElements) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, accessibilityObject->wrapper()); + + // Elements with aria-controls should have the controller-for relation as per the ARIA AAM spec. + removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLER_FOR); + AccessibilityObject::AccessibilityChildrenVector ariaControls; + coreObject->ariaControlsElements(ariaControls); + for (const auto& accessibilityObject : ariaControls) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLER_FOR, accessibilityObject->wrapper()); } -static gpointer webkitAccessibleParentClass = 0; +static gpointer webkitAccessibleParentClass = nullptr; static bool isRootObject(AccessibilityObject* coreObject) { @@ -284,18 +249,6 @@ static AtkObject* atkParentOfRootObject(AtkObject* object) Document* document = coreObject->document(); if (!document) return 0; - -#if PLATFORM(GTK) - HostWindow* hostWindow = document->view()->hostWindow(); - if (hostWindow) { - PlatformPageClient scrollView = hostWindow->platformPageClient(); - if (scrollView) { - GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView); - if (scrollViewParent) - return gtk_widget_get_accessible(scrollViewParent); - } - } -#endif // PLATFORM(GTK) } if (!coreParent) @@ -327,31 +280,9 @@ static AtkObject* webkitAccessibleGetParent(AtkObject* object) if (!coreParent) return 0; - // We don't expose table rows to Assistive technologies, but we - // need to have them anyway in the hierarchy from WebCore to - // properly perform coordinates calculations when requested. - if (coreParent->isTableRow() && coreObject->isTableCell()) - coreParent = coreParent->parentObjectUnignored(); - return coreParent->wrapper(); } -static gint getNChildrenForTable(AccessibilityObject* coreObject) -{ - const AccessibilityObject::AccessibilityChildrenVector& tableChildren = coreObject->children(); - size_t cellsCount = 0; - - // Look for the actual index of the cell inside the table. - for (const auto& tableChild : tableChildren) { - if (tableChild->isTableRow()) - cellsCount += tableChild->children().size(); - else - cellsCount++; - } - - return cellsCount; -} - static gint webkitAccessibleGetNChildren(AtkObject* object) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); @@ -359,38 +290,9 @@ static gint webkitAccessibleGetNChildren(AtkObject* object) AccessibilityObject* coreObject = core(object); - // Tables should be treated in a different way because rows should - // be bypassed when exposing the accessible hierarchy. - if (coreObject->isAccessibilityTable()) - return getNChildrenForTable(coreObject); - return coreObject->children().size(); } -static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index) -{ - const AccessibilityObject::AccessibilityChildrenVector& tableChildren = coreObject->children(); - size_t cellsCount = 0; - - // Look for the actual index of the cell inside the table. - size_t current = static_cast<size_t>(index); - for (const auto& tableChild : tableChildren) { - if (tableChild->isTableRow()) { - const AccessibilityObject::AccessibilityChildrenVector& rowChildren = tableChild->children(); - size_t rowChildrenCount = rowChildren.size(); - if (current < cellsCount + rowChildrenCount) - return rowChildren.at(current - cellsCount).get(); - cellsCount += rowChildrenCount; - } else if (cellsCount == current) - return tableChild.get(); - else - cellsCount++; - } - - // Shouldn't reach if the child was found. - return 0; -} - static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); @@ -400,18 +302,12 @@ static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) return 0; AccessibilityObject* coreObject = core(object); - AccessibilityObject* coreChild = 0; - - // Tables are special cases because rows should be bypassed, but - // still taking their cells into account. - if (coreObject->isAccessibilityTable()) - coreChild = getChildForTable(coreObject, index); - else { - const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); - if (static_cast<unsigned>(index) >= children.size()) - return 0; - coreChild = children.at(index).get(); - } + AccessibilityObject* coreChild = nullptr; + + const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); + if (static_cast<size_t>(index) >= children.size()) + return 0; + coreChild = children.at(index).get(); if (!coreChild) return 0; @@ -423,40 +319,6 @@ static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) return child; } -static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject) -{ - AccessibilityObject* parent = coreObject->parentObjectUnignored(); - if (!parent) - return -1; - - AccessibilityObject* grandParent = parent->parentObjectUnignored(); - if (!grandParent) - return -1; - - const AccessibilityObject::AccessibilityChildrenVector& rows = grandParent->children(); - size_t previousCellsCount = 0; - - // Look for the actual index of the cell inside the table. - for (const auto& row : rows) { - if (!row->isTableRow()) - continue; - - const AccessibilityObject::AccessibilityChildrenVector& cells = row->children(); - size_t cellsCount = cells.size(); - - if (row == parent) { - for (unsigned j = 0; j < cellsCount; ++j) { - if (cells[j] == coreObject) - return previousCellsCount + j; - } - } - - previousCellsCount += cellsCount; - } - - return -1; -} - static gint webkitAccessibleGetIndexInParent(AtkObject* object) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), -1); @@ -480,11 +342,6 @@ static gint webkitAccessibleGetIndexInParent(AtkObject* object) } } - // Need to calculate the index of the cell in the table, as - // rows won't be exposed to assistive technologies. - if (parent && parent->isTableRow() && coreObject->isTableCell()) - return getIndexInParentForCellInRow(coreObject); - if (!parent) return -1; @@ -497,11 +354,9 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); - AtkAttributeSet* attributeSet = 0; + AtkAttributeSet* attributeSet = nullptr; #if PLATFORM(GTK) attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk"); -#elif PLATFORM(EFL) - attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitEfl"); #endif AccessibilityObject* coreObject = core(object); @@ -511,9 +366,12 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) // Hack needed for WebKit2 tests because obtaining an element by its ID // cannot be done from the UIProcess. Assistive technologies have no need // for this information. - Node* node = coreObject->node(); - if (node && node->isElementNode()) { - String id = toElement(node)->getIdAttribute().string(); + Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement(); + if (element) { + String tagName = element->tagName(); + if (!tagName.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data()); + String id = element->getIdAttribute().string(); if (!id.isEmpty()) attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data()); } @@ -524,9 +382,16 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data()); } + if (coreObject->roleValue() == MathElementRole) { + if (coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PreSubscript)) + attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre"); + else if (coreObject->isMathMultiscriptObject(PostSuperscript) || coreObject->isMathMultiscriptObject(PostSubscript)) + attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post"); + } + // Set the 'layout-guess' attribute to help Assistive // Technologies know when an exposed table is not data table. - if (coreObject->isAccessibilityTable() && !coreObject->isDataTable()) + if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposableThroughAccessibility() && !coreObject->isDataTable()) attributeSet = addToAtkAttributeSet(attributeSet, "layout-guess", "true"); String placeholder = coreObject->placeholderValue(); @@ -543,32 +408,38 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) attributeSet = addToAtkAttributeSet(attributeSet, "sort", sortAttribute.string().utf8().data()); } - // Landmarks will be exposed with xml-roles object attributes, with the exception - // of LandmarkApplicationRole, which will be exposed with ATK_ROLE_EMBEDDED. - AccessibilityRole role = coreObject->roleValue(); - switch (role) { - case LandmarkBannerRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "banner"); - break; - case LandmarkComplementaryRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "complementary"); - break; - case LandmarkContentInfoRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "contentinfo"); - break; - case LandmarkMainRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "main"); - break; - case LandmarkNavigationRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "navigation"); - break; - case LandmarkSearchRole: - attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", "search"); - break; - default: - break; + if (coreObject->supportsARIAPosInSet()) + attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->ariaPosInSet()).utf8().data()); + + if (coreObject->supportsARIASetSize()) + attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->ariaSetSize()).utf8().data()); + + String isReadOnly = coreObject->ariaReadOnlyValue(); + if (!isReadOnly.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data()); + + String valueDescription = coreObject->valueDescription(); + if (!valueDescription.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data()); + + // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules: + // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so." + // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string"). + // The computedRoleString is primarily for testing, and not limited to elements with ARIA roles. + // Because the computedRoleString currently contains the ARIA role string, we'll use it for + // both purposes, as the "computed-role" object attribute for all elements which have a value + // and also via the "xml-roles" attribute for elements with ARIA, as well as for landmarks. + String roleString = coreObject->computedRoleString(); + if (!roleString.isEmpty()) { + if (coreObject->ariaRoleAttribute() != UnknownRole || coreObject->isLandmark()) + attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data()); + attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", roleString.utf8().data()); } + String roleDescription = coreObject->roleDescription(); + if (!roleDescription.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "roledescription", roleDescription.utf8().data()); + return attributeSet; } @@ -586,10 +457,17 @@ static AtkRole atkRole(AccessibilityObject* coreObject) case UnknownRole: return ATK_ROLE_UNKNOWN; case AudioRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_AUDIO; +#endif case VideoRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_VIDEO; +#endif return ATK_ROLE_EMBEDDED; case ButtonRole: return ATK_ROLE_PUSH_BUTTON; + case SwitchRole: case ToggleButtonRole: return ATK_ROLE_TOGGLE_BUTTON; case RadioButtonRole: @@ -603,9 +481,14 @@ static AtkRole atkRole(AccessibilityObject* coreObject) return ATK_ROLE_PAGE_TAB_LIST; case TextFieldRole: case TextAreaRole: + case SearchFieldRole: return ATK_ROLE_ENTRY; case StaticTextRole: +#if ATK_CHECK_VERSION(2, 15, 2) + return ATK_ROLE_STATIC; +#else return ATK_ROLE_TEXT; +#endif case OutlineRole: case TreeRole: return ATK_ROLE_TREE; @@ -627,15 +510,13 @@ static AtkRole atkRole(AccessibilityObject* coreObject) // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? return ATK_ROLE_UNKNOWN; // Matches Mozilla case RowRole: - // return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? - return ATK_ROLE_LIST_ITEM; // Matches Mozilla + return ATK_ROLE_TABLE_ROW; case ToolbarRole: return ATK_ROLE_TOOL_BAR; case BusyIndicatorRole: return ATK_ROLE_PROGRESS_BAR; // Is this right? case ProgressIndicatorRole: - // return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp - return ATK_ROLE_PROGRESS_BAR; + return coreObject->isMeter() ? ATK_ROLE_LEVEL_BAR : ATK_ROLE_PROGRESS_BAR; case WindowRole: return ATK_ROLE_WINDOW; case PopUpButtonRole: @@ -646,31 +527,42 @@ static AtkRole atkRole(AccessibilityObject* coreObject) case SplitterRole: return ATK_ROLE_SEPARATOR; case ColorWellRole: - return ATK_ROLE_COLOR_CHOOSER; +#if PLATFORM(GTK) + // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button). + return ATK_ROLE_PUSH_BUTTON; +#endif case ListRole: return ATK_ROLE_LIST; case ScrollBarRole: return ATK_ROLE_SCROLL_BAR; case ScrollAreaRole: return ATK_ROLE_SCROLL_PANE; - case GridRole: // Is this right? + case GridRole: case TableRole: return ATK_ROLE_TABLE; case ApplicationRole: return ATK_ROLE_APPLICATION; - case GroupRole: case RadioGroupRole: + case SVGRootRole: case TabPanelRole: return ATK_ROLE_PANEL; - case RowHeaderRole: // Row headers are cells after all. - case ColumnHeaderRole: // Column headers are cells after all. + case GroupRole: + return coreObject->isStyleFormatGroup() ? ATK_ROLE_SECTION : ATK_ROLE_PANEL; + case RowHeaderRole: + return ATK_ROLE_ROW_HEADER; + case ColumnHeaderRole: + return ATK_ROLE_COLUMN_HEADER; + case CaptionRole: + return ATK_ROLE_CAPTION; case CellRole: - return ATK_ROLE_TABLE_CELL; + case GridCellRole: + return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL; case LinkRole: case WebCoreLinkRole: case ImageMapLinkRole: return ATK_ROLE_LINK; case ImageMapRole: + return ATK_ROLE_IMAGE_MAP; case ImageRole: return ATK_ROLE_IMAGE; case ListMarkerRole: @@ -686,17 +578,27 @@ static AtkRole atkRole(AccessibilityObject* coreObject) case HeadingRole: return ATK_ROLE_HEADING; case ListBoxRole: - return ATK_ROLE_LIST; + // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-listbox + return coreObject->isDescendantOfRole(ComboBoxRole) ? ATK_ROLE_MENU : ATK_ROLE_LIST_BOX; case ListItemRole: + return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM; case ListBoxOptionRole: - return ATK_ROLE_LIST_ITEM; + return coreObject->isDescendantOfRole(ComboBoxRole) ? ATK_ROLE_MENU_ITEM : ATK_ROLE_LIST_ITEM; case ParagraphRole: return ATK_ROLE_PARAGRAPH; case LabelRole: case LegendRole: return ATK_ROLE_LABEL; + case BlockquoteRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_BLOCK_QUOTE; +#endif case DivRole: + case PreRole: + case SVGTextRole: return ATK_ROLE_SECTION; + case FooterRole: + return ATK_ROLE_FOOTER; case FormRole: return ATK_ROLE_FORM; case CanvasRole: @@ -711,7 +613,7 @@ static AtkRole atkRole(AccessibilityObject* coreObject) return ATK_ROLE_TOOL_TIP; case WebAreaRole: return ATK_ROLE_DOCUMENT_WEB; - case LandmarkApplicationRole: + case WebApplicationRole: return ATK_ROLE_EMBEDDED; #if ATK_CHECK_VERSION(2, 11, 3) case ApplicationLogRole: @@ -724,11 +626,40 @@ static AtkRole atkRole(AccessibilityObject* coreObject) return ATK_ROLE_DEFINITION; case DocumentMathRole: return ATK_ROLE_MATH; + case MathElementRole: + if (coreObject->isMathRow()) + return ATK_ROLE_PANEL; + if (coreObject->isMathTable()) + return ATK_ROLE_TABLE; + if (coreObject->isMathTableRow()) + return ATK_ROLE_TABLE_ROW; + if (coreObject->isMathTableCell()) + return ATK_ROLE_TABLE_CELL; + if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript()) + return ATK_ROLE_SECTION; +#if ATK_CHECK_VERSION(2, 15, 4) + if (coreObject->isMathFraction()) + return ATK_ROLE_MATH_FRACTION; + if (coreObject->isMathSquareRoot() || coreObject->isMathRoot()) + return ATK_ROLE_MATH_ROOT; + if (coreObject->isMathScriptObject(Subscript) + || coreObject->isMathMultiscriptObject(PreSubscript) || coreObject->isMathMultiscriptObject(PostSubscript)) + return ATK_ROLE_SUBSCRIPT; + if (coreObject->isMathScriptObject(Superscript) + || coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PostSuperscript)) + return ATK_ROLE_SUPERSCRIPT; +#endif +#if ATK_CHECK_VERSION(2, 15, 2) + if (coreObject->isMathToken()) + return ATK_ROLE_STATIC; +#endif + return ATK_ROLE_UNKNOWN; case LandmarkBannerRole: case LandmarkComplementaryRole: case LandmarkContentInfoRole: case LandmarkMainRole: case LandmarkNavigationRole: + case LandmarkRegionRole: case LandmarkSearchRole: return ATK_ROLE_LANDMARK; #endif @@ -740,6 +671,19 @@ static AtkRole atkRole(AccessibilityObject* coreObject) case DescriptionListDetailRole: return ATK_ROLE_DESCRIPTION_VALUE; #endif + case InlineRole: +#if ATK_CHECK_VERSION(2, 15, 4) + if (coreObject->isSubscriptStyleGroup()) + return ATK_ROLE_SUBSCRIPT; + if (coreObject->isSuperscriptStyleGroup()) + return ATK_ROLE_SUPERSCRIPT; +#endif +#if ATK_CHECK_VERSION(2, 15, 2) + return ATK_ROLE_STATIC; + case SVGTextPathRole: + case SVGTSpanRole: + return ATK_ROLE_STATIC; +#endif default: return ATK_ROLE_UNKNOWN; } @@ -803,16 +747,18 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta if (isListBoxOption && coreObject->isSelectedOptionActive()) atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE); + if (coreObject->isBusy()) + atk_state_set_add_state(stateSet, ATK_STATE_BUSY); + +#if ATK_CHECK_VERSION(2,11,2) + if (coreObject->supportsChecked() && coreObject->canSetValueAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE); +#endif + if (coreObject->isChecked()) atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); - // FIXME: isReadOnly does not seem to do the right thing for - // controls, so check explicitly for them. In addition, because - // isReadOnly is false for listBoxOptions, we need to add one - // more check so that we do not present them as being "editable". - if ((!coreObject->isReadOnly() - || (coreObject->isControl() && coreObject->canSetValueAttribute())) - && !isListBoxOption) + if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute()) atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); // FIXME: Put both ENABLED and SENSITIVE together here for now @@ -857,6 +803,11 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta if (coreObject->isPressed()) atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); +#if ATK_CHECK_VERSION(2,15,3) + if (!coreObject->canSetValueAttribute() && (coreObject->supportsARIAReadOnly())) + atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY); +#endif + if (coreObject->isRequired()) atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED); @@ -890,10 +841,10 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta } // Mutually exclusive, so we group these two - if (coreObject->roleValue() == TextFieldRole) - atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); - else if (coreObject->roleValue() == TextAreaRole) + if (coreObject->roleValue() == TextAreaRole || coreObject->ariaIsMultiline()) atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); + else if (coreObject->roleValue() == TextFieldRole || coreObject->roleValue() == SearchFieldRole) + atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); // TODO: ATK_STATE_SENSITIVE @@ -965,7 +916,7 @@ static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object) return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language); } else if (ATK_IS_TEXT(object)) { - const gchar* locale = 0; + const gchar* locale = nullptr; AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object)); for (AtkAttributeSet* attributes = textAttributes; attributes; attributes = attributes->next) { @@ -1046,6 +997,9 @@ static const GInterfaceInfo AtkInterfacesInitFunctions[] = { {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0}, +#if ATK_CHECK_VERSION(2,11,90) + {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableCellInterfaceInit), 0, 0}, +#endif {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0}, @@ -1053,43 +1007,50 @@ static const GInterfaceInfo AtkInterfacesInitFunctions[] = { }; enum WAIType { - WAI_ACTION, - WAI_SELECTION, - WAI_EDITABLE_TEXT, - WAI_TEXT, - WAI_COMPONENT, - WAI_IMAGE, - WAI_TABLE, - WAI_HYPERTEXT, - WAI_HYPERLINK, - WAI_DOCUMENT, - WAI_VALUE, + WAIAction, + WAISelection, + WAIEditableText, + WAIText, + WAIComponent, + WAIImage, + WAITable, +#if ATK_CHECK_VERSION(2,11,90) + WAITableCell, +#endif + WAIHypertext, + WAIHyperlink, + WAIDocument, + WAIValue, }; static GType GetAtkInterfaceTypeFromWAIType(WAIType type) { switch (type) { - case WAI_ACTION: + case WAIAction: return ATK_TYPE_ACTION; - case WAI_SELECTION: + case WAISelection: return ATK_TYPE_SELECTION; - case WAI_EDITABLE_TEXT: + case WAIEditableText: return ATK_TYPE_EDITABLE_TEXT; - case WAI_TEXT: + case WAIText: return ATK_TYPE_TEXT; - case WAI_COMPONENT: + case WAIComponent: return ATK_TYPE_COMPONENT; - case WAI_IMAGE: + case WAIImage: return ATK_TYPE_IMAGE; - case WAI_TABLE: + case WAITable: return ATK_TYPE_TABLE; - case WAI_HYPERTEXT: +#if ATK_CHECK_VERSION(2,11,90) + case WAITableCell: + return ATK_TYPE_TABLE_CELL; +#endif + case WAIHypertext: return ATK_TYPE_HYPERTEXT; - case WAI_HYPERLINK: + case WAIHyperlink: return ATK_TYPE_HYPERLINK_IMPL; - case WAI_DOCUMENT: + case WAIDocument: return ATK_TYPE_DOCUMENT; - case WAI_VALUE: + case WAIValue: return ATK_TYPE_VALUE; } @@ -1099,7 +1060,8 @@ static GType GetAtkInterfaceTypeFromWAIType(WAIType type) static bool roleIsTextType(AccessibilityRole role) { return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole - || role == LinkRole || role == WebCoreLinkRole || role == ListItemRole; + || role == LinkRole || role == WebCoreLinkRole || role == ListItemRole || role == PreRole + || role == GridCellRole; } static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) @@ -1107,7 +1069,7 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) guint16 interfaceMask = 0; // Component interface is always supported - interfaceMask |= 1 << WAI_COMPONENT; + interfaceMask |= 1 << WAIComponent; AccessibilityRole role = coreObject->roleValue(); @@ -1117,68 +1079,71 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) // object, and only supports having one action per object), it is // better just to implement this interface for every instance of // the WebKitAccessible class and let WebCore decide what to do. - interfaceMask |= 1 << WAI_ACTION; + interfaceMask |= 1 << WAIAction; // Selection - if (coreObject->isListBox() || coreObject->isMenuList()) - interfaceMask |= 1 << WAI_SELECTION; + if (coreObject->canHaveSelectedChildren() || coreObject->isMenuList()) + interfaceMask |= 1 << WAISelection; // Get renderer if available. - RenderObject* renderer = 0; + RenderObject* renderer = nullptr; if (coreObject->isAccessibilityRenderObject()) renderer = coreObject->renderer(); // Hyperlink (links and embedded objects). if (coreObject->isLink() || (renderer && renderer->isReplaced())) - interfaceMask |= 1 << WAI_HYPERLINK; + interfaceMask |= 1 << WAIHyperlink; - // Text & Editable Text + // Text, Editable Text & Hypertext if (role == StaticTextRole || coreObject->isMenuListOption()) - interfaceMask |= 1 << WAI_TEXT; - else { - if (coreObject->isTextControl()) { - interfaceMask |= 1 << WAI_TEXT; - if (!coreObject->isReadOnly()) - interfaceMask |= 1 << WAI_EDITABLE_TEXT; - } else { - if (role != TableRole) { - interfaceMask |= 1 << WAI_HYPERTEXT; - if ((renderer && renderer->childrenInline()) || roleIsTextType(role)) - interfaceMask |= 1 << WAI_TEXT; - } + interfaceMask |= 1 << WAIText; + else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) { + interfaceMask |= 1 << WAIText; + if (coreObject->canSetValueAttribute()) + interfaceMask |= 1 << WAIEditableText; + } else if (!coreObject->isWebArea()) { + if (role != TableRole) { + interfaceMask |= 1 << WAIHypertext; + if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken()) + interfaceMask |= 1 << WAIText; + } - // Add the TEXT interface for list items whose - // first accessible child has a text renderer - if (role == ListItemRole) { - const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); - if (children.size()) { - AccessibilityObject* axRenderChild = children.at(0).get(); - interfaceMask |= getInterfaceMaskFromObject(axRenderChild); - } + // Add the TEXT interface for list items whose + // first accessible child has a text renderer + if (role == ListItemRole) { + const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); + if (children.size()) { + AccessibilityObject* axRenderChild = children.at(0).get(); + interfaceMask |= getInterfaceMaskFromObject(axRenderChild); } } } // Image if (coreObject->isImage()) - interfaceMask |= 1 << WAI_IMAGE; + interfaceMask |= 1 << WAIImage; // Table - if (role == TableRole) - interfaceMask |= 1 << WAI_TABLE; + if (role == TableRole || role == GridRole) + interfaceMask |= 1 << WAITable; + +#if ATK_CHECK_VERSION(2,11,90) + if (role == CellRole || role == ColumnHeaderRole || role == RowHeaderRole) + interfaceMask |= 1 << WAITableCell; +#endif // Document if (role == WebAreaRole) - interfaceMask |= 1 << WAI_DOCUMENT; + interfaceMask |= 1 << WAIDocument; // Value if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole || role == ProgressIndicatorRole) - interfaceMask |= 1 << WAI_VALUE; + interfaceMask |= 1 << WAIValue; #if ENABLE(INPUT_TYPE_COLOR) // Color type. if (role == ColorWellRole) - interfaceMask |= 1 << WAI_TEXT; + interfaceMask |= 1 << WAIText; #endif return interfaceMask; @@ -1261,18 +1226,6 @@ bool webkitAccessibleIsDetached(WebKitAccessible* accessible) return accessible->m_object == fallbackObject(); } -AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible* accessible) -{ - if (!accessible->m_object) - return 0; - - RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); - if (!focusedObj) - return 0; - - return focusedObj->wrapper(); -} - AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset) { // Indication that something bogus has transpired. @@ -1314,7 +1267,7 @@ AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* r if (referenceObject->isDescendantOfObject(firstUnignoredParent)) referenceObject = firstUnignoredParent; - Node* startNode = 0; + Node* startNode = nullptr; if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) { // We need to use the first child's node of the reference // object as the start point to calculate the caret offset @@ -1356,7 +1309,7 @@ AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* r const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value) { WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv; - CString* propertyPtr = 0; + CString* propertyPtr = nullptr; switch (property) { case AtkCachedAccessibleName: diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h index 0d473019d..7dfebfb7f 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h @@ -20,8 +20,7 @@ * Boston, MA 02110-1301, USA. */ -#ifndef WebKitAccessibleWrapperAtk_h -#define WebKitAccessibleWrapperAtk_h +#pragma once #if HAVE(ACCESSIBILITY) @@ -79,13 +78,10 @@ void webkitAccessibleDetach(WebKitAccessible*); bool webkitAccessibleIsDetached(WebKitAccessible*); -AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible*); - WebCore::AccessibilityObject* objectFocusedAndCaretOffsetUnignored(WebCore::AccessibilityObject*, int& offset); const char* cacheAndReturnAtkProperty(AtkObject*, AtkCachedProperty, String value); G_END_DECLS -#endif -#endif // WebKitAccessibleWrapperAtk_h +#endif // HAVE(ACCESSIBILITY) |