From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- Source/WebCore/accessibility/AXObjectCache.cpp | 1956 +++++++++++++++-- Source/WebCore/accessibility/AXObjectCache.h | 309 ++- .../accessibility/AXTextStateChangeIntent.h | 94 + .../accessibility/AccessibilityARIAGrid.cpp | 68 +- .../WebCore/accessibility/AccessibilityARIAGrid.h | 27 +- .../accessibility/AccessibilityARIAGridCell.cpp | 101 +- .../accessibility/AccessibilityARIAGridCell.h | 26 +- .../accessibility/AccessibilityARIAGridRow.cpp | 32 +- .../accessibility/AccessibilityARIAGridRow.h | 25 +- .../accessibility/AccessibilityAllInOne.cpp | 60 + .../accessibility/AccessibilityAttachment.cpp | 110 + .../accessibility/AccessibilityAttachment.h | 59 + .../accessibility/AccessibilityImageMapLink.cpp | 16 +- .../accessibility/AccessibilityImageMapLink.h | 63 +- .../WebCore/accessibility/AccessibilityLabel.cpp | 109 + Source/WebCore/accessibility/AccessibilityLabel.h | 57 + Source/WebCore/accessibility/AccessibilityList.cpp | 110 +- Source/WebCore/accessibility/AccessibilityList.h | 26 +- .../WebCore/accessibility/AccessibilityListBox.cpp | 42 +- .../WebCore/accessibility/AccessibilityListBox.h | 35 +- .../accessibility/AccessibilityListBoxOption.cpp | 55 +- .../accessibility/AccessibilityListBoxOption.h | 51 +- .../accessibility/AccessibilityMathMLElement.cpp | 456 ++++ .../accessibility/AccessibilityMathMLElement.h | 115 + .../accessibility/AccessibilityMediaControls.cpp | 71 +- .../accessibility/AccessibilityMediaControls.h | 69 +- .../accessibility/AccessibilityMenuList.cpp | 42 +- .../WebCore/accessibility/AccessibilityMenuList.h | 28 +- .../accessibility/AccessibilityMenuListOption.cpp | 12 +- .../accessibility/AccessibilityMenuListOption.h | 40 +- .../accessibility/AccessibilityMenuListPopup.cpp | 17 +- .../accessibility/AccessibilityMenuListPopup.h | 33 +- .../accessibility/AccessibilityMockObject.cpp | 2 +- .../accessibility/AccessibilityMockObject.h | 22 +- .../accessibility/AccessibilityNodeObject.cpp | 856 ++++---- .../accessibility/AccessibilityNodeObject.h | 231 +- .../WebCore/accessibility/AccessibilityObject.cpp | 1562 +++++++++++--- Source/WebCore/accessibility/AccessibilityObject.h | 365 +++- .../AccessibilityProgressIndicator.cpp | 104 +- .../accessibility/AccessibilityProgressIndicator.h | 37 +- .../accessibility/AccessibilityRenderObject.cpp | 2195 ++++++++++---------- .../accessibility/AccessibilityRenderObject.h | 343 ++- .../accessibility/AccessibilitySVGElement.cpp | 314 +++ .../accessibility/AccessibilitySVGElement.h | 62 + .../WebCore/accessibility/AccessibilitySVGRoot.cpp | 12 +- .../WebCore/accessibility/AccessibilitySVGRoot.h | 29 +- .../accessibility/AccessibilityScrollView.cpp | 108 +- .../accessibility/AccessibilityScrollView.h | 62 +- .../accessibility/AccessibilityScrollbar.cpp | 20 +- .../WebCore/accessibility/AccessibilityScrollbar.h | 33 +- .../AccessibilitySearchFieldButtons.cpp | 76 - .../AccessibilitySearchFieldButtons.h | 51 - .../WebCore/accessibility/AccessibilitySlider.cpp | 31 +- Source/WebCore/accessibility/AccessibilitySlider.h | 51 +- .../accessibility/AccessibilitySpinButton.cpp | 44 +- .../accessibility/AccessibilitySpinButton.h | 41 +- .../WebCore/accessibility/AccessibilityTable.cpp | 351 ++-- Source/WebCore/accessibility/AccessibilityTable.h | 53 +- .../accessibility/AccessibilityTableCell.cpp | 280 ++- .../WebCore/accessibility/AccessibilityTableCell.h | 48 +- .../accessibility/AccessibilityTableColumn.cpp | 137 +- .../accessibility/AccessibilityTableColumn.h | 35 +- .../AccessibilityTableHeaderContainer.cpp | 16 +- .../AccessibilityTableHeaderContainer.h | 26 +- .../accessibility/AccessibilityTableRow.cpp | 83 +- .../WebCore/accessibility/AccessibilityTableRow.h | 36 +- Source/WebCore/accessibility/AccessibilityTree.cpp | 113 + Source/WebCore/accessibility/AccessibilityTree.h | 51 + .../accessibility/AccessibilityTreeItem.cpp | 65 + .../WebCore/accessibility/AccessibilityTreeItem.h | 50 + .../WebCore/accessibility/atk/AXObjectCacheAtk.cpp | 60 +- .../accessibility/atk/AccessibilityObjectAtk.cpp | 118 +- .../atk/WebKitAccessibleHyperlink.cpp | 6 +- .../accessibility/atk/WebKitAccessibleHyperlink.h | 7 +- .../atk/WebKitAccessibleInterfaceAction.h | 7 +- .../atk/WebKitAccessibleInterfaceComponent.cpp | 2 +- .../atk/WebKitAccessibleInterfaceComponent.h | 7 +- .../atk/WebKitAccessibleInterfaceDocument.cpp | 2 +- .../atk/WebKitAccessibleInterfaceDocument.h | 6 +- .../atk/WebKitAccessibleInterfaceEditableText.h | 6 +- .../atk/WebKitAccessibleInterfaceHyperlinkImpl.h | 6 +- .../atk/WebKitAccessibleInterfaceHypertext.cpp | 7 +- .../atk/WebKitAccessibleInterfaceHypertext.h | 7 +- .../atk/WebKitAccessibleInterfaceImage.cpp | 4 +- .../atk/WebKitAccessibleInterfaceImage.h | 6 +- .../atk/WebKitAccessibleInterfaceSelection.cpp | 90 +- .../atk/WebKitAccessibleInterfaceSelection.h | 6 +- .../atk/WebKitAccessibleInterfaceTable.cpp | 57 +- .../atk/WebKitAccessibleInterfaceTable.h | 6 +- .../atk/WebKitAccessibleInterfaceTableCell.cpp | 160 ++ .../atk/WebKitAccessibleInterfaceTableCell.h | 32 + .../atk/WebKitAccessibleInterfaceText.cpp | 72 +- .../atk/WebKitAccessibleInterfaceText.h | 6 +- .../atk/WebKitAccessibleInterfaceValue.cpp | 105 +- .../atk/WebKitAccessibleInterfaceValue.h | 6 +- .../accessibility/atk/WebKitAccessibleUtil.cpp | 13 +- .../accessibility/atk/WebKitAccessibleUtil.h | 8 +- .../atk/WebKitAccessibleWrapperAtk.cpp | 659 +++--- .../accessibility/atk/WebKitAccessibleWrapperAtk.h | 8 +- 99 files changed, 9601 insertions(+), 4187 deletions(-) create mode 100644 Source/WebCore/accessibility/AXTextStateChangeIntent.h create mode 100644 Source/WebCore/accessibility/AccessibilityAllInOne.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityAttachment.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityAttachment.h create mode 100644 Source/WebCore/accessibility/AccessibilityLabel.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityLabel.h create mode 100644 Source/WebCore/accessibility/AccessibilityMathMLElement.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityMathMLElement.h create mode 100644 Source/WebCore/accessibility/AccessibilitySVGElement.cpp create mode 100644 Source/WebCore/accessibility/AccessibilitySVGElement.h delete mode 100644 Source/WebCore/accessibility/AccessibilitySearchFieldButtons.cpp delete mode 100644 Source/WebCore/accessibility/AccessibilitySearchFieldButtons.h create mode 100644 Source/WebCore/accessibility/AccessibilityTree.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityTree.h create mode 100644 Source/WebCore/accessibility/AccessibilityTreeItem.cpp create mode 100644 Source/WebCore/accessibility/AccessibilityTreeItem.h create mode 100644 Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp create mode 100644 Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h (limited to 'Source/WebCore/accessibility') 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 +#include "TextBoundaries.h" +#include "TextIterator.h" +#include "htmlediting.h" +#include #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::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> 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(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(*child)) continue; - if (toAccessibilityImageMapLink(child.get())->areaElement() == areaElement) + if (downcast(*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(focusedElement)) + return focusedImageMapUIElement(downcast(focusedElement)); AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast(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::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::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::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(node)) + return false; + + auto& roleValue = downcast(*node).attributeWithoutSynchronization(roleAttr); + if (role.isNull()) + return roleValue.isEmpty(); + if (roleValue.isEmpty()) return false; - return equalIgnoringCase(toElement(node)->fastGetAttribute(roleAttr), role); + return SpaceSplitString(roleValue, true).contains(role); } -static PassRefPtr createFromRenderer(RenderObject* renderer) +static Ref createFromRenderer(RenderObject* renderer) { // FIXME: How could renderer->node() ever not be an Element? Node* node = renderer->node(); @@ -264,62 +427,80 @@ static PassRefPtr 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(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(*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(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(*renderer); + if (isAnonymousOperator || is(node)) + return AccessibilityMathMLElement::create(renderer, isAnonymousOperator); +#endif + + if (is(*renderer)) { + RenderBoxModelObject& cssBox = downcast(*renderer); + if (is(cssBox)) + return AccessibilityListBox::create(&downcast(cssBox)); + if (is(cssBox)) + return AccessibilityMenuList::create(&downcast(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(cssBox)) + return AccessibilityTable::create(&downcast(cssBox)); + if (is(cssBox)) + return AccessibilityTableRow::create(&downcast(cssBox)); + if (is(cssBox)) + return AccessibilityTableCell::create(&downcast(cssBox)); + // progress bar - if (cssBox->isProgress()) - return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); + if (is(cssBox)) + return AccessibilityProgressIndicator::create(&downcast(cssBox)); + +#if ENABLE(ATTACHMENT_ELEMENT) + if (is(cssBox)) + return AccessibilityAttachment::create(&downcast(cssBox)); #endif #if ENABLE(METER_ELEMENT) - if (cssBox->isMeter()) - return AccessibilityProgressIndicator::create(toRenderMeter(cssBox)); + if (is(cssBox)) + return AccessibilityProgressIndicator::create(&downcast(cssBox)); #endif // input type=range - if (cssBox->isSlider()) - return AccessibilitySlider::create(toRenderSlider(cssBox)); + if (is(cssBox)) + return AccessibilitySlider::create(&downcast(cssBox)); } return AccessibilityRenderObject::create(renderer); } -static PassRefPtr createFromNode(Node* node) +static Ref createFromNode(Node* node) { return AccessibilityNodeObject::create(node); } @@ -327,16 +508,16 @@ static PassRefPtr createFromNode(Node* node) AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) { if (!widget) - return 0; + return nullptr; if (AccessibilityObject* obj = get(widget)) return obj; - RefPtr newObj = 0; - if (widget->isFrameView()) - newObj = AccessibilityScrollView::create(toScrollView(widget)); - else if (widget->isScrollbar()) - newObj = AccessibilityScrollbar::create(toScrollbar(widget)); + RefPtr newObj; + if (is(*widget)) + newObj = AccessibilityScrollView::create(downcast(widget)); + else if (is(*widget)) + newObj = AccessibilityScrollbar::create(downcast(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(*node->parentElement()).first(); + bool isHidden = isNodeAriaVisible(node); bool insideMeterElement = false; #if ENABLE(METER_ELEMENT) - insideMeterElement = isHTMLMeterElement(node->parentElement()); + insideMeterElement = is(*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 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 obj = 0; + RefPtr 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(*renderer)) + m_deferredIsIgnoredChangeList.remove(downcast(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(node) || !node->renderer()) + return; + + Element* element = downcast(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&) +void AXObjectCache::notificationPostTimerFired() { Ref 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&) #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(*obj)) { + if (auto* renderer = downcast(*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(*node).focused() && !equalLettersIgnoringASCIICase(downcast(*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(*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(node)) + return; + + if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog")) + return; + + stopCachingComputedObjectAttributes(); + if (equalLettersIgnoringASCIICase(downcast(*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(*element)); + HTMLElement* correspondingControl = downcast(*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(RefPtrrange, 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(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 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 range1 = AXObjectCache::rangeForNodeContents(node1); + RefPtr 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 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:
, 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 (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 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(*domNode) && downcast(*domNode).isPasswordField()) { + textMarkerData.ignored = true; + return; + } + + RefPtr 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, 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 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, 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 = 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; - if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) + 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 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(*domNode) && downcast(*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 AXObjectCache::leftWordRange(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfWord(characterOffset, LeftWordIfOnBoundary); + CharacterOffset end = endCharacterOffsetOfWord(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +RefPtr 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 searchRange = rangeForNodeContents(boundary); + if (!searchRange) + return { }; + + Vector 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 searchRange = rangeForNodeContents(boundary); + Vector 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(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 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 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 = rangeForUnorderedCharacterOffsets(characterOffset, characterOffset); + if (!range) + return IntRect(); + + Position startPosition = range->startPosition(); + startPosition.getInlineBoxAndOffset(DOWNSTREAM, inlineBox, caretOffset); + + if (inlineBox) + renderer = &inlineBox->renderer(); + + if (is(renderer) && downcast(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 = makeRange(vp, vp); + return startOrEndCharacterOffsetForRange(range, true); +} + +CharacterOffset AXObjectCache::characterOffsetForPoint(const IntPoint &point) +{ + RefPtr 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 = 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 = 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(*node) ? downcast(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::iterator it = m_textMarkerNodes.begin(); - HashSet::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 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(*testNode)) { + const AtomicString& ariaHiddenValue = downcast(*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(*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 #include #include #include +#include #include 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 m_idMapping; }; +struct VisiblePositionIndex { + int value = -1; + RefPtr 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, bool); + CharacterOffset startOrEndCharacterOffsetForRange(RefPtr, bool); + AccessibilityObject* accessibilityObjectForTextMarkerData(TextMarkerData&); + RefPtr rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&); + static RefPtr rangeForNodeContents(Node*); + static int lengthForRange(Range*); + + // Word boundary + CharacterOffset nextWordEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousWordStartCharacterOffset(const CharacterOffset&); + RefPtr leftWordRange(const CharacterOffset&); + RefPtr rightWordRange(const CharacterOffset&); + + // Paragraph + RefPtr paragraphForCharacterOffset(const CharacterOffset&); + CharacterOffset nextParagraphEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousParagraphStartCharacterOffset(const CharacterOffset&); + + // Sentence + RefPtr 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, 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> m_objects; HashMap m_renderObjectMapping; @@ -234,20 +407,28 @@ private: HashMap m_nodeObjectMapping; HashSet m_textMarkerNodes; std::unique_ptr m_computedObjectAttributeCache; - static bool gAccessibilityEnabled; - static bool gAccessibilityEnhancedUserInterfaceEnabled; - + WEBCORE_EXPORT static bool gAccessibilityEnabled; + WEBCORE_EXPORT static bool gAccessibilityEnhancedUserInterfaceEnabled; + HashSet m_idsInUse; - - Timer m_notificationPostTimer; + + Timer m_notificationPostTimer; Vector, AXNotification>> m_notificationsToPost; - void notificationPostTimerFired(Timer&); - void handleMenuOpened(Node*); - void handleMenuItemSelected(Node*); + + Timer m_passwordNotificationPostTimer; + + ListHashSet> m_passwordNotificationsToPost; - static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + Timer m_liveRegionChangedPostTimer; + ListHashSet> m_liveRegionObjectsSet; - AXID getAXID(AccessibilityObject*); + Timer m_focusAriaModalNodeTimer; + Node* m_currentAriaModalNode; + ListHashSet m_ariaModalNodesSet; + + AXTextStateChangeIntent m_textSelectionIntent; + bool m_isSynchronizingSelection { false }; + ListHashSet 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::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 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 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, 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::create(RenderObject* renderer) +Ref AccessibilityARIAGrid::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGrid(renderer)); + return adoptRef(*new AccessibilityARIAGrid(renderer)); } bool AccessibilityARIAGrid::addTableCellChild(AccessibilityObject* child, HashSet& appendedRows, unsigned& columnCount) { - if (!child || !child->isTableRow() || child->ariaRoleAttribute() != RowRole) + if (!child || (!is(*child) && !is(*child))) return false; - AccessibilityTableRow* row = toAccessibilityTableRow(child); - if (appendedRows.contains(row)) + auto& row = downcast(*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 appendedRows; unsigned columnCount = 0; - for (RefPtr child = firstChild(); child; child = child->nextSibling()) - addRowDescendant(child.get(), appendedRows, columnCount); + AccessibilityChildrenVector footerSections; + for (RefPtr child = firstChild(); child; child = child->nextSibling()) { + bool footerSection = false; + if (RenderObject* childRenderer = child->renderer()) { + if (is(*childRenderer)) { + RenderTableSection& childSection = downcast(*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(*axCache->getOrCreate(ColumnRole)); + column.setColumnIndex(static_cast(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 @@ -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 create(RenderObject*); + static Ref 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& appendedRows, unsigned& columnCount); bool addTableCellChild(AccessibilityObject*, HashSet& 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::create(RenderObject* renderer) +Ref 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(*parent) && downcast(*parent).isExposableThroughAccessibility()) + return downcast(parent); + } + + return nullptr; } -void AccessibilityARIAGridCell::rowIndexRange(std::pair& rowRange) +void AccessibilityARIAGridCell::rowIndexRange(std::pair& rowRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (parent->isTableRow()) { + if (is(*parent)) { // We already got a table row, use its API. - rowRange.first = toAccessibilityTableRow(parent)->rowIndex(); - } else if (parent->isAccessibilityTable()) { + rowRange.first = downcast(*parent).rowIndex(); + } else if (is(*parent) && downcast(*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(*parent).columnCount(); if (!columnCount) return; @@ -94,17 +91,51 @@ void AccessibilityARIAGridCell::rowIndexRange(std::pair& 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(*parentRowGroup)) + rowSpan = downcast(*parentRowGroup).rowCount() - rowIndex; + // Otherwise, we have to get the index for the current row within the parent row group. + else if (is(*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& columnRange) +void AccessibilityARIAGridCell::columnIndexRange(std::pair& columnRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (!parent->isTableRow() && !parent->isAccessibilityTable()) + if (!is(*parent) + && !(is(*parent) && downcast(*parent).isExposableThroughAccessibility())) return; const AccessibilityChildrenVector& siblings = parent->children(); @@ -116,8 +147,20 @@ void AccessibilityARIAGridCell::columnIndexRange(std::pair& } } - // 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 create(RenderObject*); + static Ref create(RenderObject*); virtual ~AccessibilityARIAGridCell(); // fills in the start location and row span of cell - virtual void rowIndexRange(std::pair& rowRange) override; + void rowIndexRange(std::pair& rowRange) const override; // fills in the start location and column span of cell - virtual void columnIndexRange(std::pair& columnRange) override; + void columnIndexRange(std::pair& 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::create(RenderObject* renderer) +Ref 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(*parent) || !downcast(*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(*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(*parent) || !downcast(*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(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(*parent)) { + AccessibilityTable& tableParent = downcast(*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 create(RenderObject*); + static Ref 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::create(RenderAttachment* renderer) +{ + return adoptRef(*new AccessibilityAttachment(renderer)); +} + +bool AccessibilityAttachment::hasProgress(float* progress) const +{ + auto& progressString = getAttribute(progressAttr); + bool validProgress; + float result = std::max(std::min(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(node())); + if (!is(node())) + return nullptr; + + return downcast(node()); +} + +String AccessibilityAttachment::roleDescription() const +{ + return AXAttachmentRoleText(); +} + +bool AccessibilityAttachment::computeAccessibilityIsIgnored() const +{ + return false; +} + +void AccessibilityAttachment::accessibilityText(Vector& 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 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&) 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::create() +Ref 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(m_parent)) + renderer = downcast(downcast(*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 create(); + static Ref 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 m_areaElement; - RefPtr 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&) override; - virtual bool isImageMapLink() const override { return true; } - virtual bool supportsPath() const override { return true; } -}; + void accessibilityText(Vector&) override; + bool isImageMapLink() const override { return true; } + bool supportsPath() const override { return true; } -ACCESSIBILITY_OBJECT_TYPE_CASTS(AccessibilityImageMapLink, isImageMapLink()) + RefPtr m_areaElement; + RefPtr 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::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 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::create(RenderObject* renderer) +Ref 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(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
    lists and ARIA lists only need to have one child. + //
      ,
        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 create(RenderObject*); + static Ref 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::create(RenderObject* renderer) + +Ref 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(*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(*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(*child); + if (listBoxOption.isSelected()) + listBoxOption.setSelected(false); } for (const auto& obj : children) { if (obj->roleValue() != ListBoxOptionRole) continue; - toAccessibilityListBoxOption(obj.get())->setSelected(true); + downcast(*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(*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(*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(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(*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 create(RenderObject*); + static Ref 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::create() +Ref 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(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(m_optionElement)) return false; - if (!isHTMLOptionElement(m_optionElement)) - return false; - - return toHTMLOptionElement(m_optionElement)->selected(); + return downcast(*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(*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(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(*m_optionElement)) + return downcast(*m_optionElement).label(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->groupLabelText(); + if (is(*m_optionElement)) + return downcast(*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(*m_optionElement)) + return downcast(*m_optionElement).ownerSelectElement(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->ownerSelectElement(); + if (is(*m_optionElement)) + return downcast(*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 create(); + static Ref 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::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(*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 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(m_renderer)) + return false; + + return downcast(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Fence); +} + +bool AccessibilityMathMLElement::isMathSeparatorOperator() const +{ + if (!is(m_renderer)) + return false; + + return downcast(*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 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 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 tag is found + std::pair 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(m_renderer)) + return -1; + + return downcast(*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 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 namespace WebCore { @@ -52,7 +53,7 @@ AccessibilityMediaControl::AccessibilityMediaControl(RenderObject* renderer) { } -PassRefPtr AccessibilityMediaControl::create(RenderObject* renderer) +Ref AccessibilityMediaControl::create(RenderObject* renderer) { ASSERT(renderer->node()); @@ -68,7 +69,7 @@ PassRefPtr 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 mediaEnterFullscreenButtonName(ASCIILiteral("EnterFullscreenButton")); + static NeverDestroyed mediaExitFullscreenButtonName(ASCIILiteral("ExitFullscreenButton")); + static NeverDestroyed mediaMuteButtonName(ASCIILiteral("MuteButton")); + static NeverDestroyed mediaPlayButtonName(ASCIILiteral("PlayButton")); + static NeverDestroyed mediaSeekBackButtonName(ASCIILiteral("SeekBackButton")); + static NeverDestroyed mediaSeekForwardButtonName(ASCIILiteral("SeekForwardButton")); + static NeverDestroyed mediaRewindButtonName(ASCIILiteral("RewindButton")); + static NeverDestroyed mediaReturnToRealtimeButtonName(ASCIILiteral("ReturnToRealtimeButton")); + static NeverDestroyed mediaUnMuteButtonName(ASCIILiteral("UnMuteButton")); + static NeverDestroyed mediaPauseButtonName(ASCIILiteral("PauseButton")); + static NeverDestroyed mediaStatusDisplayName(ASCIILiteral("StatusDisplay")); + static NeverDestroyed mediaCurrentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed mediaTimeRemainingDisplay(ASCIILiteral("TimeRemainingDisplay")); + static NeverDestroyed mediaShowClosedCaptionsButtonName(ASCIILiteral("ShowClosedCaptionsButton")); + static NeverDestroyed mediaHideClosedCaptionsButtonName(ASCIILiteral("HideClosedCaptionsButton")); switch (controlType()) { case MediaEnterFullscreenButton: @@ -134,7 +135,7 @@ String AccessibilityMediaControl::controlTypeName() const break; } - return String(); + return nullAtom; } void AccessibilityMediaControl::accessibilityText(Vector& textOrder) @@ -155,7 +156,7 @@ void AccessibilityMediaControl::accessibilityText(Vector& tex String AccessibilityMediaControl::title() const { - DEFINE_STATIC_LOCAL(const String, controlsPanel, (ASCIILiteral("ControlsPanel"))); + static NeverDestroyed controlsPanel(ASCIILiteral("ControlsPanel")); if (controlType() == MediaControlsPanel) return localizedMediaControlElementString(controlsPanel); @@ -221,9 +222,9 @@ AccessibilityMediaControlsContainer::AccessibilityMediaControlsContainer(RenderO { } -PassRefPtr AccessibilityMediaControlsContainer::create(RenderObject* renderer) +Ref 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 videoElement(ASCIILiteral("VideoElement")); + static NeverDestroyed audioElement(ASCIILiteral("AudioElement")); if (controllingVideoElement()) return videoElement; @@ -265,24 +266,24 @@ AccessibilityMediaTimeline::AccessibilityMediaTimeline(RenderObject* renderer) { } -PassRefPtr AccessibilityMediaTimeline::create(RenderObject* renderer) +Ref 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(*node)) return String(); - float time = toHTMLInputElement(node)->value().toFloat(); + float time = downcast(*node).value().toFloat(); return localizedMediaTimeDescription(time); } String AccessibilityMediaTimeline::helpText() const { - DEFINE_STATIC_LOCAL(const String, slider, (ASCIILiteral("Slider"))); + static NeverDestroyed slider(ASCIILiteral("Slider")); return localizedMediaControlElementHelpText(slider); } @@ -295,9 +296,9 @@ AccessibilityMediaTimeDisplay::AccessibilityMediaTimeDisplay(RenderObject* rende { } -PassRefPtr AccessibilityMediaTimeDisplay::create(RenderObject* renderer) +Ref 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 currentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed 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 create(RenderObject*); + static Ref 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&) override; + void accessibilityText(Vector&) override; }; -class AccessibilityMediaTimeline : public AccessibilitySlider { - +class AccessibilityMediaTimeline final : public AccessibilitySlider { public: - static PassRefPtr create(RenderObject*); + static Ref 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 create(RenderObject*); + static Ref 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 create(RenderObject*); + static Ref 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::create(RenderMenuList* renderer) +Ref 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(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(*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(*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(*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(childObjects[0]->children().size()); + if (is(*childObjects[0]) && optionIndex >= 0 && optionIndex < popupChildrenSize) + downcast(*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 create(RenderMenuList* renderer); + static Ref 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(element)); m_element = element; } @@ -54,7 +54,7 @@ bool AccessibilityMenuListOption::isEnabled() const { // isDisabledFormControl() returns true if the parent