diff options
| author | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2017-05-30 12:48:17 +0200 |
|---|---|---|
| committer | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2017-05-30 12:48:17 +0200 |
| commit | 881da28418d380042aa95a97f0cbd42560a64f7c (patch) | |
| tree | a794dff3274695e99c651902dde93d934ea7a5af /Source/WebCore/accessibility | |
| parent | 7e104c57a70fdf551bb3d22a5d637cdcbc69dbea (diff) | |
| parent | 0fcedcd17cc00d3dd44c718b3cb36c1033319671 (diff) | |
| download | qtwebkit-881da28418d380042aa95a97f0cbd42560a64f7c.tar.gz | |
Merge 'wip/next' into dev
Change-Id: Iff9ee5e23bb326c4371ec8ed81d56f2f05d680e9
Diffstat (limited to 'Source/WebCore/accessibility')
91 files changed, 9390 insertions, 11546 deletions
diff --git a/Source/WebCore/accessibility/AXObjectCache.cpp b/Source/WebCore/accessibility/AXObjectCache.cpp index cd16a9d4b..8eeb73226 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. * @@ -55,11 +55,15 @@ #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" @@ -70,13 +74,17 @@ #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 "ScrollView.h" -#include <wtf/PassRefPtr.h> +#include "TextBoundaries.h" +#include "TextIterator.h" +#include "htmlediting.h" +#include <wtf/DataLog.h> #if ENABLE(VIDEO) #include "MediaControlElements.h" @@ -86,6 +94,10 @@ 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; + AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const { HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id); @@ -107,23 +119,132 @@ void AXComputedObjectAttributeCache::setIgnored(AXID id, AccessibilityObjectIncl bool AXObjectCache::gAccessibilityEnabled = false; bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; -AXObjectCache::AXObjectCache(const Document* doc) - : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) +void AXObjectCache::enableAccessibility() +{ + gAccessibilityEnabled = true; +} + +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_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired) + , m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired) + , m_currentAriaModalNode(nullptr) { - m_document = const_cast<Document*>(doc); + findAriaModalNodes(); } AXObjectCache::~AXObjectCache() { m_notificationPostTimer.stop(); + m_liveRegionChangedPostTimer.stop(); + + for (const auto& object : m_objects.values()) { + detachWrapper(object.get(), CacheDestroyed); + object->detach(CacheDestroyed); + removeAXID(object.get()); + } +} + +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->fastGetAttribute(aria_modalAttr), "true")) + continue; + + m_ariaModalNodesSet.add(element); + } + + // Set the current valid aria-modal node if possible. + updateCurrentAriaModalNode(); +} - HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end(); - for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) { - AccessibilityObject* obj = (*it).value.get(); - detachWrapper(obj); - obj->detach(); - removeAXID(obj); +void AXObjectCache::updateCurrentAriaModalNode() +{ + // There might be multiple nodes with aria-modal=true set. + // We use this function to pick the one we want. + m_currentAriaModalNode = nullptr; + if (m_ariaModalNodesSet.isEmpty()) + return; + + // We only care about the nodes which are visible. + ListHashSet<RefPtr<Node>> visibleNodes; + for (auto& object : m_ariaModalNodesSet) { + if (isNodeVisible(object)) + visibleNodes.add(object); } + + if (visibleNodes.isEmpty()) + return; + + // If any of the node are keyboard focused, we want to pick that. + Node* focusedNode = document().focusedElement(); + for (auto& object : visibleNodes) { + if (focusedNode != nullptr && focusedNode->isDescendantOf(object.get())) { + m_currentAriaModalNode = object.get(); + break; + } + } + + // If none of the nodes are focused, we want to pick the last dialog in the DOM. + if (!m_currentAriaModalNode) + m_currentAriaModalNode = visibleNodes.last().get(); +} + +bool AXObjectCache::isNodeVisible(Node* node) const +{ + if (!is<Element>(node)) + return false; + + RenderObject* renderer = node->renderer(); + if (!renderer) + return false; + const RenderStyle& style = renderer->style(); + if (style.display() == NONE || style.visibility() != VISIBLE) + return false; + + // We also need to consider aria hidden status. + if (!isNodeAriaVisible(node)) + return false; + + return true; +} + +Node* AXObjectCache::ariaModalNode() +{ + // This function returns the valid aria modal node. + if (m_ariaModalNodesSet.isEmpty()) + return nullptr; + + // Check the current valid aria modal node first. + // Usually when one dialog sets aria-modal=true, that dialog is the one we want. + if (isNodeVisible(m_currentAriaModalNode)) + return m_currentAriaModalNode; + + // Recompute the valid aria modal node when m_currentAriaModalNode is null or hidden. + updateCurrentAriaModalNode(); + return isNodeVisible(m_currentAriaModalNode) ? m_currentAriaModalNode : nullptr; } AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) @@ -131,44 +252,41 @@ AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* ar // 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); + AccessibilityObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement); if (!axRenderImage) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children(); - unsigned count = imageChildren.size(); - for (unsigned k = 0; k < count; ++k) { - AccessibilityObject* child = imageChildren[k].get(); - if (!child->isImageMapLink()) + for (const auto& child : axRenderImage->children()) { + if (!is<AccessibilityImageMapLink>(*child)) continue; - if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement) - return child; + if (downcast<AccessibilityImageMapLink>(*child).areaElement() == areaElement) + return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) { if (!gAccessibilityEnabled) - return 0; + return nullptr; // get the focused node in the page - Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); + Document* focusedDocument = page->focusController().focusedOrMainFrame().document(); Element* focusedElement = focusedDocument->focusedElement(); - if (focusedElement && isHTMLAreaElement(focusedElement)) - return focusedImageMapUIElement(toHTMLAreaElement(focusedElement)); + if (is<HTMLAreaElement>(focusedElement)) + return focusedImageMapUIElement(downcast<HTMLAreaElement>(focusedElement)); AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument); if (!obj) - return 0; + return nullptr; if (obj->shouldFocusActiveDescendant()) { if (AccessibilityObject* descendant = obj->activeDescendant()) @@ -185,12 +303,12 @@ AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) AccessibilityObject* AXObjectCache::get(Widget* widget) { if (!widget) - return 0; + return nullptr; AXID axID = m_widgetObjectMapping.get(widget); ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); if (!axID) - return 0; + return nullptr; return m_objects.get(axID); } @@ -198,12 +316,12 @@ AccessibilityObject* AXObjectCache::get(Widget* widget) AccessibilityObject* AXObjectCache::get(RenderObject* renderer) { if (!renderer) - return 0; + return nullptr; AXID axID = m_renderObjectMapping.get(renderer); ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); if (!axID) - return 0; + return nullptr; return m_objects.get(axID); } @@ -211,7 +329,7 @@ AccessibilityObject* AXObjectCache::get(RenderObject* renderer) AccessibilityObject* AXObjectCache::get(Node* node) { if (!node) - return 0; + return nullptr; AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0; ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); @@ -224,14 +342,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); } @@ -240,13 +358,19 @@ AccessibilityObject* AXObjectCache::get(Node* node) // FIXME: This should take a const char*, but one caller passes nullAtom. bool nodeHasRole(Node* node, const String& role) { - if (!node || !node->isElementNode()) + if (!node || !is<Element>(node)) + return false; + + auto& roleValue = downcast<Element>(*node).fastGetAttribute(roleAttr); + if (role.isNull()) + return roleValue.isEmpty(); + if (roleValue.isEmpty()) return false; - return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role); + return SpaceSplitString(roleValue, true).contains(role); } -static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer) +static Ref<AccessibilityObject> createFromRenderer(RenderObject* renderer) { // FIXME: How could renderer->node() ever not be an Element? Node* node = renderer->node(); @@ -258,58 +382,61 @@ static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer return AccessibilityList::create(renderer); // aria tables - if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) + if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid") || nodeHasRole(node, "table")) return AccessibilityARIAGrid::create(renderer); if (nodeHasRole(node, "row")) return AccessibilityARIAGridRow::create(renderer); - if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) + if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "cell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) return AccessibilityARIAGridCell::create(renderer); + // aria tree + if (nodeHasRole(node, "tree")) + return AccessibilityTree::create(renderer); + if (nodeHasRole(node, "treeitem")) + return AccessibilityTreeItem::create(renderer); + #if ENABLE(VIDEO) // media controls if (node && node->isMediaControlElement()) return AccessibilityMediaControl::create(renderer); #endif -#if ENABLE(SVG) - if (renderer->isSVGRoot()) + if (is<RenderSVGRoot>(*renderer)) return AccessibilitySVGRoot::create(renderer); -#endif - 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<RenderBoxModelObject>(*renderer)) { + RenderBoxModelObject& cssBox = downcast<RenderBoxModelObject>(*renderer); + if (is<RenderListBox>(cssBox)) + return AccessibilityListBox::create(&downcast<RenderListBox>(cssBox)); + if (is<RenderMenuList>(cssBox)) + return AccessibilityMenuList::create(&downcast<RenderMenuList>(cssBox)); // standard tables - if (cssBox->isTable()) - return AccessibilityTable::create(toRenderTable(cssBox)); - if (cssBox->isTableRow()) - return AccessibilityTableRow::create(toRenderTableRow(cssBox)); - if (cssBox->isTableCell()) - return AccessibilityTableCell::create(toRenderTableCell(cssBox)); - -#if ENABLE(PROGRESS_ELEMENT) + if (is<RenderTable>(cssBox)) + return AccessibilityTable::create(&downcast<RenderTable>(cssBox)); + if (is<RenderTableRow>(cssBox)) + return AccessibilityTableRow::create(&downcast<RenderTableRow>(cssBox)); + if (is<RenderTableCell>(cssBox)) + return AccessibilityTableCell::create(&downcast<RenderTableCell>(cssBox)); + // progress bar - if (cssBox->isProgress()) - return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); -#endif + if (is<RenderProgress>(cssBox)) + return AccessibilityProgressIndicator::create(&downcast<RenderProgress>(cssBox)); + #if ENABLE(METER_ELEMENT) - if (cssBox->isMeter()) - return AccessibilityProgressIndicator::create(toRenderMeter(cssBox)); + if (is<RenderMeter>(cssBox)) + return AccessibilityProgressIndicator::create(&downcast<RenderMeter>(cssBox)); #endif // input type=range - if (cssBox->isSlider()) - return AccessibilitySlider::create(toRenderSlider(cssBox)); + if (is<RenderSlider>(cssBox)) + return AccessibilitySlider::create(&downcast<RenderSlider>(cssBox)); } return AccessibilityRenderObject::create(renderer); } -static PassRefPtr<AccessibilityObject> createFromNode(Node* node) +static Ref<AccessibilityObject> createFromNode(Node* node) { return AccessibilityNodeObject::create(node); } @@ -317,20 +444,25 @@ static PassRefPtr<AccessibilityObject> createFromNode(Node* node) AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) { if (!widget) - return 0; + return nullptr; if (AccessibilityObject* obj = get(widget)) return obj; - RefPtr<AccessibilityObject> newObj = 0; - if (widget->isFrameView()) - newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget)); - else if (widget->isScrollbar()) - newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget)); + RefPtr<AccessibilityObject> newObj; + if (is<ScrollView>(*widget)) + newObj = AccessibilityScrollView::create(downcast<ScrollView>(widget)); + else if (is<Scrollbar>(*widget)) + newObj = AccessibilityScrollbar::create(downcast<Scrollbar>(widget)); // Will crash later if we have two objects for the same widget. ASSERT(!get(widget)); - + + // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now. + ASSERT(newObj); + if (!newObj) + return nullptr; + getAXID(newObj.get()); m_widgetObjectMapping.set(widget, newObj->axObjectID()); @@ -343,7 +475,7 @@ AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) AccessibilityObject* AXObjectCache::getOrCreate(Node* node) { if (!node) - return 0; + return nullptr; if (AccessibilityObject* obj = get(node)) return obj; @@ -352,20 +484,25 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node) return getOrCreate(node->renderer()); if (!node->parentElement()) - return 0; + return nullptr; // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree. // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes. - bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); - bool isHidden = !node->renderer() && isNodeAriaVisible(node); + bool inCanvasSubtree = lineageOfType<HTMLCanvasElement>(*node->parentElement()).first(); + bool isHidden = isNodeAriaVisible(node); bool insideMeterElement = false; #if ENABLE(METER_ELEMENT) - insideMeterElement = isHTMLMeterElement(node->parentElement()); + insideMeterElement = is<HTMLMeterElement>(*node->parentElement()); #endif if (!inCanvasSubtree && !isHidden && !insideMeterElement) - return 0; + return nullptr; + + // Fallback content is only focusable as long as the canvas is displayed and visible. + // Update the style before Element::isFocusable() gets called. + if (inCanvasSubtree) + node->document().updateStyleIfNeeded(); RefPtr<AccessibilityObject> newObj = createFromNode(node); @@ -379,14 +516,18 @@ AccessibilityObject* AXObjectCache::getOrCreate(Node* node) newObj->init(); attachWrapper(newObj.get()); newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); - + // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then + // it will disappear when this function is finished, leading to a use-after-free. + if (newObj->isDetached()) + return nullptr; + return newObj.get(); } AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) { if (!renderer) - return 0; + return nullptr; if (AccessibilityObject* obj = get(renderer)) return obj; @@ -403,31 +544,35 @@ AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) newObj->init(); attachWrapper(newObj.get()); newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); - + // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then + // it will disappear when this function is finished, leading to a use-after-free. + if (newObj->isDetached()) + return nullptr; + return newObj.get(); } AccessibilityObject* AXObjectCache::rootObject() { if (!gAccessibilityEnabled) - return 0; - - return getOrCreate(m_document->view()); + return nullptr; + + return getOrCreate(m_document.view()); } AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame) { if (!gAccessibilityEnabled) - return 0; + return nullptr; if (!frame) - return 0; + return nullptr; return getOrCreate(frame->view()); } AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) { - RefPtr<AccessibilityObject> obj = 0; + RefPtr<AccessibilityObject> obj = nullptr; // will be filled in... switch (role) { @@ -459,13 +604,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(); @@ -483,8 +628,8 @@ void AXObjectCache::remove(AXID axID) if (!obj) return; - detachWrapper(obj); - obj->detach(); + detachWrapper(obj, ElementDestroyed); + obj->detach(ElementDestroyed, this); removeAXID(obj); // finally remove the object @@ -516,6 +661,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; @@ -533,7 +684,7 @@ void AXObjectCache::remove(Widget* view) } -#if !PLATFORM(WIN) || OS(WINCE) +#if !PLATFORM(WIN) AXID AXObjectCache::platformGenerateAXID() const { static AXID lastUsedID = 0; @@ -598,7 +749,7 @@ void AXObjectCache::textChanged(AccessibilityObject* obj) bool parentAlreadyExists = obj->parentObjectIfExists(); obj->textChanged(); - postNotification(obj, obj->document(), AXObjectCache::AXTextChanged, true); + postNotification(obj, obj->document(), AXObjectCache::AXTextChanged); if (parentAlreadyExists) obj->notifyIfIgnoredValueChanged(); } @@ -610,13 +761,51 @@ void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node) get(node); } -void AXObjectCache::childrenChanged(Node* node) +void AXObjectCache::handleMenuOpened(Node* node) +{ + if (!node || !node->renderer() || !nodeHasRole(node, "menu")) + return; + + postNotification(getOrCreate(node), &document(), AXMenuOpened); +} + +void AXObjectCache::handleLiveRegionCreated(Node* node) +{ + if (!is<Element>(node) || !node->renderer()) + return; + + Element* element = downcast<Element>(node); + String liveRegionStatus = element->fastGetAttribute(aria_liveAttr); + if (liveRegionStatus.isEmpty()) { + const AtomicString& ariaRole = element->fastGetAttribute(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)); } -void AXObjectCache::childrenChanged(RenderObject* renderer) +void AXObjectCache::childrenChanged(RenderObject* renderer, RenderObject* newChild) { + if (!renderer) + return; + + if (newChild) { + handleMenuOpened(newChild->node()); + handleLiveRegionCreated(newChild->node()); + } + childrenChanged(get(renderer)); } @@ -628,40 +817,64 @@ void AXObjectCache::childrenChanged(AccessibilityObject* obj) obj->childrenChanged(); } -void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*) +void AXObjectCache::notificationPostTimerFired() { - RefPtr<Document> protectorForCacheOwner(m_document); - + Ref<Document> protectorForCacheOwner(m_document); m_notificationPostTimer.stop(); - - unsigned i = 0, count = m_notificationsToPost.size(); - for (i = 0; i < count; ++i) { - AccessibilityObject* obj = m_notificationsToPost[i].first.get(); + + // 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_notificationsToPost); + + for (const auto& note : notifications) { + AccessibilityObject* obj = note.first.get(); if (!obj->axObjectID()) continue; + if (!obj->axObjectCache()) + continue; + #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 = static_cast<AccessibilityRenderObject*>(obj); - RenderObject* renderer = renderObj->renderer(); - if (renderer && renderer->view()) - ASSERT(!renderer->view()->layoutState()); + if (is<AccessibilityRenderObject>(*obj)) { + if (auto* renderer = downcast<AccessibilityRenderObject>(*obj).renderer()) + ASSERT(!renderer->view().layoutState()); } #endif + + AXNotification notification = note.second; + + // Ensure that this menu really is a menu. We do this check here so that we don't have to create + // the axChildren when the menu is marked as opening. + if (notification == AXMenuOpened) { + obj->updateChildrenIfNecessary(); + if (obj->roleValue() != MenuRole) + continue; + } - AXNotification notification = m_notificationsToPost[i].second; postPlatformNotification(obj, notification); if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) childrenChanged(obj->parentObject()); } - - m_notificationsToPost.clear(); +} + +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, bool postToElement, PostType postType) +void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, PostTarget postTarget, PostType postType) { if (!renderer) return; @@ -679,10 +892,10 @@ void AXObjectCache::postNotification(RenderObject* renderer, AXNotification noti if (!renderer) return; - postNotification(object.get(), renderer->document(), notification, postToElement, postType); + postNotification(object.get(), &renderer->document(), notification, postTarget, postType); } -void AXObjectCache::postNotification(Node* node, AXNotification notification, bool postToElement, PostType postType) +void AXObjectCache::postNotification(Node* node, AXNotification notification, PostTarget postTarget, PostType postType) { if (!node) return; @@ -700,18 +913,18 @@ void AXObjectCache::postNotification(Node* node, AXNotification notification, bo if (!node) return; - postNotification(object.get(), node->document(), notification, postToElement, postType); + postNotification(object.get(), &node->document(), notification, postTarget, postType); } -void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType) +void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, PostTarget postTarget, PostType postType) { stopCachingComputedObjectAttributes(); - if (object && !postToElement) + if (object && postTarget == TargetObservableParent) object = object->observableObject(); if (!object && document) - object = get(document->renderer()); + object = get(document->renderView()); if (!object) return; @@ -726,33 +939,312 @@ void AXObjectCache::postNotification(AccessibilityObject* object, Document* docu void AXObjectCache::checkedStateChanged(Node* node) { - postNotification(node, AXObjectCache::AXCheckedStateChanged, true); + postNotification(node, AXObjectCache::AXCheckedStateChanged); } +void AXObjectCache::handleMenuItemSelected(Node* node) +{ + if (!node) + return; + + if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox")) + return; + + if (!downcast<Element>(*node).focused() && !equalLettersIgnoringASCIICase(downcast<Element>(*node).fastGetAttribute(aria_selectedAttr), "true")) + return; + + postNotification(getOrCreate(node), &document(), AXMenuListItemSelected); +} + +void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode) +{ + handleMenuItemSelected(newNode); + platformHandleFocusedUIElementChanged(oldNode, newNode); +} + void AXObjectCache::selectedChildrenChanged(Node* node) { - // postToElement is false so that you can pass in any child of an element and it will go up the parent tree + handleMenuItemSelected(node); + + // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree // to find the container which should send out the notification. - postNotification(node, AXSelectedChildrenChanged, false); + postNotification(node, AXSelectedChildrenChanged, TargetObservableParent); } void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) { - // postToElement is false so that you can pass in any child of an element and it will go up the parent tree + if (renderer) + handleMenuItemSelected(renderer->node()); + + // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree // to find the container which should send out the notification. - postNotification(renderer, AXSelectedChildrenChanged, false); + 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; + ASSERT(type != AXTextEditTypeUnknown); + + 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; + ASSERT(deletionType == AXTextEditTypeDelete); + ASSERT(insertionType == AXTextEditTypeInsert || insertionType == AXTextEditTypeTyping || insertionType == AXTextEditTypeDictation || insertionType == AXTextEditTypePaste); + + 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) @@ -769,6 +1261,29 @@ 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(); +} + void AXObjectCache::handleScrollbarUpdate(ScrollView* view) { if (!view) @@ -809,7 +1324,7 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen handleAriaRoleChanged(element); else if (attrName == altAttr || attrName == titleAttr) textChanged(element); - else if (attrName == forAttr && isHTMLLabelElement(element)) + else if (attrName == forAttr && is<HTMLLabelElement>(*element)) labelChanged(element); if (!attrName.localName().string().startsWith("aria-")) @@ -817,8 +1332,10 @@ void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Elemen if (attrName == aria_activedescendantAttr) handleActiveDescendantChanged(element); + else if (attrName == aria_busyAttr) + postNotification(element, AXObjectCache::AXElementBusyChanged); else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr) - postNotification(element, AXObjectCache::AXValueChanged, true); + postNotification(element, AXObjectCache::AXValueChanged); else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr) textChanged(element); else if (attrName == aria_checkedAttr) @@ -828,17 +1345,41 @@ 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, true); + postNotification(element, AXObjectCache::AXInvalidStatusChanged); + else if (attrName == aria_modalAttr) + handleAriaModalChange(element); else - postNotification(element, AXObjectCache::AXAriaAttributeChanged, true); + postNotification(element, AXObjectCache::AXAriaAttributeChanged); +} + +void AXObjectCache::handleAriaModalChange(Node* node) +{ + if (!is<Element>(node)) + return; + + if (!nodeHasRole(node, "dialog") && !nodeHasRole(node, "alertdialog")) + return; + + stopCachingComputedObjectAttributes(); + if (equalLettersIgnoringASCIICase(downcast<Element>(*node).fastGetAttribute(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(); + } + startCachingComputedObjectAttributesUntilTreeMutates(); } void AXObjectCache::labelChanged(Element* element) { - ASSERT(isHTMLLabelElement(element)); - HTMLElement* correspondingControl = toHTMLLabelElement(element)->control(); + ASSERT(is<HTMLLabelElement>(*element)); + HTMLElement* correspondingControl = downcast<HTMLLabelElement>(*element).control(); textChanged(correspondingControl); } @@ -851,13 +1392,12 @@ void AXObjectCache::recomputeIsIgnored(RenderObject* renderer) void AXObjectCache::startCachingComputedObjectAttributesUntilTreeMutates() { if (!m_computedObjectAttributeCache) - m_computedObjectAttributeCache = AXComputedObjectAttributeCache::create(); + m_computedObjectAttributeCache = std::make_unique<AXComputedObjectAttributeCache>(); } void AXObjectCache::stopCachingComputedObjectAttributes() { - if (m_computedObjectAttributeCache) - m_computedObjectAttributeCache.clear(); + m_computedObjectAttributeCache = nullptr; } VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) @@ -875,7 +1415,7 @@ VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& if (!renderer) return VisiblePosition(); - AXObjectCache* cache = renderer->document()->axObjectCache(); + AXObjectCache* cache = renderer->document().axObjectCache(); if (!cache->isIDinUse(textMarkerData.axID)) return VisiblePosition(); @@ -885,6 +1425,449 @@ VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& return visiblePos; } +CharacterOffset AXObjectCache::characterOffsetForTextMarkerData(TextMarkerData& textMarkerData) +{ + if (!isNodeInUse(textMarkerData.node)) + return CharacterOffset(); + + if (textMarkerData.ignored) + return CharacterOffset(); + + return CharacterOffset(textMarkerData.node, textMarkerData.characterStartIndex, textMarkerData.characterOffset); +} + +CharacterOffset AXObjectCache::traverseToOffsetInRange(RefPtr<Range>range, int offset, bool toNodeEnd, bool stayWithinRange) +{ + if (!range) + return CharacterOffset(); + + int offsetInCharacter = 0; + int offsetSoFar = 0; + int remaining = 0; + int lastLength = 0; + Node* currentNode = nullptr; + bool finished = false; + int lastStartOffset = 0; + + TextIterator iterator(range.get()); + + // 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())) + offsetSoFar++; + lastLength = offsetSoFar; + + // 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, toNodeEnd); + 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)) { + offsetSoFar++; + 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 != previousNode) + continue; + } + } + offsetSoFar += currentLength; + } + + if (currentNode == previousNode) + lastLength += currentLength; + else { + lastLength = currentLength; + lastStartOffset = hasReplacedNodeOrBR ? 0 : iterator.range()->startOffset(); + } + + // Break early if we have advanced enough characters. + if (!toNodeEnd && offsetSoFar >= offset) { + offsetInCharacter = offset - (offsetSoFar - lastLength); + finished = true; + break; + } + previousNode = currentNode; + } + + if (!finished) { + offsetInCharacter = lastLength; + if (!toNodeEnd) + remaining = offset - offsetSoFar; + } + + return CharacterOffset(currentNode, lastStartOffset, offsetInCharacter, remaining); +} + +int AXObjectCache::lengthForRange(Range* range) +{ + if (!range) + return -1; + + int length = 0; + for (TextIterator it(range); !it.atEnd(); it.advance()) { + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.text().length()) + length += it.text().length(); + else { + // locate the node and starting offset for this replaced range + Node& node = it.range()->startContainer(); + int offset = it.range()->startOffset(); + if (AccessibilityObject::replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + ++length; + } + } + + return length; +} + +RefPtr<Range> AXObjectCache::rangeForNodeContents(Node* node) +{ + if (!node) + return nullptr; + + Document* document = &node->document(); + if (!document) + return nullptr; + RefPtr<Range> range = Range::create(*document); + ExceptionCode ec = 0; + range->selectNodeContents(node, ec); + return ec ? nullptr : 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 = node1->traverseToChildAt(characterOffset1.offset); + if (!node2->offsetInCharacters() && !isReplacedNodeOrBR(node2)) + node2 = node2->traverseToChildAt(characterOffset2.offset); + + if (!node1 || !node2) + return false; + + RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(node1); + RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(node2); + return range1->compareBoundaryPoints(Range::START_TO_START, range2.get(), IGNORE_EXCEPTION) <= 0; +} + +static Node* resetNodeAndOffsetForReplacedNode(Node* replacedNode, int& offset, int characterCount) +{ + // Use this function to include the replaced node itself in the range we are creating. + if (!replacedNode) + return nullptr; + + RefPtr<Range> nodeRange = AXObjectCache::rangeForNodeContents(replacedNode); + int nodeLength = TextIterator::rangeLength(nodeRange.get()); + offset = characterCount <= nodeLength ? replacedNode->computeNodeIndex() : replacedNode->computeNodeIndex() + 1; + return replacedNode->parentNode(); +} + +static void setRangeStartOrEndWithCharacterOffset(RefPtr<Range> range, const CharacterOffset& characterOffset, bool isStart, ExceptionCode& ec) +{ + if (!range) { + ec = RangeError; + return; + } + if (characterOffset.isNull()) { + ec = TypeError; + return; + } + + int offset = characterOffset.startIndex + characterOffset.offset; + Node* node = characterOffset.node; + if (isReplacedNodeOrBR(node)) + node = resetNodeAndOffsetForReplacedNode(node, offset, characterOffset.offset); + + if (isStart) + range->setStart(node, offset, ec); + else + range->setEnd(node, offset, ec); +} + +RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2) +{ + if (characterOffset1.isNull() || characterOffset2.isNull()) + return nullptr; + + bool alreadyInOrder = characterOffsetsInOrder(characterOffset1, characterOffset2); + CharacterOffset startCharacterOffset = alreadyInOrder ? characterOffset1 : characterOffset2; + CharacterOffset endCharacterOffset = alreadyInOrder ? characterOffset2 : characterOffset1; + + RefPtr<Range> result = Range::create(m_document); + ExceptionCode ec = 0; + setRangeStartOrEndWithCharacterOffset(result, startCharacterOffset, true, ec); + setRangeStartOrEndWithCharacterOffset(result, endCharacterOffset, false, ec); + if (ec) + return nullptr; + + return result; +} + +void AXObjectCache::setTextMarkerDataWithCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return; + + Node* domNode = characterOffset.node; + if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) { + textMarkerData.ignored = true; + return; + } + + RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode); + if (!obj) + return; + + // Convert to visible position. + VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(characterOffset); + int vpOffset = 0; + if (!visiblePosition.isNull()) { + Position deepPos = visiblePosition.deepEquivalent(); + vpOffset = deepPos.deprecatedEditingOffset(); + } + + textMarkerData.axID = obj.get()->axObjectID(); + textMarkerData.node = domNode; + textMarkerData.characterOffset = characterOffset.offset; + textMarkerData.characterStartIndex = characterOffset.startIndex; + textMarkerData.offset = vpOffset; + textMarkerData.affinity = visiblePosition.affinity(); + + this->setNodeInUse(domNode); +} + +CharacterOffset AXObjectCache::startOrEndCharacterOffsetForRange(RefPtr<Range> range, bool isStart) +{ + if (!range) + return CharacterOffset(); + + // If it's end text marker, we want to go to the end of the range, and stay within the range. + bool stayWithinRange = !isStart; + + RefPtr<Range> copyRange = range; + // Change the start of the range, so the character offset starts from node beginning. + int offset = 0; + Node* node = ©Range->startContainer(); + if (node->offsetInCharacters()) { + copyRange = Range::create(range->ownerDocument(), &range->startContainer(), range->startOffset(), &range->endContainer(), range->endOffset()); + CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(node), 0, false); + offset = std::max(copyRange->startOffset() - nodeStartOffset.startIndex, 0); + copyRange->setStart(node, nodeStartOffset.startIndex); + } + + return traverseToOffsetInRange(copyRange, offset, !isStart, stayWithinRange); +} + +void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart) +{ + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + + CharacterOffset characterOffset = startOrEndCharacterOffsetForRange(range, isStart); + if (characterOffset.isNull()) + return; + + setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset); +} + +CharacterOffset AXObjectCache::characterOffsetForNodeAndOffset(Node& node, int offset, TraverseOption option) +{ + Node* domNode = &node; + if (!domNode) + return CharacterOffset(); + + bool toNodeEnd = option & TraverseOptionToNodeEnd; + bool includeStart = option & TraverseOptionIncludeStart; + + // ignoreStart is used to determine if we should go to previous node or + // stay in current node when offset is 0. + if (!toNodeEnd && (offset < 0 || (!offset && !includeStart))) { + // Set the offset to the amount of characters we need to go backwards. + offset = - offset; + CharacterOffset charOffset = CharacterOffset(); + while (offset >= 0 && charOffset.offset <= offset) { + offset -= charOffset.offset; + domNode = previousNode(domNode); + if (domNode) { + charOffset = characterOffsetForNodeAndOffset(*domNode, 0, TraverseOptionToNodeEnd); + } else + return CharacterOffset(); + if (charOffset.offset == offset) + break; + } + if (offset > 0) + charOffset = characterOffsetForNodeAndOffset(*charOffset.node, charOffset.offset - offset, TraverseOptionIncludeStart); + return charOffset; + } + + RefPtr<Range> range = rangeForNodeContents(domNode); + + // Traverse the offset amount of characters forward and see if there's remaining offsets. + // Keep traversing to the next node when there's remaining offsets. + CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, toNodeEnd); + while (!characterOffset.isNull() && characterOffset.remaining() && !toNodeEnd) { + domNode = nextNode(domNode); + if (!domNode) + return CharacterOffset(); + range = rangeForNodeContents(domNode); + characterOffset = traverseToOffsetInRange(range, characterOffset.remaining(), toNodeEnd); + } + + return characterOffset; +} + +void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + memset(&textMarkerData, 0, sizeof(TextMarkerData)); + setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset); +} + +void AXObjectCache::textMarkerDataForNextCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + CharacterOffset next = characterOffset; + do { + next = nextCharacterOffset(next); + textMarkerDataForCharacterOffset(textMarkerData, next); + } while (textMarkerData.ignored); +} + +void AXObjectCache::textMarkerDataForPreviousCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset) +{ + CharacterOffset previous = characterOffset; + do { + previous = previousCharacterOffset(previous); + textMarkerDataForCharacterOffset(textMarkerData, previous); + } while (textMarkerData.ignored); +} + +Node* AXObjectCache::nextNode(Node* node) const +{ + if (!node) + return nullptr; + + return NodeTraversal::nextSkippingChildren(*node); +} + +Node* AXObjectCache::previousNode(Node* node) const +{ + if (!node) + return nullptr; + + // First child of body shouldn't have previous node. + if (node->parentNode() && node->parentNode()->renderer() && node->parentNode()->renderer()->isBody() && !node->previousSibling()) + return nullptr; + + return NodeTraversal::previousSkippingChildren(*node); +} + +VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return VisiblePosition(); + + RefPtr<AccessibilityObject> obj = this->getOrCreate(characterOffset.node); + if (!obj) + return VisiblePosition(); + + // nextVisiblePosition means advancing one character. Use this to calculate the character offset. + VisiblePositionRange vpRange = obj->visiblePositionRange(); + VisiblePosition start = vpRange.start; + VisiblePosition result = start; + for (int i = 0; i < characterOffset.offset; i++) + result = obj->nextVisiblePosition(result); + + return result; +} + +CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(const VisiblePosition& visiblePos) +{ + if (visiblePos.isNull()) + return CharacterOffset(); + + Position deepPos = visiblePos.deepEquivalent(); + Node* domNode = deepPos.deprecatedNode(); + ASSERT(domNode); + + RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode); + if (!obj) + return CharacterOffset(); + + // Use nextVisiblePosition to calculate how many characters we need to traverse to the current position. + VisiblePositionRange vpRange = obj->visiblePositionRange(); + VisiblePosition vp = vpRange.start; + int characterOffset = 0; + Position vpDeepPos = vp.deepEquivalent(); + + VisiblePosition previousVisiblePos; + while (!vpDeepPos.isNull() && !deepPos.equals(vpDeepPos)) { + previousVisiblePos = vp; + vp = obj->nextVisiblePosition(vp); + vpDeepPos = vp.deepEquivalent(); + // Sometimes nextVisiblePosition will give the same VisiblePostion, + // we break here to avoid infinite loop. + if (vpDeepPos.equals(previousVisiblePos.deepEquivalent())) + break; + characterOffset++; + } + + return traverseToOffsetInRange(rangeForNodeContents(obj->node()), characterOffset, false); +} + +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) { // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. @@ -900,28 +1883,378 @@ void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerD if (!domNode) return; - if (domNode->isHTMLElement()) { - HTMLInputElement* inputElement = domNode->toInputElement(); - if (inputElement && inputElement->isPasswordField()) - return; - } + if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) + return; // find or create an accessibility object for this node - AXObjectCache* cache = domNode->document()->axObjectCache(); + AXObjectCache* cache = domNode->document().axObjectCache(); RefPtr<AccessibilityObject> obj = cache->getOrCreate(domNode); textMarkerData.axID = obj.get()->axObjectID(); textMarkerData.node = domNode; textMarkerData.offset = deepPos.deprecatedEditingOffset(); - textMarkerData.affinity = visiblePos.affinity(); + textMarkerData.affinity = visiblePos.affinity(); + + // convert to character offset + CharacterOffset characterOffset = characterOffsetFromVisiblePosition(visiblePos); + textMarkerData.characterOffset = characterOffset.offset; + textMarkerData.characterStartIndex = characterOffset.startIndex; cache->setNodeInUse(domNode); } +CharacterOffset AXObjectCache::nextCharacterOffset(const CharacterOffset& characterOffset, bool ignoreStart) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset + 1, ignoreStart ? TraverseOptionDefault : TraverseOptionIncludeStart); +} + +CharacterOffset AXObjectCache::previousCharacterOffset(const CharacterOffset& characterOffset, bool ignoreStart) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset - 1, ignoreStart ? TraverseOptionDefault : 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; + + c = nextCharacterOffset(characterOffset); + 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, false); + if (c.isNull()) + return characterOffset; + } + + return nextBoundary(c, endWordBoundary); +} + +CharacterOffset AXObjectCache::previousWordStartCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset previousOffset = previousCharacterOffset(characterOffset, false); + if (previousOffset.isNull()) + return CharacterOffset(); + + return startCharacterOffsetOfWord(previousOffset, RightWordIfOnBoundary); +} + +CharacterOffset AXObjectCache::nextWordEndCharacterOffset(const CharacterOffset& characterOffset) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + CharacterOffset nextOffset = nextCharacterOffset(characterOffset); + if (nextOffset.isNull()) + return CharacterOffset(); + + return endCharacterOffsetOfWord(nextOffset, LeftWordIfOnBoundary); +} + +RefPtr<Range> AXObjectCache::leftWordRange(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfWord(characterOffset, LeftWordIfOnBoundary); + CharacterOffset end = endCharacterOffsetOfWord(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +RefPtr<Range> AXObjectCache::rightWordRange(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfWord(characterOffset); + 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 CharacterOffset(); + + Node* boundary = parentEditingBoundary(characterOffset.node); + if (!boundary) + return CharacterOffset(); + + RefPtr<Range> searchRange = rangeForNodeContents(boundary); + Vector<UChar, 1024> string; + unsigned prefixLength = 0; + + ExceptionCode ec = 0; + if (requiresContextForWordBoundary(characterAfter(characterOffset))) { + RefPtr<Range> backwardsScanRange(boundary->document().createRange()); + setRangeStartOrEndWithCharacterOffset(backwardsScanRange, characterOffset, false, ec); + prefixLength = prefixLengthForRange(backwardsScanRange, string); + } + + setRangeStartOrEndWithCharacterOffset(searchRange, characterOffset, true, ec); + CharacterOffset end = startOrEndCharacterOffsetForRange(searchRange, false); + + ASSERT(!ec); + if (ec) + return CharacterOffset(); + + TextIterator it(searchRange.get(), TextIteratorEmitsObjectReplacementCharacters); + unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction); + + if (it.atEnd() && next == string.size()) + return end; + + // The endSentenceBoundary function will include a line break at the end of the sentence. + if (searchFunction == endSentenceBoundary && string[next - 1] == '\n') + next--; + + if (next > prefixLength) + return characterOffsetForNodeAndOffset(*characterOffset.node, characterOffset.offset + next - prefixLength); + + return characterOffset; +} + +CharacterOffset AXObjectCache::previousBoundary(const CharacterOffset& characterOffset, BoundarySearchFunction searchFunction) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + Node* boundary = parentEditingBoundary(characterOffset.node); + if (!boundary) + return CharacterOffset(); + + RefPtr<Range> searchRange = rangeForNodeContents(boundary); + Vector<UChar, 1024> string; + unsigned suffixLength = 0; + + ExceptionCode ec = 0; + if (requiresContextForWordBoundary(characterBefore(characterOffset))) { + RefPtr<Range> forwardsScanRange(boundary->document().createRange()); + forwardsScanRange->setEndAfter(boundary, ec); + setRangeStartOrEndWithCharacterOffset(forwardsScanRange, characterOffset, true, ec); + suffixLength = suffixLengthForRange(forwardsScanRange, string); + } + + setRangeStartOrEndWithCharacterOffset(searchRange, characterOffset, false, ec); + CharacterOffset start = startOrEndCharacterOffsetForRange(searchRange, true); + + ASSERT(!ec); + if (ec) + return CharacterOffset(); + + 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(); + if ((node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) { + // The next variable contains a usable index into a text node + if (&node == characterOffset.node) + next -= characterOffset.startIndex; + 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() - 1] == '\n')) + characterCount = 0; + return characterOffsetForNodeAndOffset(*characterOffset.node, characterCount, TraverseOptionIncludeStart); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfParagraph(const CharacterOffset& characterOffset, EditingBoundaryCrossingRule boundaryCrossingRule) +{ + if (characterOffset.isNull()) + return CharacterOffset(); + + Node* startNode = characterOffset.node; + + if (isRenderedAsNonInlineTableImageOrHR(startNode)) + return startOrEndCharacterOffsetForRange(rangeForNodeContents(startNode), true); + + Node* startBlock = enclosingBlock(startNode); + int offset = characterOffset.startIndex + characterOffset.offset; + Position p(startNode, offset, Position::PositionIsOffsetInAnchor); + Node* highestRoot = highestEditableRoot(p); + Position::AnchorType type = Position::PositionIsOffsetInAnchor; + + Node* 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; + Position p(startNode, offset, Position::PositionIsOffsetInAnchor); + Node* highestRoot = highestEditableRoot(p); + Position::AnchorType type = Position::PositionIsOffsetInAnchor; + + Node* node = findEndOfParagraph(startNode, highestRoot, stayInsideBlock, offset, type, boundaryCrossingRule); + if (type == Position::PositionIsOffsetInAnchor) { + if (node->isTextNode()) { + CharacterOffset startOffset = startOrEndCharacterOffsetForRange(rangeForNodeContents(node), true); + offset -= startOffset.startIndex; + } + return characterOffsetForNodeAndOffset(*node, offset, TraverseOptionIncludeStart); + } + + return startOrEndCharacterOffsetForRange(rangeForNodeContents(node), false); +} + +RefPtr<Range> AXObjectCache::paragraphForCharacterOffset(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfParagraph(characterOffset); + CharacterOffset end = endCharacterOffsetOfParagraph(start); + + return rangeForUnorderedCharacterOffsets(start, end); +} + +CharacterOffset AXObjectCache::nextParagraphEndCharacterOffset(const CharacterOffset& characterOffset) +{ + // make sure we move off of a paragraph end + CharacterOffset next = nextCharacterOffset(characterOffset); + + // We should skip the following BR node. + if (characterOffsetNodeIsBR(next) && !characterOffsetNodeIsBR(characterOffset)) + next = nextCharacterOffset(next); + + return endCharacterOffsetOfParagraph(next); +} + +CharacterOffset AXObjectCache::previousParagraphStartCharacterOffset(const CharacterOffset& characterOffset) +{ + // make sure we move off of a paragraph start + CharacterOffset previous = previousCharacterOffset(characterOffset, false); + + // We should skip the preceding BR node. + if (characterOffsetNodeIsBR(previous) && !characterOffsetNodeIsBR(characterOffset)) + previous = previousCharacterOffset(previous, false); + + return startCharacterOffsetOfParagraph(previous); +} + +CharacterOffset AXObjectCache::startCharacterOffsetOfSentence(const CharacterOffset& characterOffset) +{ + return previousBoundary(characterOffset, startSentenceBoundary); +} + +CharacterOffset AXObjectCache::endCharacterOffsetOfSentence(const CharacterOffset& characterOffset) +{ + return nextBoundary(characterOffset, endSentenceBoundary); +} + +RefPtr<Range> AXObjectCache::sentenceForCharacterOffset(const CharacterOffset& characterOffset) +{ + CharacterOffset start = startCharacterOffsetOfSentence(characterOffset); + CharacterOffset end = endCharacterOffsetOfSentence(start); + return rangeForUnorderedCharacterOffsets(start, end); +} + +CharacterOffset AXObjectCache::nextSentenceEndCharacterOffset(const CharacterOffset& characterOffset) +{ + // Make sure we move off of a sentence end. + return endCharacterOffsetOfSentence(nextCharacterOffset(characterOffset)); +} + +CharacterOffset AXObjectCache::previousSentenceStartCharacterOffset(const CharacterOffset& characterOffset) +{ + // Make sure we move off of a sentence start. + CharacterOffset previous = previousCharacterOffset(characterOffset, false); + + // We should skip the preceding BR node. + if (!previous.isNull() && previous.node->hasTagName(brTag) && !characterOffset.node->hasTagName(brTag)) + previous = previousCharacterOffset(previous, false); + + return startCharacterOffsetOfSentence(previous); +} + const Element* AXObjectCache::rootAXEditableElement(const Node* node) { const Element* result = node->rootEditableElement(); - const Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); + const Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); for (; element; element = element->parentElement()) { if (nodeIsTextControl(element)) @@ -931,6 +2264,22 @@ const Element* AXObjectCache::rootAXEditableElement(const Node* node) return result; } +void AXObjectCache::clearTextMarkerNodesInUse(Document* document) +{ + if (!document) + return; + + // Check each node to see if it's inside the document being deleted, of if it no longer belongs to a document. + HashSet<Node*> nodesToDelete; + for (const auto& node : m_textMarkerNodes) { + if (!node->inDocument() || &(node)->document() == document) + nodesToDelete.add(node); + } + + for (const auto& node : nodesToDelete) + m_textMarkerNodes.remove(node); +} + bool AXObjectCache::nodeIsTextControl(const Node* node) { if (!node) @@ -944,11 +2293,38 @@ bool isNodeAriaVisible(Node* node) { if (!node) return false; + + // 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 (is<Element>(*testNode)) { + const AtomicString& ariaHiddenValue = downcast<Element>(*testNode).fastGetAttribute(aria_hiddenAttr); + if (equalLettersIgnoringASCIICase(ariaHiddenValue, "true")) + return false; + + bool ariaHiddenFalse = equalLettersIgnoringASCIICase(ariaHiddenValue, "false"); + if (!testNode->renderer() && !ariaHiddenFalse) + return false; + if (!ariaHiddenFalsePresent && ariaHiddenFalse) + ariaHiddenFalsePresent = true; + } + } - if (!node->isElementNode()) - return false; - - return equalIgnoringCase(toElement(node)->getAttribute(aria_hiddenAttr), "false"); + return !requiresAriaHiddenFalse || ariaHiddenFalsePresent; +} + +AccessibilityObject* AXObjectCache::rootWebArea() +{ + AccessibilityObject* rootObject = this->rootObject(); + if (!rootObject || !rootObject->isAccessibilityScrollView()) + return nullptr; + return downcast<AccessibilityScrollView>(*rootObject).webAreaObject(); } AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache) @@ -963,6 +2339,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 52b4e2fb9..ab11f32ec 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 @@ -26,12 +26,16 @@ #ifndef AXObjectCache_h #define AXObjectCache_h +#include "AXTextStateChangeIntent.h" #include "AccessibilityObject.h" +#include "Range.h" #include "Timer.h" +#include "VisibleUnits.h" #include <limits.h> #include <wtf/Forward.h> #include <wtf/HashMap.h> #include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> #include <wtf/RefPtr.h> namespace WebCore { @@ -49,19 +53,41 @@ 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: - static PassOwnPtr<AXComputedObjectAttributeCache> create() { return adoptPtr(new AXComputedObjectAttributeCache()); } - AccessibilityObjectInclusion getIgnored(AXID) const; void setIgnored(AXID, AccessibilityObjectInclusion); private: - AXComputedObjectAttributeCache() { } - struct CachedAXObjectAttributes { CachedAXObjectAttributes() : ignored(DefaultBehavior) { } @@ -71,20 +97,26 @@ private: HashMap<AXID, CachedAXObjectAttributes> m_idMapping; }; +#if !PLATFORM(COCOA) +enum AXTextChange { AXTextInserted, AXTextDeleted, AXTextAttributesChanged }; +#endif + +enum PostTarget { TargetElement, TargetObservableParent }; + enum PostType { PostSynchronously, PostAsynchronously }; class AXObjectCache { WTF_MAKE_NONCOPYABLE(AXObjectCache); WTF_MAKE_FAST_ALLOCATED; public: - explicit AXObjectCache(const Document*); + 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,10 +136,10 @@ public: void remove(Widget*); void remove(AXID); - void detachWrapper(AccessibilityObject*); + void detachWrapper(AccessibilityObject*, AccessibilityDetachmentType); void attachWrapper(AccessibilityObject*); - void childrenChanged(Node*); - void childrenChanged(RenderObject*); + void childrenChanged(Node*, Node* newChild = nullptr); + void childrenChanged(RenderObject*, RenderObject* newChild = nullptr); void childrenChanged(AccessibilityObject*); void checkedStateChanged(Node*); void selectedChildrenChanged(Node*); @@ -124,19 +156,25 @@ 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() { gAccessibilityEnabled = true; } + 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; } #else static void enableAccessibility() { } + static void disableAccessibility() { } static void setEnhancedUserInterfaceAccessibility(bool) { } static bool accessibilityEnabled() { return false; } static bool accessibilityEnhancedUserInterfaceEnabled() { return false; } @@ -145,7 +183,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*); @@ -154,7 +191,34 @@ 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&); + CharacterOffset nextCharacterOffset(const CharacterOffset&, bool ignoreStart = true); + CharacterOffset previousCharacterOffset(const CharacterOffset&, bool ignoreStart = true); + void startOrEndTextMarkerDataForRange(TextMarkerData&, RefPtr<Range>, bool); + AccessibilityObject* accessibilityObjectForTextMarkerData(TextMarkerData&); + RefPtr<Range> rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&); + static RefPtr<Range> rangeForNodeContents(Node*); + static int lengthForRange(Range*); + + // Word boundary + CharacterOffset nextWordEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousWordStartCharacterOffset(const CharacterOffset&); + RefPtr<Range> leftWordRange(const CharacterOffset&); + RefPtr<Range> rightWordRange(const CharacterOffset&); + + // Paragraph + RefPtr<Range> paragraphForCharacterOffset(const CharacterOffset&); + CharacterOffset nextParagraphEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousParagraphStartCharacterOffset(const CharacterOffset&); + + // Sentence + RefPtr<Range> sentenceForCharacterOffset(const CharacterOffset&); + CharacterOffset nextSentenceEndCharacterOffset(const CharacterOffset&); + CharacterOffset previousSentenceStartCharacterOffset(const CharacterOffset&); enum AXNotification { AXActiveDescendantChanged, @@ -164,31 +228,43 @@ public: AXFocusedUIElementChanged, AXLayoutComplete, AXLoadComplete, + AXNewDocumentLoadComplete, AXSelectedChildrenChanged, AXSelectedTextChanged, AXValueChanged, AXScrolledToAnchor, + AXLiveRegionCreated, AXLiveRegionChanged, AXMenuListItemSelected, AXMenuListValueChanged, + AXMenuClosed, + AXMenuOpened, AXRowCountChanged, AXRowCollapsed, AXRowExpanded, + AXExpandedChanged, AXInvalidStatusChanged, AXTextChanged, - AXAriaAttributeChanged + AXAriaAttributeChanged, + AXElementBusyChanged }; - void postNotification(RenderObject*, AXNotification, bool postToElement, PostType = PostAsynchronously); - void postNotification(Node*, AXNotification, bool postToElement, PostType = PostAsynchronously); - void postNotification(AccessibilityObject*, Document*, AXNotification, bool postToElement, PostType = PostAsynchronously); + void postNotification(RenderObject*, AXNotification, PostTarget = TargetElement, PostType = PostAsynchronously); + 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 setTextSelectionIntent(const AXTextStateChangeIntent&); + void setIsSynchronizingSelection(bool); - void nodeTextChangeNotification(Node*, AXTextChange, unsigned offset, const String&); + 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*); enum AXLoadingEvent { AXLoadingStarted, @@ -199,16 +275,32 @@ public: void frameLoadingEventNotification(Frame*, AXLoadingEvent); - bool nodeHasRole(Node*, const AtomicString& role); + void clearTextMarkerNodesInUse(Document*); void startCachingComputedObjectAttributesUntilTreeMutates(); void stopCachingComputedObjectAttributes(); AXComputedObjectAttributeCache* computedObjectAttributeCache() { return m_computedObjectAttributeCache.get(); } + Document& document() const { return m_document; } + +#if PLATFORM(MAC) + static void setShouldRepostNotificationsForTests(bool value); +#endif + protected: void postPlatformNotification(AccessibilityObject*, AXNotification); - void nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned offset, const String&); + void platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode); + +#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*); @@ -217,27 +309,80 @@ 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 }; + Node* nextNode(Node*) const; + Node* previousNode(Node*) const; + CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, bool, 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 startOrEndCharacterOffsetForRange(RefPtr<Range>, bool); + 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&); private: - Document* m_document; - HashMap<AXID, RefPtr<AccessibilityObject> > m_objects; + AccessibilityObject* rootWebArea(); + + static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + + AXID getAXID(AccessibilityObject*); + + void notificationPostTimerFired(); + + void liveRegionChangedNotificationPostTimerFired(); + + void postTextStateChangeNotification(AccessibilityObject*, const AXTextStateChangeIntent&, const VisibleSelection&); + + bool enqueuePasswordValueChangeNotification(AccessibilityObject*); + void passwordNotificationPostTimerFired(); + + void handleMenuOpened(Node*); + void handleLiveRegionCreated(Node*); + void handleMenuItemSelected(Node*); + + // aria-modal related + void findAriaModalNodes(); + void updateCurrentAriaModalNode(); + bool isNodeVisible(Node*) const; + + Document& m_document; + HashMap<AXID, RefPtr<AccessibilityObject>> m_objects; HashMap<RenderObject*, AXID> m_renderObjectMapping; HashMap<Widget*, AXID> m_widgetObjectMapping; HashMap<Node*, AXID> m_nodeObjectMapping; HashSet<Node*> m_textMarkerNodes; - OwnPtr<AXComputedObjectAttributeCache> m_computedObjectAttributeCache; - static bool gAccessibilityEnabled; - static bool gAccessibilityEnhancedUserInterfaceEnabled; - + std::unique_ptr<AXComputedObjectAttributeCache> m_computedObjectAttributeCache; + WEBCORE_EXPORT static bool gAccessibilityEnabled; + WEBCORE_EXPORT static bool gAccessibilityEnhancedUserInterfaceEnabled; + HashSet<AXID> m_idsInUse; + + Timer m_notificationPostTimer; + Vector<std::pair<RefPtr<AccessibilityObject>, AXNotification>> m_notificationsToPost; + + Timer m_passwordNotificationPostTimer; + + ListHashSet<RefPtr<AccessibilityObject>> m_passwordNotificationsToPost; - Timer<AXObjectCache> m_notificationPostTimer; - Vector<pair<RefPtr<AccessibilityObject>, AXNotification> > m_notificationsToPost; - void notificationPostTimerFired(Timer<AXObjectCache>*); - - static AccessibilityObject* focusedImageMapUIElement(HTMLAreaElement*); + Timer m_liveRegionChangedPostTimer; + ListHashSet<RefPtr<AccessibilityObject>> m_liveRegionObjectsSet; - AXID getAXID(AccessibilityObject*); + Node* m_currentAriaModalNode; + ListHashSet<Node*> m_ariaModalNodesSet; + + AXTextStateChangeIntent m_textSelectionIntent; + bool m_isSynchronizingSelection { false }; }; class AXAttributeCacheEnabler @@ -245,11 +390,13 @@ 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*); @@ -257,56 +404,78 @@ bool isNodeAriaVisible(Node*); #if !HAVE(ACCESSIBILITY) inline AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID) const { return DefaultBehavior; } inline void AXComputedObjectAttributeCache::setIgnored(AXID, AccessibilityObjectInclusion) { } -inline AXObjectCache::AXObjectCache(const Document* doc) : m_document(const_cast<Document*>(doc)), m_notificationPostTimer(this, 0) { } +inline AXObjectCache::AXObjectCache(Document& document) : m_document(document), m_notificationPostTimer(nullptr), m_passwordNotificationPostTimer(nullptr), m_liveRegionChangedPostTimer(nullptr) { } 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*) { } -inline void AXObjectCache::childrenChanged(Node*) { } +inline void AXObjectCache::childrenChanged(RenderObject*, RenderObject*) { } +inline void AXObjectCache::childrenChanged(Node*, Node*) { } inline void AXObjectCache::childrenChanged(AccessibilityObject*) { } inline void AXObjectCache::textChanged(RenderObject*) { } inline void AXObjectCache::textChanged(Node*) { } inline void AXObjectCache::textChanged(AccessibilityObject*) { } inline void AXObjectCache::updateCacheAfterNodeIsAttached(Node*) { } -inline void AXObjectCache::detachWrapper(AccessibilityObject*) { } +inline void AXObjectCache::detachWrapper(AccessibilityObject*, AccessibilityDetachmentType) { } inline void AXObjectCache::frameLoadingEventNotification(Frame*, AXLoadingEvent) { } inline void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent) { } 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::handleScrolledToAnchor(const Node*) { } -inline void AXObjectCache::nodeTextChangeNotification(Node*, AXTextChange, unsigned, const String&) { } -inline void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) { } -inline void AXObjectCache::postNotification(AccessibilityObject*, Document*, AXNotification, bool, PostType) { } -inline void AXObjectCache::postNotification(RenderObject*, AXNotification, bool, PostType) { } -inline void AXObjectCache::postNotification(Node*, AXNotification, bool, PostType) { } +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 RefPtr<Range> AXObjectCache::rangeForNodeContents(Node*) { return nullptr; } inline void AXObjectCache::remove(AXID) { } inline void AXObjectCache::remove(RenderObject*) { } inline void AXObjectCache::remove(Node*) { } inline void AXObjectCache::remove(Widget*) { } inline void AXObjectCache::selectedChildrenChanged(RenderObject*) { } inline void AXObjectCache::selectedChildrenChanged(Node*) { } +inline void AXObjectCache::setIsSynchronizingSelection(bool) { } +inline void AXObjectCache::setTextSelectionIntent(const AXTextStateChangeIntent&) { } +inline RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&) { return nullptr; } +inline CharacterOffset AXObjectCache::startOrEndCharacterOffsetForRange(RefPtr<Range>, bool) { 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 } diff --git a/Source/WebCore/accessibility/AXTextStateChangeIntent.h b/Source/WebCore/accessibility/AXTextStateChangeIntent.h new file mode 100644 index 000000000..389dd9aa7 --- /dev/null +++ b/Source/WebCore/accessibility/AXTextStateChangeIntent.h @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#ifndef AXTextStateChangeIntent_h +#define AXTextStateChangeIntent_h + +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) + { } +}; + +} + +#endif // AXTextStateChangeIntent_h diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp b/Source/WebCore/accessibility/AccessibilityARIAGrid.cpp index 5632f5aaa..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,13 +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" - -using namespace std; +#include "RenderTableSection.h" namespace WebCore { @@ -49,36 +49,36 @@ AccessibilityARIAGrid::~AccessibilityARIAGrid() { } -PassRefPtr<AccessibilityARIAGrid> AccessibilityARIAGrid::create(RenderObject* renderer) +Ref<AccessibilityARIAGrid> AccessibilityARIAGrid::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGrid(renderer)); + return adoptRef(*new AccessibilityARIAGrid(renderer)); } bool AccessibilityARIAGrid::addTableCellChild(AccessibilityObject* child, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount) { - if (!child || !child->isTableRow() || child->ariaRoleAttribute() != RowRole) + if (!child || (!is<AccessibilityTableRow>(*child) && !is<AccessibilityARIAGridRow>(*child))) return false; - AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(child); - if (appendedRows.contains(row)) + auto& row = downcast<AccessibilityTableRow>(*child); + if (appendedRows.contains(&row)) return false; // store the maximum number of columns - unsigned rowCellCount = row->children().size(); + unsigned rowCellCount = row.children().size(); if (rowCellCount > columnCount) columnCount = rowCellCount; - row->setRowIndex((int)m_rows.size()); - m_rows.append(row); + row.setRowIndex((int)m_rows.size()); + m_rows.append(&row); // Try adding the row if it's not ignoring accessibility, // otherwise add its children (the cells) as the grid's children. - if (!row->accessibilityIsIgnored()) - m_children.append(row); + if (!row.accessibilityIsIgnored()) + m_children.append(&row); else - m_children.appendVector(row->children()); + m_children.appendVector(row.children()); - appendedRows.add(row); + appendedRows.add(&row); return true; } @@ -87,13 +87,11 @@ 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. - AccessibilityChildrenVector children = rowChild->children(); - size_t length = children.size(); - for (size_t i = 0; i < length; ++i) - addRowDescendant(children[i].get(), appendedRows, columnCount); + // 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 addTableCellChild(rowChild, appendedRows, columnCount); } @@ -102,7 +100,7 @@ void AccessibilityARIAGrid::addChildren() { ASSERT(!m_haveChildren); - if (!isAccessibilityTable()) { + if (!isExposableThroughAccessibility()) { AccessibilityRenderObject::addChildren(); return; } @@ -111,22 +109,38 @@ void AccessibilityARIAGrid::addChildren() if (!m_renderer) return; - AXObjectCache* axCache = m_renderer->document()->axObjectCache(); + AXObjectCache* axCache = m_renderer->document().axObjectCache(); - // add only rows that are labeled as aria rows + // Add the children rows but be mindful in case there are footer sections in this table. HashSet<AccessibilityObject*> appendedRows; unsigned columnCount = 0; - for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) - addRowDescendant(child.get(), appendedRows, columnCount); + AccessibilityChildrenVector footerSections; + for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) { + bool footerSection = false; + if (RenderObject* childRenderer = child->renderer()) { + if (is<RenderTableSection>(*childRenderer)) { + RenderTableSection& childSection = downcast<RenderTableSection>(*childRenderer); + if (&childSection == childSection.table()->footer()) { + footerSections.append(child); + footerSection = true; + } + } + } + if (!footerSection) + addRowDescendant(child.get(), appendedRows, columnCount); + } + + for (const auto& footerSection : footerSections) + addRowDescendant(footerSection.get(), appendedRows, columnCount); // make the columns based on the number of columns in the first body for (unsigned i = 0; i < columnCount; ++i) { - AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole)); - column->setColumnIndex((int)i); - column->setParent(this); - m_columns.append(column); - if (!column->accessibilityIsIgnored()) - m_children.append(column); + auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole)); + column.setColumnIndex(static_cast<int>(i)); + column.setParent(this); + m_columns.append(&column); + if (!column.accessibilityIsIgnored()) + m_children.append(&column); } AccessibilityObject* headerContainerObject = headerContainer(); diff --git a/Source/WebCore/accessibility/AccessibilityARIAGrid.h b/Source/WebCore/accessibility/AccessibilityARIAGrid.h index 22910e0a3..f16cb916a 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. * @@ -37,23 +37,21 @@ namespace WebCore { class AccessibilityTableCell; class AccessibilityTableHeaderContainer; -class AccessibilityARIAGrid : public AccessibilityTable { - -private: - explicit AccessibilityARIAGrid(RenderObject*); +class AccessibilityARIAGrid final : public AccessibilityTable { public: - static PassRefPtr<AccessibilityARIAGrid> create(RenderObject*); + static Ref<AccessibilityARIAGrid> create(RenderObject*); virtual ~AccessibilityARIAGrid(); - - virtual bool isAriaTable() const { return true; } - virtual void addChildren(); + virtual void addChildren() override; private: + explicit AccessibilityARIAGrid(RenderObject*); + // ARIA treegrids and grids support selected rows. - virtual bool supportsSelectedRows() { return true; } - virtual bool isMultiSelectable() const { return true; } - virtual bool isTableExposableThroughAccessibility() const { return true; } + virtual bool supportsSelectedRows() override { return true; } + virtual bool isMultiSelectable() const override { return true; } + virtual bool computeIsTableExposableThroughAccessibility() const override { return true; } + virtual bool isAriaTable() const override { return true; } void addRowDescendant(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); bool addTableCellChild(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridCell.cpp index 9c3b1679d..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,11 +32,12 @@ #include "AccessibilityObject.h" #include "AccessibilityTable.h" #include "AccessibilityTableRow.h" - -using namespace std; +#include "HTMLNames.h" namespace WebCore { +using namespace HTMLNames; + AccessibilityARIAGridCell::AccessibilityARIAGridCell(RenderObject* renderer) : AccessibilityTableCell(renderer) { @@ -46,47 +47,41 @@ AccessibilityARIAGridCell::~AccessibilityARIAGridCell() { } -PassRefPtr<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer) +Ref<AccessibilityARIAGridCell> AccessibilityARIAGridCell::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGridCell(renderer)); + return adoptRef(*new AccessibilityARIAGridCell(renderer)); } -AccessibilityObject* AccessibilityARIAGridCell::parentTable() const +AccessibilityTable* AccessibilityARIAGridCell::parentTable() const { - AccessibilityObject* parent = parentObjectUnignored(); - if (!parent) - return 0; - - if (parent->isAccessibilityTable()) - return 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 0; - - return parent; + // ARIA gridcells may have multiple levels of unignored ancestors that are not the parent table, + // including rows and interactive rowgroups. In addition, poorly-formed grids may contain elements + // which pass the tests for inclusion. + for (AccessibilityObject* parent = parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) { + if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) + return downcast<AccessibilityTable>(parent); + } + + return nullptr; } -void AccessibilityARIAGridCell::rowIndexRange(pair<unsigned, unsigned>& rowRange) +void AccessibilityARIAGridCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (parent->isTableRow()) { + if (is<AccessibilityTableRow>(*parent)) { // We already got a table row, use its API. - rowRange.first = static_cast<AccessibilityTableRow*>(parent)->rowIndex(); - } else if (parent->isAccessibilityTable()) { + rowRange.first = downcast<AccessibilityTableRow>(*parent).rowIndex(); + } else if (is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) { // We reached the parent table, so we need to inspect its // children to determine the row index for the cell in it. - unsigned columnCount = static_cast<AccessibilityTable*>(parent)->columnCount(); + unsigned columnCount = downcast<AccessibilityTable>(*parent).columnCount(); if (!columnCount) return; - AccessibilityChildrenVector siblings = parent->children(); + const auto& siblings = parent->children(); unsigned childrenSize = siblings.size(); for (unsigned k = 0; k < childrenSize; ++k) { if (siblings[k].get() == this) { @@ -96,20 +91,54 @@ void AccessibilityARIAGridCell::rowIndexRange(pair<unsigned, unsigned>& rowRange } } - // 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); } -void AccessibilityARIAGridCell::columnIndexRange(pair<unsigned, unsigned>& columnRange) +unsigned AccessibilityARIAGridCell::ariaRowSpanWithRowIndex(unsigned rowIndex) const +{ + unsigned rowSpan = AccessibilityTableCell::ariaRowSpan(); + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent) + return 1; + + // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group. + if (!rowSpan) { + // rowSpan defaults to 1. + rowSpan = 1; + if (AccessibilityObject* parentRowGroup = this->parentRowGroup()) { + // If the row group is the parent table, we use total row count to calculate the span. + if (is<AccessibilityTable>(*parentRowGroup)) + rowSpan = downcast<AccessibilityTable>(*parentRowGroup).rowCount() - rowIndex; + // Otherwise, we have to get the index for the current row within the parent row group. + else if (is<AccessibilityTableRow>(*parent)) { + const auto& siblings = parentRowGroup->children(); + unsigned rowCount = siblings.size(); + for (unsigned k = 0; k < rowCount; ++k) { + if (siblings[k].get() == parent) { + rowSpan = rowCount - k; + break; + } + } + } + } + } + + return rowSpan; +} + +void AccessibilityARIAGridCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const { AccessibilityObject* parent = parentObjectUnignored(); if (!parent) return; - if (!parent->isTableRow() && !parent->isAccessibilityTable()) + if (!is<AccessibilityTableRow>(*parent) + && !(is<AccessibilityTable>(*parent) && downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility())) return; - AccessibilityChildrenVector siblings = parent->children(); + const AccessibilityChildrenVector& siblings = parent->children(); unsigned childrenSize = siblings.size(); for (unsigned k = 0; k < childrenSize; ++k) { if (siblings[k].get() == this) { @@ -118,8 +147,20 @@ void AccessibilityARIAGridCell::columnIndexRange(pair<unsigned, unsigned>& colum } } - // 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 d5ae4ac59..aa77136be 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. * @@ -33,21 +33,22 @@ namespace WebCore { -class AccessibilityARIAGridCell : public AccessibilityTableCell { - -private: - explicit AccessibilityARIAGridCell(RenderObject*); +class AccessibilityARIAGridCell final : public AccessibilityTableCell { public: - static PassRefPtr<AccessibilityARIAGridCell> create(RenderObject*); + static Ref<AccessibilityARIAGridCell> create(RenderObject*); virtual ~AccessibilityARIAGridCell(); // fills in the start location and row span of cell - virtual void rowIndexRange(pair<unsigned, unsigned>& rowRange); + virtual void rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const override; // fills in the start location and column span of cell - virtual void columnIndexRange(pair<unsigned, unsigned>& columnRange); + virtual void columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const override; -protected: - virtual AccessibilityObject* parentTable() const; +private: + explicit AccessibilityARIAGridCell(RenderObject*); + + virtual AccessibilityTable* parentTable() const override; + AccessibilityObject* parentRowGroup() const; + unsigned ariaRowSpanWithRowIndex(unsigned index) const; }; } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp b/Source/WebCore/accessibility/AccessibilityARIAGridRow.cpp index 39edf702f..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. * @@ -33,8 +33,6 @@ #include "AccessibilityTable.h" #include "RenderObject.h" -using namespace std; - namespace WebCore { AccessibilityARIAGridRow::AccessibilityARIAGridRow(RenderObject* renderer) @@ -46,9 +44,9 @@ AccessibilityARIAGridRow::~AccessibilityARIAGridRow() { } -PassRefPtr<AccessibilityARIAGridRow> AccessibilityARIAGridRow::create(RenderObject* renderer) +Ref<AccessibilityARIAGridRow> AccessibilityARIAGridRow::create(RenderObject* renderer) { - return adoptRef(new AccessibilityARIAGridRow(renderer)); + return adoptRef(*new AccessibilityARIAGridRow(renderer)); } bool AccessibilityARIAGridRow::isARIATreeGridRow() const @@ -65,7 +63,7 @@ void AccessibilityARIAGridRow::disclosedRows(AccessibilityChildrenVector& disclo // The contiguous disclosed rows will be the rows in the table that // have an aria-level of plus 1 from this row. AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(*parent) || !downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) return; // Search for rows that match the correct level. @@ -75,7 +73,7 @@ void AccessibilityARIAGridRow::disclosedRows(AccessibilityChildrenVector& disclo return; unsigned level = hierarchicalLevel(); - AccessibilityChildrenVector& allRows = static_cast<AccessibilityTable*>(parent)->rows(); + auto& allRows = downcast<AccessibilityTable>(*parent).rows(); int rowCount = allRows.size(); for (int k = index + 1; k < rowCount; ++k) { AccessibilityObject* row = allRows[k].get(); @@ -92,20 +90,20 @@ AccessibilityObject* AccessibilityARIAGridRow::disclosedByRow() const // The row that discloses this one is the row in the table // that is aria-level subtract 1 from this row. AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isAccessibilityTable()) - return 0; + if (!is<AccessibilityTable>(*parent) || !downcast<AccessibilityTable>(*parent).isExposableThroughAccessibility()) + return nullptr; // If the level is 1 or less, than nothing discloses this row. unsigned level = hierarchicalLevel(); if (level <= 1) - return 0; + return nullptr; // Search for the previous row that matches the correct level. int index = rowIndex(); - AccessibilityChildrenVector& allRows = static_cast<AccessibilityTable*>(parent)->rows(); + auto& allRows = downcast<AccessibilityTable>(parent)->rows(); int rowCount = allRows.size(); if (index >= rowCount) - return 0; + return nullptr; for (int k = index - 1; k >= 0; --k) { AccessibilityObject* row = allRows[k].get(); @@ -113,33 +111,38 @@ AccessibilityObject* AccessibilityARIAGridRow::disclosedByRow() const return row; } - return 0; + return nullptr; +} + +AccessibilityObject* AccessibilityARIAGridRow::parentObjectUnignored() const +{ + return parentTable(); } -AccessibilityObject* AccessibilityARIAGridRow::parentTable() const +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 parent; + if (is<AccessibilityTable>(*parent)) { + AccessibilityTable& tableParent = downcast<AccessibilityTable>(*parent); + if (tableParent.isExposableThroughAccessibility() && tableParent.isAriaTable()) + return &tableParent; + } } - return 0; + return nullptr; } AccessibilityObject* AccessibilityARIAGridRow::headerObject() { - AccessibilityChildrenVector rowChildren = children(); - unsigned childrenCount = rowChildren.size(); - for (unsigned i = 0; i < childrenCount; ++i) { - AccessibilityObject* cell = rowChildren[i].get(); - if (cell->ariaRoleAttribute() == RowHeaderRole) - return cell; + for (const auto& child : children()) { + if (child->ariaRoleAttribute() == RowHeaderRole) + return child.get(); } - return 0; + return nullptr; } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityARIAGridRow.h b/Source/WebCore/accessibility/AccessibilityARIAGridRow.h index 054fcf6ff..2d9ac292f 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. * @@ -33,24 +33,28 @@ namespace WebCore { -class AccessibilityARIAGridRow : public AccessibilityTableRow { +class AccessibilityTable; -private: - explicit AccessibilityARIAGridRow(RenderObject*); +class AccessibilityARIAGridRow final : public AccessibilityTableRow { public: - static PassRefPtr<AccessibilityARIAGridRow> create(RenderObject*); + static Ref<AccessibilityARIAGridRow> create(RenderObject*); virtual ~AccessibilityARIAGridRow(); void disclosedRows(AccessibilityChildrenVector&); AccessibilityObject* disclosedByRow() const; - virtual AccessibilityObject* headerObject(); + virtual AccessibilityObject* headerObject() override; private: - virtual bool isARIATreeGridRow() const; - virtual AccessibilityObject* parentTable() const; -}; - + explicit AccessibilityARIAGridRow(RenderObject*); + + virtual bool isARIATreeGridRow() const override; + virtual AccessibilityTable* parentTable() const override; + virtual AccessibilityObject* parentObjectUnignored() const override; +}; + } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityARIAGridRow, isARIATreeGridRow()) + #endif // AccessibilityARIAGridRow_h diff --git a/Source/WebCore/accessibility/AccessibilityAllInOne.cpp b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp index 00740fa43..06afd8b22 100644 --- a/Source/WebCore/accessibility/AccessibilityAllInOne.cpp +++ b/Source/WebCore/accessibility/AccessibilityAllInOne.cpp @@ -34,6 +34,10 @@ #include "AccessibilityListBox.cpp" #include "AccessibilityListBoxOption.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" @@ -42,9 +46,11 @@ #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/AccessibilityImageMapLink.cpp b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp index 132f16b0b..96bc1392b 100644 --- a/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp +++ b/Source/WebCore/accessibility/AccessibilityImageMapLink.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -40,8 +40,8 @@ namespace WebCore { using namespace HTMLNames; AccessibilityImageMapLink::AccessibilityImageMapLink() - : m_areaElement(0) - , m_mapElement(0) + : m_areaElement(nullptr) + , m_mapElement(nullptr) { } @@ -49,9 +49,9 @@ AccessibilityImageMapLink::~AccessibilityImageMapLink() { } -PassRefPtr<AccessibilityImageMapLink> AccessibilityImageMapLink::create() +Ref<AccessibilityImageMapLink> AccessibilityImageMapLink::create() { - return adoptRef(new AccessibilityImageMapLink()); + return adoptRef(*new AccessibilityImageMapLink()); } AccessibilityObject* AccessibilityImageMapLink::parentObject() const @@ -60,9 +60,9 @@ 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()); + return m_mapElement->document().axObjectCache()->getOrCreate(m_mapElement->renderer()); } AccessibilityRole AccessibilityImageMapLink::roleValue() const @@ -87,10 +87,10 @@ Element* AccessibilityImageMapLink::anchorElement() const return m_areaElement.get(); } -KURL AccessibilityImageMapLink::url() const +URL AccessibilityImageMapLink::url() const { if (!m_areaElement.get()) - return KURL(); + return URL(); return m_areaElement->href(); } @@ -134,23 +134,30 @@ String AccessibilityImageMapLink::title() const return String(); } -RenderObject* AccessibilityImageMapLink::imageMapLinkRenderer() const +RenderElement* AccessibilityImageMapLink::imageMapLinkRenderer() const { - if (!m_mapElement.get() || !m_areaElement.get()) - return 0; + if (!m_mapElement || !m_areaElement) + return nullptr; - RenderObject* renderer = 0; - if (m_parent && m_parent->isAccessibilityRenderObject()) - renderer = static_cast<AccessibilityRenderObject*>(m_parent)->renderer(); + RenderElement* renderer = nullptr; + if (is<AccessibilityRenderObject>(m_parent)) + renderer = downcast<RenderElement>(downcast<AccessibilityRenderObject>(*m_parent).renderer()); else renderer = m_mapElement->renderer(); return renderer; } - + +void AccessibilityImageMapLink::detachFromParent() +{ + AccessibilityMockObject::detachFromParent(); + m_areaElement = nullptr; + m_mapElement = nullptr; +} + Path AccessibilityImageMapLink::elementPath() const { - RenderObject* renderer = imageMapLinkRenderer(); + auto renderer = imageMapLinkRenderer(); if (!renderer) return Path(); @@ -159,7 +166,7 @@ Path AccessibilityImageMapLink::elementPath() const LayoutRect AccessibilityImageMapLink::elementRect() const { - RenderObject* renderer = imageMapLinkRenderer(); + auto renderer = imageMapLinkRenderer(); if (!renderer) return LayoutRect(); diff --git a/Source/WebCore/accessibility/AccessibilityImageMapLink.h b/Source/WebCore/accessibility/AccessibilityImageMapLink.h index 40fb92678..69a7e20a5 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. * @@ -35,12 +35,9 @@ namespace WebCore { -class AccessibilityImageMapLink : public AccessibilityMockObject { - -private: - AccessibilityImageMapLink(); +class AccessibilityImageMapLink final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityImageMapLink> create(); + static Ref<AccessibilityImageMapLink> create(); virtual ~AccessibilityImageMapLink(); void setHTMLAreaElement(HTMLAreaElement* element) { m_areaElement = element; } @@ -49,36 +46,41 @@ public: void setHTMLMapElement(HTMLMapElement* element) { m_mapElement = element; } HTMLMapElement* mapElement() const { return m_mapElement.get(); } - virtual Node* node() const { return m_areaElement.get(); } + virtual Node* node() const override { return m_areaElement.get(); } - virtual AccessibilityRole roleValue() const; - virtual bool isEnabled() const { return true; } + virtual AccessibilityRole roleValue() const override; + virtual bool isEnabled() const override { return true; } - virtual Element* anchorElement() const; - virtual Element* actionElement() const; - virtual KURL url() const; - virtual bool isLink() const { return true; } - virtual bool isLinked() const { return true; } - virtual String title() const; - virtual String accessibilityDescription() const; - virtual AccessibilityObject* parentObject() const; + 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; - virtual String stringValueForMSAA() const; - virtual String nameForMSAA() const; + virtual String stringValueForMSAA() const override; + virtual String nameForMSAA() const override; - virtual LayoutRect elementRect() const; + virtual LayoutRect elementRect() const override; + +private: + AccessibilityImageMapLink(); + + virtual void detachFromParent() override; + virtual Path elementPath() const override; + RenderElement* imageMapLinkRenderer() const; + virtual void accessibilityText(Vector<AccessibilityText>&) override; + virtual bool isImageMapLink() const override { return true; } + virtual bool supportsPath() const override { return true; } -private: RefPtr<HTMLAreaElement> m_areaElement; RefPtr<HTMLMapElement> m_mapElement; - - virtual Path elementPath() const; - RenderObject* imageMapLinkRenderer() const; - virtual void accessibilityText(Vector<AccessibilityText>&); - virtual bool isImageMapLink() const { return true; } - virtual bool supportsPath() const { return true; } }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityImageMapLink, isImageMapLink()) + #endif // AccessibilityImageMapLink_h diff --git a/Source/WebCore/accessibility/AccessibilityList.cpp b/Source/WebCore/accessibility/AccessibilityList.cpp index 8039cf034..c78d36486 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,10 +30,12 @@ #include "AccessibilityList.h" #include "AXObjectCache.h" +#include "HTMLElement.h" #include "HTMLNames.h" +#include "PseudoElement.h" +#include "RenderListItem.h" #include "RenderObject.h" - -using namespace std; +#include "RenderStyle.h" namespace WebCore { @@ -48,9 +50,9 @@ AccessibilityList::~AccessibilityList() { } -PassRefPtr<AccessibilityList> AccessibilityList::create(RenderObject* renderer) +Ref<AccessibilityList> AccessibilityList::create(RenderObject* renderer) { - return adoptRef(new AccessibilityList(renderer)); + return adoptRef(*new AccessibilityList(renderer)); } bool AccessibilityList::computeAccessibilityIsIgnored() const @@ -93,8 +95,108 @@ bool AccessibilityList::isDescriptionList() const return false; Node* node = m_renderer->node(); - return node && node->hasTagName(dlTag); + return node && node->hasTagName(dlTag); +} + +bool AccessibilityList::childHasPseudoVisibleListItemMarkers(RenderObject* listItem) +{ + // Check if the list item has a pseudo-element that should be accessible (e.g. an image or text) + Element* listItemElement = downcast<Element>(listItem->node()); + if (!listItemElement || !listItemElement->beforePseudoElement()) + return false; + + AccessibilityObject* axObj = axObjectCache()->getOrCreate(listItemElement->beforePseudoElement()->renderer()); + if (!axObj) + return false; + + if (!axObj->accessibilityIsIgnored()) + return true; + + for (const auto& child : axObj->children()) { + if (!child->accessibilityIsIgnored()) + return true; + } + + // Platforms which expose rendered text content through the parent element will treat + // those renderers as "ignored" objects. +#if PLATFORM(GTK) || PLATFORM(EFL) + String text = axObj->textUnderElement(); + return !text.isEmpty() && !text.containsOnlyWhitespace(); +#else + return false; +#endif } +AccessibilityRole AccessibilityList::determineAccessibilityRole() +{ + m_ariaRole = determineAriaRoleAttribute(); + + // Directory is mapped to list for now, but does not adhere to the same heuristics. + if (ariaRoleAttribute() == DirectoryRole) + return ListRole; + + // Heuristic to determine if this list is being used for layout or for content. + // 1. If it's a named list, like ol or aria=list, then it's a list. + // 1a. Unless the list has no children, then it's not a list. + // 2. If it displays visible list markers, it's a list. + // 3. If it does not display list markers and has only one child, it's not a list. + // 4. If it does not have any listitem children, it's not a list. + // 5. Otherwise it's a list (for now). + + AccessibilityRole role = ListRole; + + // Temporarily set role so that we can query children (otherwise canHaveChildren returns false). + m_role = role; + + unsigned listItemCount = 0; + bool hasVisibleMarkers = false; + + const auto& children = this->children(); + // DescriptionLists are always semantically a description list, so do not apply heuristics. + if (isDescriptionList() && children.size()) + return DescriptionListRole; + + for (const auto& child : children) { + if (child->ariaRoleAttribute() == ListItemRole) + listItemCount++; + else if (child->roleValue() == ListItemRole) { + RenderObject* listItem = child->renderer(); + if (!listItem) + continue; + + // Rendered list items always count. + if (listItem->isListItem()) { + if (!hasVisibleMarkers && (listItem->style().listStyleType() != NoneListStyle || listItem->style().listStyleImage() || childHasPseudoVisibleListItemMarkers(listItem))) + hasVisibleMarkers = true; + listItemCount++; + } else if (listItem->node() && listItem->node()->hasTagName(liTag)) { + // Inline elements that are in a list with an explicit role should also count. + if (m_ariaRole == ListRole) + listItemCount++; + + if (childHasPseudoVisibleListItemMarkers(listItem)) { + hasVisibleMarkers = true; + listItemCount++; + } + } + } + } + + // Non <ul> lists and ARIA lists only need to have one child. + // <ul>, <ol> lists need to have visible markers. + if (ariaRoleAttribute() != UnknownRole) { + if (!listItemCount) + role = GroupRole; + } else if (!hasVisibleMarkers) + role = GroupRole; + + return role; +} + +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 c5916ae6e..10d4276f7 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. * @@ -33,30 +33,27 @@ namespace WebCore { -class AccessibilityList : public AccessibilityRenderObject { - -private: - explicit AccessibilityList(RenderObject*); +class AccessibilityList final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityList> create(RenderObject*); + static Ref<AccessibilityList> create(RenderObject*); virtual ~AccessibilityList(); - virtual bool isList() const { return true; } bool isUnorderedList() const; bool isOrderedList() const; bool isDescriptionList() const; - virtual AccessibilityRole roleValue() const { return ListRole; } + virtual AccessibilityRole roleValue() const override; + private: - virtual bool computeAccessibilityIsIgnored() const; + explicit AccessibilityList(RenderObject*); + virtual bool isList() const override { return true; } + virtual bool computeAccessibilityIsIgnored() const override; + virtual AccessibilityRole determineAccessibilityRole() override; + bool childHasPseudoVisibleListItemMarkers(RenderObject*); }; - -inline AccessibilityList* toAccessibilityList(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isList()); - return static_cast<AccessibilityList*>(object); -} - + } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityList, isList()) + #endif // AccessibilityList_h diff --git a/Source/WebCore/accessibility/AccessibilityListBox.cpp b/Source/WebCore/accessibility/AccessibilityListBox.cpp index 9c4970edb..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. * @@ -37,8 +37,6 @@ #include "RenderListBox.h" #include "RenderObject.h" -using namespace std; - namespace WebCore { using namespace HTMLNames; @@ -51,10 +49,10 @@ AccessibilityListBox::AccessibilityListBox(RenderObject* renderer) AccessibilityListBox::~AccessibilityListBox() { } - -PassRefPtr<AccessibilityListBox> AccessibilityListBox::create(RenderObject* renderer) + +Ref<AccessibilityListBox> AccessibilityListBox::create(RenderObject* renderer) { - return adoptRef(new AccessibilityListBox(renderer)); + return adoptRef(*new AccessibilityListBox(renderer)); } bool AccessibilityListBox::canSetSelectedChildrenAttribute() const @@ -63,7 +61,7 @@ bool AccessibilityListBox::canSetSelectedChildrenAttribute() const if (!selectNode) return false; - return !toHTMLSelectElement(selectNode)->isDisabledFormControl(); + return !downcast<HTMLSelectElement>(*selectNode).isDisabledFormControl(); } void AccessibilityListBox::addChildren() @@ -74,18 +72,16 @@ void AccessibilityListBox::addChildren() m_haveChildren = true; - const Vector<HTMLElement*>& listItems = toHTMLSelectElement(selectNode)->listItems(); - unsigned length = listItems.size(); - for (unsigned i = 0; i < length; i++) { + for (const auto& listItem : downcast<HTMLSelectElement>(*selectNode).listItems()) { // The cast to HTMLElement below is safe because the only other possible listItem type // would be a WMLElement, but WML builds don't use accessibility features at all. - AccessibilityObject* listOption = listBoxOptionAccessibilityObject(listItems[i]); + AccessibilityObject* listOption = listBoxOptionAccessibilityObject(listItem); if (listOption && !listOption->accessibilityIsIgnored()) m_children.append(listOption); } } -void AccessibilityListBox::setSelectedChildren(AccessibilityChildrenVector& children) +void AccessibilityListBox::setSelectedChildren(const AccessibilityChildrenVector& children) { if (!canSetSelectedChildrenAttribute()) return; @@ -95,20 +91,17 @@ void AccessibilityListBox::setSelectedChildren(AccessibilityChildrenVector& chil return; // disable any selected options - unsigned length = m_children.size(); - for (unsigned i = 0; i < length; i++) { - AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(m_children[i].get()); - if (listBoxOption->isSelected()) - listBoxOption->setSelected(false); + for (const auto& child : m_children) { + auto& listBoxOption = downcast<AccessibilityListBoxOption>(*child); + if (listBoxOption.isSelected()) + listBoxOption.setSelected(false); } - length = children.size(); - for (unsigned i = 0; i < length; i++) { - AccessibilityObject* obj = children[i].get(); + for (const auto& obj : children) { if (obj->roleValue() != ListBoxOptionRole) continue; - static_cast<AccessibilityListBoxOption*>(obj)->setSelected(true); + downcast<AccessibilityListBoxOption>(*obj).setSelected(true); } } @@ -119,10 +112,9 @@ void AccessibilityListBox::selectedChildren(AccessibilityChildrenVector& result) if (!hasChildren()) addChildren(); - unsigned length = m_children.size(); - for (unsigned i = 0; i < length; i++) { - if (static_cast<AccessibilityListBoxOption*>(m_children[i].get())->isSelected()) - result.append(m_children[i]); + for (const auto& child : m_children) { + if (downcast<AccessibilityListBoxOption>(*child).isSelected()) + result.append(child.get()); } } @@ -135,7 +127,7 @@ void AccessibilityListBox::visibleChildren(AccessibilityChildrenVector& result) unsigned length = m_children.size(); for (unsigned i = 0; i < length; i++) { - if (toRenderListBox(m_renderer)->listIndexIsVisible(i)) + if (downcast<RenderListBox>(*m_renderer).listIndexIsVisible(i)) result.append(m_children[i]); } } @@ -144,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); - static_cast<AccessibilityListBoxOption*>(listBoxObject)->setHTMLElement(element); + AccessibilityObject& listBoxObject = *m_renderer->document().axObjectCache()->getOrCreate(ListBoxOptionRole); + downcast<AccessibilityListBoxOption>(listBoxObject).setHTMLElement(element); - return listBoxObject; + return &listBoxObject; } AccessibilityObject* AccessibilityListBox::elementAccessibilityHitTest(const IntPoint& point) const @@ -157,18 +149,18 @@ AccessibilityObject* AccessibilityListBox::elementAccessibilityHitTest(const Int // the internal HTMLSelectElement methods for returning a listbox option at a point // ignore optgroup elements. if (!m_renderer) - return 0; + return nullptr; Node* node = m_renderer->node(); if (!node) - return 0; + return nullptr; LayoutRect parentRect = boundingBoxRect(); - AccessibilityObject* listBoxOption = 0; + AccessibilityObject* listBoxOption = nullptr; unsigned length = m_children.size(); - for (unsigned i = 0; i < length; i++) { - LayoutRect rect = toRenderListBox(m_renderer)->itemBoundingBoxRect(parentRect.location(), i); + for (unsigned i = 0; i < length; ++i) { + LayoutRect rect = downcast<RenderListBox>(*m_renderer).itemBoundingBoxRect(parentRect.location(), i); // The cast to HTMLElement below is safe because the only other possible listItem type // would be a WMLElement, but WML builds don't use accessibility features at all. if (rect.contains(point)) { diff --git a/Source/WebCore/accessibility/AccessibilityListBox.h b/Source/WebCore/accessibility/AccessibilityListBox.h index dda37e8d5..ad2df1da4 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. * @@ -33,30 +33,30 @@ namespace WebCore { -class AccessibilityListBox : public AccessibilityRenderObject { - -private: - explicit AccessibilityListBox(RenderObject*); +class AccessibilityListBox final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityListBox> create(RenderObject*); + static Ref<AccessibilityListBox> create(RenderObject*); virtual ~AccessibilityListBox(); - virtual bool isListBox() const { return true; } - - virtual bool canSetSelectedChildrenAttribute() const; - void setSelectedChildren(AccessibilityChildrenVector&); - virtual AccessibilityRole roleValue() const { return ListBoxRole; } + virtual bool canSetSelectedChildrenAttribute() const override; + void setSelectedChildren(const AccessibilityChildrenVector&); + virtual AccessibilityRole roleValue() const override { return ListBoxRole; } - virtual void selectedChildren(AccessibilityChildrenVector&); - virtual void visibleChildren(AccessibilityChildrenVector&); + virtual void selectedChildren(AccessibilityChildrenVector&) override; + virtual void visibleChildren(AccessibilityChildrenVector&) override; - virtual void addChildren(); + virtual void addChildren() override; -private: +private: + explicit AccessibilityListBox(RenderObject*); + + virtual bool isListBox() const override { return true; } AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityListBox, isListBox()) + #endif // AccessibilityListBox_h diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp b/Source/WebCore/accessibility/AccessibilityListBoxOption.cpp index 8895f1015..9cb5a74ab 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. * @@ -41,14 +41,12 @@ #include "RenderListBox.h" #include "RenderObject.h" -using namespace std; - namespace WebCore { using namespace HTMLNames; AccessibilityListBoxOption::AccessibilityListBoxOption() - : m_optionElement(0) + : m_optionElement(nullptr) { } @@ -56,23 +54,20 @@ AccessibilityListBoxOption::~AccessibilityListBoxOption() { } -PassRefPtr<AccessibilityListBoxOption> AccessibilityListBoxOption::create() +Ref<AccessibilityListBoxOption> AccessibilityListBoxOption::create() { - return adoptRef(new AccessibilityListBoxOption()); + return adoptRef(*new AccessibilityListBoxOption()); } bool AccessibilityListBoxOption::isEnabled() const { - if (!m_optionElement) - return false; - - if (isHTMLOptGroupElement(m_optionElement)) + if (is<HTMLOptGroupElement>(m_optionElement)) return false; - if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_disabledAttr), "true")) return false; - if (m_optionElement->hasAttribute(disabledAttr)) + if (m_optionElement->fastHasAttribute(disabledAttr)) return false; return true; @@ -80,13 +75,10 @@ bool AccessibilityListBoxOption::isEnabled() const bool AccessibilityListBoxOption::isSelected() const { - if (!m_optionElement) + if (!is<HTMLOptionElement>(m_optionElement)) return false; - if (!isHTMLOptionElement(m_optionElement)) - return false; - - return toHTMLOptionElement(m_optionElement)->selected(); + return downcast<HTMLOptionElement>(*m_optionElement).selected(); } bool AccessibilityListBoxOption::isSelectedOptionActive() const @@ -108,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(); + LayoutRect parentRect = listBoxRenderer->document().axObjectCache()->getOrCreate(listBoxRenderer)->boundingBoxRect(); int index = listBoxOptionIndex(); if (index != -1) - rect = toRenderListBox(listBoxRenderer)->itemBoundingBoxRect(parentRect.location(), index); + rect = downcast<RenderListBox>(*listBoxRenderer).itemBoundingBoxRect(parentRect.location(), index); return rect; } @@ -133,10 +125,7 @@ bool AccessibilityListBoxOption::computeAccessibilityIsIgnored() const bool AccessibilityListBoxOption::canSetSelectedAttribute() const { - if (!m_optionElement) - return false; - - if (!isHTMLOptionElement(m_optionElement)) + if (!is<HTMLOptionElement>(m_optionElement)) return false; if (m_optionElement->isDisabledFormControl()) @@ -158,11 +147,11 @@ String AccessibilityListBoxOption::stringValue() const if (!ariaLabel.isNull()) return ariaLabel; - if (isHTMLOptionElement(m_optionElement)) - return toHTMLOptionElement(m_optionElement)->text(); + if (is<HTMLOptionElement>(*m_optionElement)) + return downcast<HTMLOptionElement>(*m_optionElement).label(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->groupLabelText(); + if (is<HTMLOptGroupElement>(*m_optionElement)) + return downcast<HTMLOptGroupElement>(*m_optionElement).groupLabelText(); return String(); } @@ -176,9 +165,9 @@ AccessibilityObject* AccessibilityListBoxOption::parentObject() const { HTMLSelectElement* parentNode = listBoxOptionParentNode(); if (!parentNode) - return 0; + return nullptr; - return m_optionElement->document()->axObjectCache()->getOrCreate(parentNode); + return m_optionElement->document().axObjectCache()->getOrCreate(parentNode); } void AccessibilityListBoxOption::setSelected(bool selected) @@ -202,15 +191,15 @@ void AccessibilityListBoxOption::setSelected(bool selected) HTMLSelectElement* AccessibilityListBoxOption::listBoxOptionParentNode() const { if (!m_optionElement) - return 0; + return nullptr; - if (isHTMLOptionElement(m_optionElement)) - return toHTMLOptionElement(m_optionElement)->ownerSelectElement(); + if (is<HTMLOptionElement>(*m_optionElement)) + return downcast<HTMLOptionElement>(*m_optionElement).ownerSelectElement(); - if (isHTMLOptGroupElement(m_optionElement)) - return toHTMLOptGroupElement(m_optionElement)->ownerSelectElement(); + if (is<HTMLOptGroupElement>(*m_optionElement)) + return downcast<HTMLOptGroupElement>(*m_optionElement).ownerSelectElement(); - return 0; + return nullptr; } int AccessibilityListBoxOption::listBoxOptionIndex() const @@ -222,7 +211,7 @@ int AccessibilityListBoxOption::listBoxOptionIndex() const if (!selectElement) return -1; - const Vector<HTMLElement*>& listItems = selectElement->listItems(); + const auto& listItems = selectElement->listItems(); unsigned length = listItems.size(); for (unsigned i = 0; i < length; i++) if (listItems[i] == m_optionElement) diff --git a/Source/WebCore/accessibility/AccessibilityListBoxOption.h b/Source/WebCore/accessibility/AccessibilityListBoxOption.h index 114445c31..31c433670 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. * @@ -40,41 +40,42 @@ class Element; class HTMLElement; class HTMLSelectElement; -class AccessibilityListBoxOption : public AccessibilityObject { - -private: - AccessibilityListBoxOption(); +class AccessibilityListBoxOption final : public AccessibilityObject { public: - static PassRefPtr<AccessibilityListBoxOption> create(); + static Ref<AccessibilityListBoxOption> create(); virtual ~AccessibilityListBoxOption(); void setHTMLElement(HTMLElement* element) { m_optionElement = element; } - virtual AccessibilityRole roleValue() const { return ListBoxOptionRole; } - virtual bool isSelected() const; - virtual bool isEnabled() const; - virtual bool isSelectedOptionActive() const; - virtual String stringValue() const; - virtual Element* actionElement() const; - virtual Node* node() const { return m_optionElement; } - virtual void setSelected(bool); - virtual bool canSetSelectedAttribute() const; + 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; + + virtual LayoutRect elementRect() const override; + virtual AccessibilityObject* parentObject() const override; - virtual LayoutRect elementRect() const; - virtual AccessibilityObject* parentObject() const; - bool isListBoxOption() const { return true; } - private: - HTMLElement* m_optionElement; - - virtual bool canHaveChildren() const { return false; } + AccessibilityListBoxOption(); + + virtual bool isListBoxOption() const override { return true; } + virtual bool canHaveChildren() const override { return false; } HTMLSelectElement* listBoxOptionParentNode() const; int listBoxOptionIndex() const; IntRect listBoxOptionRect() const; AccessibilityObject* listBoxOptionAccessibilityObject(HTMLElement*) const; - virtual bool computeAccessibilityIsIgnored() const; + virtual bool computeAccessibilityIsIgnored() const override; + + HTMLElement* m_optionElement; }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityListBoxOption, isListBoxOption()) + #endif // AccessibilityListBoxOption_h diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.cpp b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp index 3a464ae0a..a59d1682b 100644 --- a/Source/WebCore/accessibility/AccessibilityMediaControls.cpp +++ b/Source/WebCore/accessibility/AccessibilityMediaControls.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -41,6 +41,7 @@ #include "MediaControlElements.h" #include "RenderObject.h" #include "RenderSlider.h" +#include <wtf/NeverDestroyed.h> namespace WebCore { @@ -52,7 +53,7 @@ AccessibilityMediaControl::AccessibilityMediaControl(RenderObject* renderer) { } -PassRefPtr<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* renderer) { ASSERT(renderer->node()); @@ -68,7 +69,7 @@ PassRefPtr<AccessibilityObject> AccessibilityMediaControl::create(RenderObject* return AccessibilityMediaControlsContainer::create(renderer); default: - return adoptRef(new AccessibilityMediaControl(renderer)); + return adoptRef(*new AccessibilityMediaControl(renderer)); } } @@ -80,23 +81,23 @@ MediaControlElementType AccessibilityMediaControl::controlType() const return mediaControlElementType(renderer()->node()); } -String AccessibilityMediaControl::controlTypeName() const +const String& AccessibilityMediaControl::controlTypeName() const { - DEFINE_STATIC_LOCAL(const String, mediaEnterFullscreenButtonName, (ASCIILiteral("EnterFullscreenButton"))); - DEFINE_STATIC_LOCAL(const String, mediaExitFullscreenButtonName, (ASCIILiteral("ExitFullscreenButton"))); - DEFINE_STATIC_LOCAL(const String, mediaMuteButtonName, (ASCIILiteral("MuteButton"))); - DEFINE_STATIC_LOCAL(const String, mediaPlayButtonName, (ASCIILiteral("PlayButton"))); - DEFINE_STATIC_LOCAL(const String, mediaSeekBackButtonName, (ASCIILiteral("SeekBackButton"))); - DEFINE_STATIC_LOCAL(const String, mediaSeekForwardButtonName, (ASCIILiteral("SeekForwardButton"))); - DEFINE_STATIC_LOCAL(const String, mediaRewindButtonName, (ASCIILiteral("RewindButton"))); - DEFINE_STATIC_LOCAL(const String, mediaReturnToRealtimeButtonName, (ASCIILiteral("ReturnToRealtimeButton"))); - DEFINE_STATIC_LOCAL(const String, mediaUnMuteButtonName, (ASCIILiteral("UnMuteButton"))); - DEFINE_STATIC_LOCAL(const String, mediaPauseButtonName, (ASCIILiteral("PauseButton"))); - DEFINE_STATIC_LOCAL(const String, mediaStatusDisplayName, (ASCIILiteral("StatusDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaCurrentTimeDisplay, (ASCIILiteral("CurrentTimeDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaTimeRemainingDisplay, (ASCIILiteral("TimeRemainingDisplay"))); - DEFINE_STATIC_LOCAL(const String, mediaShowClosedCaptionsButtonName, (ASCIILiteral("ShowClosedCaptionsButton"))); - DEFINE_STATIC_LOCAL(const String, mediaHideClosedCaptionsButtonName, (ASCIILiteral("HideClosedCaptionsButton"))); + static NeverDestroyed<const String> mediaEnterFullscreenButtonName(ASCIILiteral("EnterFullscreenButton")); + static NeverDestroyed<const String> mediaExitFullscreenButtonName(ASCIILiteral("ExitFullscreenButton")); + static NeverDestroyed<const String> mediaMuteButtonName(ASCIILiteral("MuteButton")); + static NeverDestroyed<const String> mediaPlayButtonName(ASCIILiteral("PlayButton")); + static NeverDestroyed<const String> mediaSeekBackButtonName(ASCIILiteral("SeekBackButton")); + static NeverDestroyed<const String> mediaSeekForwardButtonName(ASCIILiteral("SeekForwardButton")); + static NeverDestroyed<const String> mediaRewindButtonName(ASCIILiteral("RewindButton")); + static NeverDestroyed<const String> mediaReturnToRealtimeButtonName(ASCIILiteral("ReturnToRealtimeButton")); + static NeverDestroyed<const String> mediaUnMuteButtonName(ASCIILiteral("UnMuteButton")); + static NeverDestroyed<const String> mediaPauseButtonName(ASCIILiteral("PauseButton")); + static NeverDestroyed<const String> mediaStatusDisplayName(ASCIILiteral("StatusDisplay")); + static NeverDestroyed<const String> mediaCurrentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed<const String> mediaTimeRemainingDisplay(ASCIILiteral("TimeRemainingDisplay")); + static NeverDestroyed<const String> mediaShowClosedCaptionsButtonName(ASCIILiteral("ShowClosedCaptionsButton")); + static NeverDestroyed<const String> mediaHideClosedCaptionsButtonName(ASCIILiteral("HideClosedCaptionsButton")); switch (controlType()) { case MediaEnterFullscreenButton: @@ -134,7 +135,7 @@ String AccessibilityMediaControl::controlTypeName() const break; } - return String(); + return nullAtom; } void AccessibilityMediaControl::accessibilityText(Vector<AccessibilityText>& textOrder) @@ -155,7 +156,7 @@ void AccessibilityMediaControl::accessibilityText(Vector<AccessibilityText>& tex String AccessibilityMediaControl::title() const { - DEFINE_STATIC_LOCAL(const String, controlsPanel, (ASCIILiteral("ControlsPanel"))); + static NeverDestroyed<const String> controlsPanel(ASCIILiteral("ControlsPanel")); if (controlType() == MediaControlsPanel) return localizedMediaControlElementString(controlsPanel); @@ -175,7 +176,7 @@ String AccessibilityMediaControl::helpText() const bool AccessibilityMediaControl::computeAccessibilityIsIgnored() const { - if (!m_renderer || !m_renderer->style() || m_renderer->style()->visibility() != VISIBLE || controlType() == MediaTimelineContainer) + if (!m_renderer || m_renderer->style().visibility() != VISIBLE || controlType() == MediaTimelineContainer) return true; return accessibilityIsIgnoredByDefault(); @@ -221,9 +222,9 @@ AccessibilityMediaControlsContainer::AccessibilityMediaControlsContainer(RenderO { } -PassRefPtr<AccessibilityObject> AccessibilityMediaControlsContainer::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaControlsContainer::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaControlsContainer(renderer)); + return adoptRef(*new AccessibilityMediaControlsContainer(renderer)); } String AccessibilityMediaControlsContainer::accessibilityDescription() const @@ -238,18 +239,14 @@ String AccessibilityMediaControlsContainer::helpText() const bool AccessibilityMediaControlsContainer::controllingVideoElement() const { - if (!m_renderer->node()) - return true; - - MediaControlTimeDisplayElement* element = static_cast<MediaControlTimeDisplayElement*>(m_renderer->node()); - - return toParentMediaElement(element)->isVideo(); + auto element = parentMediaElement(*m_renderer); + return !element || element->isVideo(); } -const String AccessibilityMediaControlsContainer::elementTypeName() const +const String& AccessibilityMediaControlsContainer::elementTypeName() const { - DEFINE_STATIC_LOCAL(const String, videoElement, (ASCIILiteral("VideoElement"))); - DEFINE_STATIC_LOCAL(const String, audioElement, (ASCIILiteral("AudioElement"))); + static NeverDestroyed<const String> videoElement(ASCIILiteral("VideoElement")); + static NeverDestroyed<const String> audioElement(ASCIILiteral("AudioElement")); if (controllingVideoElement()) return videoElement; @@ -269,24 +266,24 @@ AccessibilityMediaTimeline::AccessibilityMediaTimeline(RenderObject* renderer) { } -PassRefPtr<AccessibilityObject> AccessibilityMediaTimeline::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaTimeline::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaTimeline(renderer)); + return adoptRef(*new AccessibilityMediaTimeline(renderer)); } String AccessibilityMediaTimeline::valueDescription() const { Node* node = m_renderer->node(); - if (!isHTMLInputElement(node)) + if (!is<HTMLInputElement>(*node)) return String(); - float time = toHTMLInputElement(node)->value().toFloat(); + float time = downcast<HTMLInputElement>(*node).value().toFloat(); return localizedMediaTimeDescription(time); } String AccessibilityMediaTimeline::helpText() const { - DEFINE_STATIC_LOCAL(const String, slider, (ASCIILiteral("Slider"))); + static NeverDestroyed<const String> slider(ASCIILiteral("Slider")); return localizedMediaControlElementHelpText(slider); } @@ -299,17 +296,17 @@ AccessibilityMediaTimeDisplay::AccessibilityMediaTimeDisplay(RenderObject* rende { } -PassRefPtr<AccessibilityObject> AccessibilityMediaTimeDisplay::create(RenderObject* renderer) +Ref<AccessibilityObject> AccessibilityMediaTimeDisplay::create(RenderObject* renderer) { - return adoptRef(new AccessibilityMediaTimeDisplay(renderer)); + return adoptRef(*new AccessibilityMediaTimeDisplay(renderer)); } bool AccessibilityMediaTimeDisplay::computeAccessibilityIsIgnored() const { - if (!m_renderer || !m_renderer->style() || m_renderer->style()->visibility() != VISIBLE) + if (!m_renderer || m_renderer->style().visibility() != VISIBLE) return true; - if (!m_renderer->style()->width().value()) + if (!m_renderer->style().width().value()) return true; return accessibilityIsIgnoredByDefault(); @@ -317,8 +314,8 @@ bool AccessibilityMediaTimeDisplay::computeAccessibilityIsIgnored() const String AccessibilityMediaTimeDisplay::accessibilityDescription() const { - DEFINE_STATIC_LOCAL(const String, currentTimeDisplay, (ASCIILiteral("CurrentTimeDisplay"))); - DEFINE_STATIC_LOCAL(const String, timeRemainingDisplay, (ASCIILiteral("TimeRemainingDisplay"))); + static NeverDestroyed<const String> currentTimeDisplay(ASCIILiteral("CurrentTimeDisplay")); + static NeverDestroyed<const String> timeRemainingDisplay(ASCIILiteral("TimeRemainingDisplay")); if (controlType() == MediaCurrentTimeDisplay) return localizedMediaControlElementString(currentTimeDisplay); diff --git a/Source/WebCore/accessibility/AccessibilityMediaControls.h b/Source/WebCore/accessibility/AccessibilityMediaControls.h index 337ec5c72..a0ce71792 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. * @@ -40,75 +40,77 @@ namespace WebCore { class AccessibilityMediaControl : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaControl() { } - virtual AccessibilityRole roleValue() const; + virtual AccessibilityRole roleValue() const override; - virtual String title() const; - virtual String accessibilityDescription() const; - virtual String helpText() const; + virtual String title() const override; + virtual String accessibilityDescription() const override; + virtual String helpText() const override; protected: explicit AccessibilityMediaControl(RenderObject*); MediaControlElementType controlType() const; - String controlTypeName() const; - virtual void accessibilityText(Vector<AccessibilityText>&); - virtual bool computeAccessibilityIsIgnored() const; + const String& controlTypeName() const; + virtual bool computeAccessibilityIsIgnored() const override; + +private: + virtual void accessibilityText(Vector<AccessibilityText>&) override; }; -class AccessibilityMediaTimeline : public AccessibilitySlider { +class AccessibilityMediaTimeline final : public AccessibilitySlider { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaTimeline() { } - virtual bool isMediaTimeline() const { return true; } - - virtual String helpText() const; - virtual String valueDescription() const; + virtual String helpText() const override; + virtual String valueDescription() const override; const AtomicString& getAttribute(const QualifiedName& attribute) const; private: explicit AccessibilityMediaTimeline(RenderObject*); + + virtual bool isMediaTimeline() const override { return true; } }; -class AccessibilityMediaControlsContainer : public AccessibilityMediaControl { +class AccessibilityMediaControlsContainer final : public AccessibilityMediaControl { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaControlsContainer() { } - virtual AccessibilityRole roleValue() const { return ToolbarRole; } + virtual AccessibilityRole roleValue() const override { return ToolbarRole; } - virtual String helpText() const; - virtual String accessibilityDescription() const; + virtual String helpText() const override; + virtual String accessibilityDescription() const override; private: explicit AccessibilityMediaControlsContainer(RenderObject*); bool controllingVideoElement() const; - const String elementTypeName() const; - virtual bool computeAccessibilityIsIgnored() const; + const String& elementTypeName() const; + virtual bool computeAccessibilityIsIgnored() const override; }; -class AccessibilityMediaTimeDisplay : public AccessibilityMediaControl { +class AccessibilityMediaTimeDisplay final : public AccessibilityMediaControl { public: - static PassRefPtr<AccessibilityObject> create(RenderObject*); + static Ref<AccessibilityObject> create(RenderObject*); virtual ~AccessibilityMediaTimeDisplay() { } - virtual AccessibilityRole roleValue() const { return ApplicationTimerRole; } + virtual AccessibilityRole roleValue() const override { return ApplicationTimerRole; } - virtual String stringValue() const; - virtual String accessibilityDescription() const; + virtual String stringValue() const override; + virtual String accessibilityDescription() const override; private: explicit AccessibilityMediaTimeDisplay(RenderObject*); - virtual bool isMediaControlLabel() const { return true; } - virtual bool computeAccessibilityIsIgnored() const; + virtual bool isMediaControlLabel() const override { return true; } + virtual bool computeAccessibilityIsIgnored() const override; }; diff --git a/Source/WebCore/accessibility/AccessibilityMenuList.cpp b/Source/WebCore/accessibility/AccessibilityMenuList.cpp index 994a33468..c0e5cf00e 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuList.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuList.cpp @@ -37,37 +37,45 @@ AccessibilityMenuList::AccessibilityMenuList(RenderMenuList* renderer) { } -PassRefPtr<AccessibilityMenuList> AccessibilityMenuList::create(RenderMenuList* renderer) +Ref<AccessibilityMenuList> AccessibilityMenuList::create(RenderMenuList* renderer) { - return adoptRef(new AccessibilityMenuList(renderer)); + return adoptRef(*new AccessibilityMenuList(renderer)); } -bool AccessibilityMenuList::press() const +bool AccessibilityMenuList::press() { +#if !PLATFORM(IOS) RenderMenuList* menuList = static_cast<RenderMenuList*>(m_renderer); if (menuList->popupIsVisible()) menuList->hidePopup(); else menuList->showPopup(); return true; +#else + return false; +#endif } 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; - static_cast<AccessibilityMockObject*>(list)->setParent(this); + downcast<AccessibilityMockObject>(*list).setParent(this); if (list->accessibilityIsIgnored()) { cache->remove(list->axObjectID()); return; } + m_haveChildren = true; m_children.append(list); list->addChildren(); @@ -84,7 +92,11 @@ void AccessibilityMenuList::childrenChanged() bool AccessibilityMenuList::isCollapsed() const { +#if !PLATFORM(IOS) return !static_cast<RenderMenuList*>(m_renderer)->popupIsVisible(); +#else + return true; +#endif } bool AccessibilityMenuList::canSetFocusAttribute() const @@ -92,26 +104,32 @@ bool AccessibilityMenuList::canSetFocusAttribute() const if (!node()) return false; - return !toElement(node())->isDisabledFormControl(); + return !downcast<Element>(*node()).isDisabledFormControl(); } void AccessibilityMenuList::didUpdateActiveOption(int optionIndex) { - RefPtr<Document> document = m_renderer->document(); + Ref<Document> document(m_renderer->document()); AXObjectCache* cache = document->axObjectCache(); - const AccessibilityChildrenVector& childObjects = children(); + const auto& childObjects = children(); if (!childObjects.isEmpty()) { ASSERT(childObjects.size() == 1); - ASSERT(childObjects[0]->isMenuListPopup()); - - if (childObjects[0]->isMenuListPopup()) { - if (AccessibilityMenuListPopup* popup = static_cast<AccessibilityMenuListPopup*>(childObjects[0].get())) - popup->didUpdateActiveOption(optionIndex); - } + ASSERT(is<AccessibilityMenuListPopup>(*childObjects[0])); + + // We might be calling this method in situations where the renderers for list items + // associated to the menu list have not been created (e.g. they might be rendered + // in the UI process, as it's the case in the GTK+ port, which uses GtkMenuItem). + // So, we need to make sure that the accessibility popup object has some children + // before asking it to update its active option, or it will read invalid memory. + // You can reproduce the issue in the GTK+ port by removing this check and running + // accessibility/insert-selected-option-into-select-causes-crash.html (will crash). + int popupChildrenSize = static_cast<int>(childObjects[0]->children().size()); + if (is<AccessibilityMenuListPopup>(*childObjects[0]) && optionIndex >= 0 && optionIndex < popupChildrenSize) + downcast<AccessibilityMenuListPopup>(*childObjects[0]).didUpdateActiveOption(optionIndex); } - cache->postNotification(this, document.get(), AXObjectCache::AXMenuListValueChanged, true, 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 95f7972b3..c081a72f7 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuList.h +++ b/Source/WebCore/accessibility/AccessibilityMenuList.h @@ -35,32 +35,28 @@ class AccessibilityMenuListPopup; class HTMLOptionElement; class RenderMenuList; -class AccessibilityMenuList : public AccessibilityRenderObject { +class AccessibilityMenuList final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilityMenuList> create(RenderMenuList* renderer); + static Ref<AccessibilityMenuList> create(RenderMenuList* renderer); - virtual bool isCollapsed() const; - virtual bool press() const; + virtual bool isCollapsed() const override; + virtual bool press() override; void didUpdateActiveOption(int optionIndex); private: explicit AccessibilityMenuList(RenderMenuList*); - virtual bool isMenuList() const { return true; } - virtual AccessibilityRole roleValue() const { return PopUpButtonRole; } - virtual bool canSetFocusAttribute() const; + virtual bool isMenuList() const override { return true; } + virtual AccessibilityRole roleValue() const override { return PopUpButtonRole; } + virtual bool canSetFocusAttribute() const override; - virtual void addChildren(); - virtual void childrenChanged(); + virtual void addChildren() override; + virtual void childrenChanged() override; }; -inline AccessibilityMenuList* toAccessibilityMenuList(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isMenuList()); - return static_cast<AccessibilityMenuList*>(object); -} - } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuList, isMenuList()) + #endif // AccessibilityMenuList_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp index 88e2e6ee1..ddb26464a 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.cpp @@ -41,7 +41,7 @@ AccessibilityMenuListOption::AccessibilityMenuListOption() void AccessibilityMenuListOption::setElement(HTMLElement* element) { - ASSERT_ARG(element, isHTMLOptionElement(element)); + ASSERT_ARG(element, is<HTMLOptionElement>(element)); m_element = element; } @@ -54,7 +54,7 @@ bool AccessibilityMenuListOption::isEnabled() const { // isDisabledFormControl() returns true if the parent <select> element is disabled, // which we don't want. - return !toHTMLOptionElement(m_element.get())->ownElementDisabled(); + return !downcast<HTMLOptionElement>(*m_element).ownElementDisabled(); } bool AccessibilityMenuListOption::isVisible() const @@ -75,15 +75,15 @@ bool AccessibilityMenuListOption::isOffScreen() const bool AccessibilityMenuListOption::isSelected() const { - return toHTMLOptionElement(m_element.get())->selected(); + return downcast<HTMLOptionElement>(*m_element).selected(); } -void AccessibilityMenuListOption::setSelected(bool b) +void AccessibilityMenuListOption::setSelected(bool selected) { if (!canSetSelectedAttribute()) return; - toHTMLOptionElement(m_element.get())->setSelected(b); + downcast<HTMLOptionElement>(*m_element).setSelected(selected); } String AccessibilityMenuListOption::nameForMSAA() const @@ -104,9 +104,13 @@ bool AccessibilityMenuListOption::computeAccessibilityIsIgnored() const LayoutRect AccessibilityMenuListOption::elementRect() const { AccessibilityObject* parent = parentObject(); + if (!parent) + return boundingBoxRect(); ASSERT(parent->isMenuListPopup()); AccessibilityObject* grandparent = parent->parentObject(); + if (!grandparent) + return boundingBoxRect(); ASSERT(grandparent->isMenuList()); return grandparent->elementRect(); @@ -114,7 +118,7 @@ LayoutRect AccessibilityMenuListOption::elementRect() const String AccessibilityMenuListOption::stringValue() const { - return toHTMLOptionElement(m_element.get())->text(); + return downcast<HTMLOptionElement>(*m_element).label(); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuListOption.h b/Source/WebCore/accessibility/AccessibilityMenuListOption.h index c340b7e4f..b68150355 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListOption.h +++ b/Source/WebCore/accessibility/AccessibilityMenuListOption.h @@ -33,35 +33,37 @@ namespace WebCore { class AccessibilityMenuListPopup; class HTMLElement; -class AccessibilityMenuListOption : public AccessibilityMockObject { +class AccessibilityMenuListOption final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityMenuListOption> create() { return adoptRef(new AccessibilityMenuListOption); } + static Ref<AccessibilityMenuListOption> create() { return adoptRef(*new AccessibilityMenuListOption); } void setElement(HTMLElement*); private: AccessibilityMenuListOption(); - virtual bool isMenuListOption() const { return true; } + virtual bool isMenuListOption() const override { return true; } - virtual AccessibilityRole roleValue() const { return MenuListOptionRole; } - virtual bool canHaveChildren() const { return false; } + virtual AccessibilityRole roleValue() const override { return MenuListOptionRole; } + virtual bool canHaveChildren() const override { return false; } - virtual Element* actionElement() const; - virtual bool isEnabled() const; - virtual bool isVisible() const; - virtual bool isOffScreen() const; - virtual bool isSelected() const; - virtual String nameForMSAA() const; - virtual void setSelected(bool); - virtual bool canSetSelectedAttribute() const; - virtual LayoutRect elementRect() const; - virtual String stringValue() const; - virtual bool computeAccessibilityIsIgnored() const; + virtual Element* actionElement() const override; + virtual bool isEnabled() const override; + virtual bool isVisible() const override; + virtual bool isOffScreen() const override; + virtual bool isSelected() const override; + virtual String nameForMSAA() const override; + virtual void setSelected(bool) override; + virtual bool canSetSelectedAttribute() const override; + virtual LayoutRect elementRect() const override; + virtual String stringValue() const override; + virtual bool computeAccessibilityIsIgnored() const override; RefPtr<HTMLElement> m_element; }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuListOption, isMenuListOption()) + #endif // AccessibilityMenuListOption_h diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp index 6e67f2cf5..fe1bd34b8 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.cpp @@ -70,19 +70,16 @@ bool AccessibilityMenuListPopup::computeAccessibilityIsIgnored() const AccessibilityMenuListOption* AccessibilityMenuListPopup::menuListOptionAccessibilityObject(HTMLElement* element) const { - if (!element || !isHTMLOptionElement(element)) - return 0; + if (!is<HTMLOptionElement>(element) || !element->inRenderedDocument()) + return nullptr; - AccessibilityObject* object = document()->axObjectCache()->getOrCreate(MenuListOptionRole); - ASSERT_WITH_SECURITY_IMPLICATION(object->isMenuListOption()); + auto& option = downcast<AccessibilityMenuListOption>(*document()->axObjectCache()->getOrCreate(MenuListOptionRole)); + option.setElement(element); - AccessibilityMenuListOption* option = static_cast<AccessibilityMenuListOption*>(object); - option->setElement(element); - - return option; + return &option; } -bool AccessibilityMenuListPopup::press() const +bool AccessibilityMenuListPopup::press() { if (!m_parent) return false; @@ -102,10 +99,8 @@ void AccessibilityMenuListPopup::addChildren() m_haveChildren = true; - const Vector<HTMLElement*>& listItems = toHTMLSelectElement(selectNode)->listItems(); - unsigned length = listItems.size(); - for (unsigned i = 0; i < length; i++) { - AccessibilityMenuListOption* option = menuListOptionAccessibilityObject(listItems[i]); + for (const auto& listItem : downcast<HTMLSelectElement>(*selectNode).listItems()) { + AccessibilityMenuListOption* option = menuListOptionAccessibilityObject(listItem); if (option) { option->setParent(this); m_children.append(option); @@ -118,7 +113,7 @@ void AccessibilityMenuListPopup::childrenChanged() AXObjectCache* cache = axObjectCache(); for (size_t i = m_children.size(); i > 0 ; --i) { AccessibilityObject* child = m_children[i - 1].get(); - if (child->actionElement() && !child->actionElement()->attached()) { + if (child->actionElement() && !child->actionElement()->inRenderedDocument()) { child->detachFromParent(); cache->remove(child->axObjectID()); } @@ -137,8 +132,8 @@ void AccessibilityMenuListPopup::didUpdateActiveOption(int optionIndex) AXObjectCache* cache = axObjectCache(); RefPtr<AccessibilityObject> child = m_children[optionIndex].get(); - cache->postNotification(child.get(), document(), AXObjectCache::AXFocusedUIElementChanged, true, PostSynchronously); - cache->postNotification(child.get(), document(), AXObjectCache::AXMenuListItemSelected, true, PostSynchronously); + cache->postNotification(child.get(), document(), AXObjectCache::AXFocusedUIElementChanged, TargetElement, PostSynchronously); + cache->postNotification(child.get(), document(), AXObjectCache::AXMenuListItemSelected, TargetElement, PostSynchronously); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityMenuListPopup.h b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h index 52b30dcef..6b4d223b3 100644 --- a/Source/WebCore/accessibility/AccessibilityMenuListPopup.h +++ b/Source/WebCore/accessibility/AccessibilityMenuListPopup.h @@ -35,33 +35,34 @@ class AccessibilityMenuListOption; class HTMLElement; class HTMLSelectElement; -class AccessibilityMenuListPopup : public AccessibilityMockObject { +class AccessibilityMenuListPopup final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityMenuListPopup> create() { return adoptRef(new AccessibilityMenuListPopup); } + static Ref<AccessibilityMenuListPopup> create() { return adoptRef(*new AccessibilityMenuListPopup); } - virtual bool isEnabled() const; - virtual bool isOffScreen() const; + virtual bool isEnabled() const override; + virtual bool isOffScreen() const override; void didUpdateActiveOption(int optionIndex); - private: AccessibilityMenuListPopup(); - virtual bool isMenuListPopup() const { return true; } + virtual bool isMenuListPopup() const override { return true; } - virtual LayoutRect elementRect() const { return LayoutRect(); } - virtual AccessibilityRole roleValue() const { return MenuListPopupRole; } + virtual LayoutRect elementRect() const override { return LayoutRect(); } + virtual AccessibilityRole roleValue() const override { return MenuListPopupRole; } - virtual bool isVisible() const; - virtual bool press() const; - virtual void addChildren(); - virtual void childrenChanged(); - virtual bool computeAccessibilityIsIgnored() const; + virtual bool isVisible() const override; + virtual bool press() override; + virtual void addChildren() override; + virtual void childrenChanged() override; + virtual bool computeAccessibilityIsIgnored() const override; AccessibilityMenuListOption* menuListOptionAccessibilityObject(HTMLElement*) const; }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMenuListPopup, isMenuListPopup()) + #endif // AccessibilityMenuListPopup_h diff --git a/Source/WebCore/accessibility/AccessibilityMockObject.cpp b/Source/WebCore/accessibility/AccessibilityMockObject.cpp index f48156125..0d804cecb 100644 --- a/Source/WebCore/accessibility/AccessibilityMockObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityMockObject.cpp @@ -29,7 +29,7 @@ namespace WebCore { AccessibilityMockObject::AccessibilityMockObject() - : m_parent(0) + : m_parent(nullptr) { } diff --git a/Source/WebCore/accessibility/AccessibilityMockObject.h b/Source/WebCore/accessibility/AccessibilityMockObject.h index 8a9a36e89..5059350b8 100644 --- a/Source/WebCore/accessibility/AccessibilityMockObject.h +++ b/Source/WebCore/accessibility/AccessibilityMockObject.h @@ -37,27 +37,25 @@ protected: public: virtual ~AccessibilityMockObject(); - virtual AccessibilityObject* parentObject() const { return m_parent; } - virtual void setParent(AccessibilityObject* parent) { m_parent = parent; }; - virtual bool isEnabled() const { return true; } - + virtual AccessibilityObject* parentObject() const override { return m_parent; } + virtual void setParent(AccessibilityObject* parent) { m_parent = parent; } + virtual bool isEnabled() const override { return true; } + protected: AccessibilityObject* m_parent; + // Must be called when the parent object clears its children. + virtual void detachFromParent() override { m_parent = nullptr; } + private: - virtual bool isMockObject() const { return true; } + virtual bool isMockObject() const override final { return true; } + virtual bool isDetachedFromParent() override { return !m_parent; } + + virtual bool computeAccessibilityIsIgnored() const override; +}; - virtual bool computeAccessibilityIsIgnored() const; - // Must be called when the parent object clears its children. - virtual void detachFromParent() { m_parent = 0; } -}; - -inline AccessibilityMockObject* toAccessibilityMockObject(AccessibilityObject* object) -{ - ASSERT(!object || object->isMockObject()); - return static_cast<AccessibilityMockObject*>(object); -} - } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityMockObject, isMockObject()) + #endif // AccessibilityMockObject_h diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp index 6aedd68be..5be294ff0 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,9 +31,11 @@ #include "AXObjectCache.h" #include "AccessibilityImageMapLink.h" +#include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" +#include "ElementIterator.h" #include "EventNames.h" #include "FloatRect.h" #include "Frame.h" @@ -41,6 +43,8 @@ #include "FrameSelection.h" #include "FrameView.h" #include "HTMLAreaElement.h" +#include "HTMLCanvasElement.h" +#include "HTMLDetailsElement.h" #include "HTMLFieldSetElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" @@ -53,6 +57,7 @@ #include "HTMLOptGroupElement.h" #include "HTMLOptionElement.h" #include "HTMLOptionsCollection.h" +#include "HTMLParserIdioms.h" #include "HTMLPlugInImageElement.h" #include "HTMLSelectElement.h" #include "HTMLTextAreaElement.h" @@ -61,17 +66,18 @@ #include "HitTestResult.h" #include "LabelableElement.h" #include "LocalizedStrings.h" +#include "MathMLElement.h" #include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" #include "Page.h" #include "ProgressTracker.h" +#include "RenderImage.h" +#include "RenderView.h" #include "SVGElement.h" #include "SVGNames.h" -#include "SVGStyledElement.h" #include "Text.h" #include "TextControlInnerElements.h" -#include "TextIterator.h" #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "Widget.h" @@ -80,12 +86,12 @@ #include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> -using namespace std; - namespace WebCore { using namespace HTMLNames; +static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr); + AccessibilityNodeObject::AccessibilityNodeObject(Node* node) : AccessibilityObject() , m_ariaRole(UnknownRole) @@ -112,16 +118,16 @@ void AccessibilityNodeObject::init() m_role = determineAccessibilityRole(); } -PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node) +Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node) { - return adoptRef(new AccessibilityNodeObject(node)); + return adoptRef(*new AccessibilityNodeObject(node)); } -void AccessibilityNodeObject::detach() +void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { - clearChildren(); - AccessibilityObject::detach(); - m_node = 0; + // AccessibilityObject calls clearChildren. + AccessibilityObject::detach(detachmentType, cache); + m_node = nullptr; } void AccessibilityNodeObject::childrenChanged() @@ -130,7 +136,10 @@ void AccessibilityNodeObject::childrenChanged() if (!node() && !renderer()) return; - axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged); // Go up the accessibility parent chain, but only if the element already exists. This method is // called during render layouts, minimal work should be done. @@ -143,12 +152,15 @@ void AccessibilityNodeObject::childrenChanged() // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update. // If this element supports ARIA live regions, then notify the AT of changes. + // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged + // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined + // time interval. if (parent->supportsARIALiveRegion()) - axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true); + cache->postLiveRegionChangeNotification(parent); // If this element is an ARIA text control, notify the AT of changes. - if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->rendererIsEditable()) - axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true); + if (parent->isNonNativeTextControl()) + cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged); } } @@ -165,12 +177,12 @@ void AccessibilityNodeObject::updateAccessibilityRole() AccessibilityObject* AccessibilityNodeObject::firstChild() const { if (!node()) - return 0; + return nullptr; Node* firstChild = node()->firstChild(); if (!firstChild) - return 0; + return nullptr; return axObjectCache()->getOrCreate(firstChild); } @@ -178,11 +190,11 @@ AccessibilityObject* AccessibilityNodeObject::firstChild() const AccessibilityObject* AccessibilityNodeObject::lastChild() const { if (!node()) - return 0; + return nullptr; Node* lastChild = node()->lastChild(); if (!lastChild) - return 0; + return nullptr; return axObjectCache()->getOrCreate(lastChild); } @@ -190,11 +202,11 @@ AccessibilityObject* AccessibilityNodeObject::lastChild() const AccessibilityObject* AccessibilityNodeObject::previousSibling() const { if (!node()) - return 0; + return nullptr; Node* previousSibling = node()->previousSibling(); if (!previousSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(previousSibling); } @@ -202,11 +214,11 @@ AccessibilityObject* AccessibilityNodeObject::previousSibling() const AccessibilityObject* AccessibilityNodeObject::nextSibling() const { if (!node()) - return 0; + return nullptr; Node* nextSibling = node()->nextSibling(); if (!nextSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(nextSibling); } @@ -219,13 +231,16 @@ AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const AccessibilityObject* AccessibilityNodeObject::parentObject() const { if (!node()) - return 0; + return nullptr; Node* parentObj = node()->parentNode(); - if (parentObj) - return axObjectCache()->getOrCreate(parentObj); + if (!parentObj) + return nullptr; - return 0; + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(parentObj); + + return nullptr; } LayoutRect AccessibilityNodeObject::elementRect() const @@ -261,8 +276,8 @@ void AccessibilityNodeObject::setNode(Node* node) Document* AccessibilityNodeObject::document() const { if (!node()) - return 0; - return node()->document(); + return nullptr; + return &node()->document(); } AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() @@ -270,52 +285,52 @@ AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() if (!node()) return UnknownRole; - m_ariaRole = determineAriaRoleAttribute(); + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; - if (node()->isLink()) return WebCoreLinkRole; if (node()->isTextNode()) return StaticTextRole; if (node()->hasTagName(buttonTag)) return buttonRoleType(); - if (isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isCheckbox()) + if (is<HTMLInputElement>(*node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isCheckbox()) return CheckBoxRole; - if (input->isRadioButton()) + if (input.isRadioButton()) return RadioButtonRole; - if (input->isTextButton()) + if (input.isTextButton()) return buttonRoleType(); - if (input->isRangeControl()) + if (input.isRangeControl()) return SliderRole; - + if (input.isInputTypeHidden()) + return IgnoredRole; + if (input.isSearchField()) + return SearchFieldRole; #if ENABLE(INPUT_TYPE_COLOR) - const AtomicString& type = input->getAttribute(typeAttr); - if (equalIgnoringCase(type, "color")) + if (input.isColorControl()) return ColorWellRole; #endif - return TextFieldRole; } if (node()->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(node()); - return selectElement->multiple() ? ListBoxRole : PopUpButtonRole; + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node()); + return selectElement.multiple() ? ListBoxRole : PopUpButtonRole; } - if (isHTMLTextAreaElement(node())) + if (is<HTMLTextAreaElement>(*node())) return TextAreaRole; if (headingLevel()) return HeadingRole; + if (node()->hasTagName(blockquoteTag)) + return BlockquoteRole; if (node()->hasTagName(divTag)) return DivRole; if (node()->hasTagName(pTag)) return ParagraphRole; - if (isHTMLLabelElement(node())) + if (is<HTMLLabelElement>(*node())) return LabelRole; - if (node()->isElementNode() && toElement(node())->isFocusable()) + if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable()) return GroupRole; return UnknownRole; @@ -332,7 +347,7 @@ void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned i child->clearChildren(); if (child->accessibilityIsIgnored()) { - AccessibilityChildrenVector children = child->children(); + const auto& children = child->children(); size_t length = children.size(); for (size_t i = 0; i < length; ++i) m_children.insert(index + i, children[i]); @@ -374,6 +389,10 @@ bool AccessibilityNodeObject::canHaveChildren() const if (!node() && !isAccessibilityRenderObject()) return false; + // When <noscript> is not being used (its renderer() == 0), ignore its children. + if (node() && !renderer() && node()->hasTagName(noscriptTag)) + return false; + // Elements that should not have children switch (roleValue()) { case ImageRole: @@ -387,6 +406,7 @@ bool AccessibilityNodeObject::canHaveChildren() const case ListBoxOptionRole: case ScrollBarRole: case ProgressIndicatorRole: + case SwitchRole: return false; default: return true; @@ -401,28 +421,43 @@ bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const ASSERT(m_initialized); #endif + // Handle non-rendered text that is exposed through aria-hidden=false. + if (m_node && m_node->isTextNode() && !renderer()) { + // Fallback content in iframe nodes should be ignored. + if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer()) + return true; + + // Whitespace only text elements should be ignored when they have no renderer. + String string = stringValue().stripWhiteSpace().simplifyWhiteSpace(); + if (!string.length()) + return true; + } + + AccessibilityObjectInclusion decision = defaultObjectInclusion(); + if (decision == IncludeObject) + return false; + if (decision == IgnoreObject) + return true; // If this element is within a parent that cannot have children, it should not be exposed. if (isDescendantOfBarrenParent()) return true; + if (roleValue() == IgnoredRole) + return true; + return m_role == UnknownRole; } bool AccessibilityNodeObject::canvasHasFallbackContent() const { Node* node = this->node(); - if (!node || !node->hasTagName(canvasTag)) + if (!is<HTMLCanvasElement>(node)) return false; - + HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node); // If it has any children that are elements, we'll assume it might be fallback // content. If it has no children or its only children are not elements // (e.g. just text nodes), it doesn't have fallback content. - for (Node* child = node->firstChild(); child; child = child->nextSibling()) { - if (child->isElementNode()) - return true; - } - - return false; + return childrenOfType<Element>(canvasElement).first(); } bool AccessibilityNodeObject::isImageButton() const @@ -430,23 +465,18 @@ bool AccessibilityNodeObject::isImageButton() const return isNativeImage() && isButton(); } -bool AccessibilityNodeObject::isAnchor() const -{ - return !isNativeImage() && isLink(); -} - bool AccessibilityNodeObject::isNativeTextControl() const { Node* node = this->node(); if (!node) return false; - if (isHTMLTextAreaElement(node)) + if (is<HTMLTextAreaElement>(*node)) return true; - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isText() || input->isNumberField(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isText() || input.isNumberField(); } return false; @@ -458,12 +488,13 @@ bool AccessibilityNodeObject::isSearchField() const if (!node) return false; - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) + if (roleValue() == SearchFieldRole) + return true; + + if (!is<HTMLInputElement>(*node)) return false; - if (inputElement->isSearchField()) - return true; + auto& inputElement = downcast<HTMLInputElement>(*node); // Some websites don't label their search fields as such. However, they will // use the word "search" in either the form or input type. This won't catch every case, @@ -475,7 +506,7 @@ bool AccessibilityNodeObject::isSearchField() const return true; // Check the form action and the name, which will sometimes be "search". - HTMLFormElement* form = inputElement->form(); + auto* form = inputElement.form(); if (form && (form->name().contains("search", false) || form->action().contains("search", false))) return true; @@ -488,15 +519,15 @@ bool AccessibilityNodeObject::isNativeImage() const if (!node) return false; - if (isHTMLImageElement(node)) + if (is<HTMLImageElement>(*node)) return true; if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag)) return true; - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isImageButton(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isImageButton(); } return false; @@ -509,29 +540,41 @@ bool AccessibilityNodeObject::isImage() const bool AccessibilityNodeObject::isPasswordField() const { - Node* node = this->node(); - if (!node || !node->isHTMLElement()) + auto* node = this->node(); + if (!is<HTMLInputElement>(node)) return false; if (ariaRoleAttribute() != UnknownRole) return false; - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) - return false; + return downcast<HTMLInputElement>(*node).isPasswordField(); +} + +AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField() +{ + Node* node = this->node(); + if (!node) + return nullptr; - return inputElement->isPasswordField(); + if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField()) + return this; + + auto* element = node->shadowHost(); + if (!is<HTMLInputElement>(element)) + return nullptr; + + if (auto* cache = axObjectCache()) + return cache->getOrCreate(element); + + return nullptr; } bool AccessibilityNodeObject::isInputImage() const { Node* node = this->node(); - if (!node) - return false; - - if (roleValue() == ButtonRole && isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - return input->isImageButton(); + if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + return input.isImageButton(); } return false; @@ -554,6 +597,8 @@ bool AccessibilityNodeObject::isMenuRelated() const case MenuBarRole: case MenuButtonRole: case MenuItemRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: return true; default: return false; @@ -577,45 +622,51 @@ bool AccessibilityNodeObject::isMenuButton() const bool AccessibilityNodeObject::isMenuItem() const { - return roleValue() == MenuItemRole; + switch (roleValue()) { + case MenuItemRole: + case MenuItemRadioRole: + case MenuItemCheckboxRole: + return true; + default: + return false; + } } bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const { Node* node = this->node(); - if (!node) + if (!is<HTMLInputElement>(node)) return false; - HTMLInputElement* input = node->toInputElement(); - if (input) - return input->isCheckbox() || input->isRadioButton(); - - return false; + auto& input = downcast<HTMLInputElement>(*node); + return input.isCheckbox() || input.isRadioButton(); } bool AccessibilityNodeObject::isEnabled() const { - if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true")) + // ARIA says that the disabled status applies to the current element and all descendant elements. + for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) { + const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr); + if (equalLettersIgnoringASCIICase(disabledStatus, "true")) + return false; + if (equalLettersIgnoringASCIICase(disabledStatus, "false")) + break; + } + + if (roleValue() == HorizontalRuleRole) return false; - + Node* node = this->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return true; - return !toElement(node)->isDisabledFormControl(); + return !downcast<Element>(*node).isDisabledFormControl(); } bool AccessibilityNodeObject::isIndeterminate() const { - Node* node = this->node(); - if (!node) - return false; - - HTMLInputElement* inputElement = node->toInputElement(); - if (!inputElement) - return false; - - return inputElement->shouldAppearIndeterminate(); + auto* node = this->node(); + return is<HTMLInputElement>(node) && downcast<HTMLInputElement>(*node).shouldAppearIndeterminate(); } bool AccessibilityNodeObject::isPressed() const @@ -628,15 +679,16 @@ bool AccessibilityNodeObject::isPressed() const return false; // If this is an ARIA button, check the aria-pressed attribute rather than node()->active() - if (ariaRoleAttribute() == ButtonRole) { - if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true")) + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == ButtonRole || ariaRole == ToggleButtonRole) { + if (equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true")) return true; return false; } - if (!node->isElementNode()) + if (!is<Element>(*node)) return false; - return toElement(node)->active(); + return downcast<Element>(*node).active(); } bool AccessibilityNodeObject::isChecked() const @@ -646,9 +698,8 @@ bool AccessibilityNodeObject::isChecked() const return false; // First test for native checkedness semantics - HTMLInputElement* inputElement = node->toInputElement(); - if (inputElement) - return inputElement->shouldAppearChecked(); + if (is<HTMLInputElement>(*node)) + return downcast<HTMLInputElement>(*node).shouldAppearChecked(); // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute bool validRole = false; @@ -656,13 +707,16 @@ bool AccessibilityNodeObject::isChecked() const case RadioButtonRole: case CheckBoxRole: case MenuItemRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: + case SwitchRole: validRole = true; break; default: break; } - if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true")) + if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true")) return true; return false; @@ -671,49 +725,32 @@ bool AccessibilityNodeObject::isChecked() const bool AccessibilityNodeObject::isHovered() const { Node* node = this->node(); - if (!node) - return false; - - return node->isElementNode() && toElement(node)->hovered(); + return is<Element>(node) && downcast<Element>(*node).hovered(); } bool AccessibilityNodeObject::isMultiSelectable() const { const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr); - if (equalIgnoringCase(ariaMultiSelectable, "true")) + if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true")) return true; - if (equalIgnoringCase(ariaMultiSelectable, "false")) + if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "false")) return false; - return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple(); -} - -bool AccessibilityNodeObject::isReadOnly() const -{ - Node* node = this->node(); - if (!node) - return true; - - if (isHTMLTextAreaElement(node)) - return toHTMLFormControlElement(node)->isReadOnly(); - - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextField()) - return input->isReadOnly(); - } - - return !node->rendererIsEditable(); + return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple(); } bool AccessibilityNodeObject::isRequired() const { - if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true")) + // Explicit aria-required values should trump native required attributes. + const AtomicString& requiredValue = getAttribute(aria_requiredAttr); + if (equalLettersIgnoringASCIICase(requiredValue, "true")) return true; + if (equalLettersIgnoringASCIICase(requiredValue, "false")) + return false; Node* n = this->node(); - if (n && (n->isElementNode() && toElement(n)->isFormControlElement())) - return static_cast<HTMLFormControlElement*>(n)->isRequired(); + if (is<HTMLFormControlElement>(n)) + return downcast<HTMLFormControlElement>(*n).isRequired(); return false; } @@ -721,10 +758,14 @@ bool AccessibilityNodeObject::isRequired() const bool AccessibilityNodeObject::supportsRequiredAttribute() const { switch (roleValue()) { + case ButtonRole: + return isFileUploadButton(); case CellRole: + case ColumnHeaderRole: case CheckBoxRole: case ComboBoxRole: case GridRole: + case GridCellRole: case IncrementorRole: case ListBoxRole: case PopUpButtonRole: @@ -787,10 +828,10 @@ String AccessibilityNodeObject::valueDescription() const float AccessibilityNodeObject::valueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->valueAsNumber(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.valueAsNumber(); } if (!isRangeControl()) @@ -801,10 +842,10 @@ float AccessibilityNodeObject::valueForRange() const float AccessibilityNodeObject::maxValueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->maximum(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.maximum(); } if (!isRangeControl()) @@ -815,10 +856,10 @@ float AccessibilityNodeObject::maxValueForRange() const float AccessibilityNodeObject::minValueForRange() const { - if (node() && isHTMLInputElement(node())) { - HTMLInputElement* input = toHTMLInputElement(node()); - if (input->isRangeControl()) - return input->minimum(); + if (is<HTMLInputElement>(node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node()); + if (input.isRangeControl()) + return input.minimum(); } if (!isRangeControl()) @@ -848,8 +889,7 @@ bool AccessibilityNodeObject::isControl() const if (!node) return false; - return ((node->isElementNode() && toElement(node)->isFormControlElement()) - || AccessibilityObject::isARIAControl(ariaRoleAttribute())); + return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute()); } bool AccessibilityNodeObject::isFieldset() const @@ -869,37 +909,30 @@ bool AccessibilityNodeObject::isGroup() const AccessibilityObject* AccessibilityNodeObject::selectedRadioButton() { if (!isRadioGroup()) - return 0; - - AccessibilityObject::AccessibilityChildrenVector children = this->children(); + return nullptr; // Find the child radio button that is selected (ie. the intValue == 1). - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) { - AccessibilityObject* object = children[i].get(); - if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn) - return object; + for (const auto& child : children()) { + if (child->roleValue() == RadioButtonRole && child->checkboxOrRadioValue() == ButtonStateOn) + return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AccessibilityNodeObject::selectedTabItem() { if (!isTabList()) - return 0; + return nullptr; // Find the child tab item that is selected (ie. the intValue == 1). AccessibilityObject::AccessibilityChildrenVector tabs; tabChildren(tabs); - AccessibilityObject::AccessibilityChildrenVector children = this->children(); - size_t size = tabs.size(); - for (size_t i = 0; i < size; ++i) { - AccessibilityObject* object = children[i].get(); - if (object->isTabItem() && object->isChecked()) - return object; + for (const auto& child : children()) { + if (child->isTabItem() && child->isChecked()) + return child.get(); } - return 0; + return nullptr; } AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const @@ -914,44 +947,62 @@ Element* AccessibilityNodeObject::anchorElement() const { Node* node = this->node(); if (!node) - return 0; + return nullptr; AXObjectCache* cache = axObjectCache(); // search up the DOM tree for an anchor element // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement for ( ; node; node = node->parentNode()) { - if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) - return toElement(node); + if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink())) + return downcast<Element>(node); } - return 0; + return nullptr; } +static bool isNodeActionElement(Node* node) +{ + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton())) + return true; + } else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag)) + return true; + + return false; +} + +static Element* nativeActionElement(Node* start) +{ + if (!start) + return nullptr; + + // Do a deep-dive to see if any nodes should be used as the action element. + // We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons). + // It solves the problem when authors put role="button" on a group and leave the actual button inside the group. + + for (Node* child = start->firstChild(); child; child = child->nextSibling()) { + if (isNodeActionElement(child)) + return downcast<Element>(child); + + if (Element* subChild = nativeActionElement(child)) + return subChild; + } + return nullptr; +} + Element* AccessibilityNodeObject::actionElement() const { Node* node = this->node(); if (!node) - return 0; - - if (isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (!input->isDisabledFormControl() && (isCheckboxOrRadio() || input->isTextButton())) - return input; - } else if (node->hasTagName(buttonTag)) - return toElement(node); - - if (isFileUploadButton()) - return toElement(node); - - if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) - return toElement(node); + return nullptr; - if (isImageButton()) - return toElement(node); + if (isNodeActionElement(node)) + return downcast<Element>(node); - if (node->hasTagName(selectTag)) - return toElement(node); + if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) + return downcast<Element>(node); switch (roleValue()) { case ButtonRole: @@ -959,8 +1010,13 @@ Element* AccessibilityNodeObject::actionElement() const case ToggleButtonRole: case TabRole: case MenuItemRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: case ListItemRole: - return toElement(node); + // Check if the author is hiding the real control element inside the ARIA element. + if (Element* nativeElement = nativeActionElement(node)) + return nativeElement; + return downcast<Element>(node); default: break; } @@ -971,31 +1027,25 @@ Element* AccessibilityNodeObject::actionElement() const return elt; } -Element* AccessibilityNodeObject::mouseButtonListener() const +Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const { Node* node = this->node(); if (!node) - return 0; + return nullptr; // check if our parent is a mouse button listener - while (node && !node->isElementNode()) - node = node->parentNode(); - - if (!node) - return 0; - // FIXME: Do the continuation search like anchorElement does - for (Element* element = toElement(node); element; element = element->parentElement()) { - // If we've reached the body and this is not a control element, do not expose press action for this element. - // It can cause false positives, where every piece of text is labeled as accepting press actions. - if (element->hasTagName(bodyTag) && isStaticText()) + for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) { + // If we've reached the body and this is not a control element, do not expose press action for this element unless filter is IncludeBodyElement. + // It can cause false positives, where every piece of text is labeled as accepting press actions. + if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement) break; - if (element->hasEventListeners(eventNames().clickEvent) || element->hasEventListeners(eventNames().mousedownEvent) || element->hasEventListeners(eventNames().mouseupEvent)) - return element; + if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent)) + return &element; } - return 0; + return nullptr; } bool AccessibilityNodeObject::isDescendantOfBarrenParent() const @@ -1021,13 +1071,13 @@ void AccessibilityNodeObject::alterSliderValue(bool increase) void AccessibilityNodeObject::increment() { - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); + UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document()); alterSliderValue(true); } void AccessibilityNodeObject::decrement() { - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); + UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document()); alterSliderValue(false); } @@ -1040,18 +1090,23 @@ void AccessibilityNodeObject::changeValueByStep(bool increase) setValue(String::number(value)); - axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true); + axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged); } void AccessibilityNodeObject::changeValueByPercent(float percentChange) { float range = maxValueForRange() - minValueForRange(); + float step = range * (percentChange / 100); float value = valueForRange(); - value += range * (percentChange / 100); + // Make sure the specified percent will cause a change of one integer step or larger. + if (fabs(step) < 1) + step = fabs(percentChange) * (1 / percentChange); + + value += step; setValue(String::number(value)); - axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true); + axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged); } bool AccessibilityNodeObject::isGenericFocusableElement() const @@ -1059,8 +1114,12 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const if (!canSetFocusAttribute()) return false; - // If it's a control, it's not generic. - if (isControl()) + // If it's a control, it's not generic. + if (isControl()) + return false; + + AccessibilityRole role = roleValue(); + if (role == VideoRole || role == AudioRole) return false; // If it has an aria role, it's not generic. @@ -1076,14 +1135,14 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const // The web area and body element are both focusable, but existing logic handles these // cases already, so we don't need to include them here. - if (roleValue() == WebAreaRole) + if (role == WebAreaRole) return false; if (node() && node()->hasTagName(bodyTag)) return false; // An SVG root is focusable by default, but it's probably not interactive, so don't // include it. It can still be made accessible by giving it an ARIA role. - if (roleValue() == SVGRootRole) + if (role == SVGRootRole) return false; return true; @@ -1091,21 +1150,16 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const { - if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable()) - return 0; + if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable()) + return nullptr; const AtomicString& id = element->getIdAttribute(); if (!id.isEmpty()) { - if (HTMLLabelElement* label = element->treeScope()->labelElementForId(id)) + if (HTMLLabelElement* label = element->treeScope().labelElementForId(id)) return label; } - for (Element* parent = element->parentElement(); parent; parent = parent->parentElement()) { - if (isHTMLLabelElement(parent)) - return toHTMLLabelElement(parent); - } - - return 0; + return ancestorsOfType<HTMLLabelElement>(*element).first(); } String AccessibilityNodeObject::ariaAccessibilityDescription() const @@ -1121,69 +1175,81 @@ String AccessibilityNodeObject::ariaAccessibilityDescription() const return String(); } -static Element* siblingWithAriaRole(String role, Node* node) +static Element* siblingWithAriaRole(Node* node, const char* role) { - Node* parent = node->parentNode(); + // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer. + ContainerNode* parent = node->parentNode(); if (!parent) - return 0; - - for (Node* sibling = parent->firstChild(); sibling; sibling = sibling->nextSibling()) { - if (sibling->isElementNode()) { - const AtomicString& siblingAriaRole = toElement(sibling)->getAttribute(roleAttr); - if (equalIgnoringCase(siblingAriaRole, role)) - return toElement(sibling); - } + return nullptr; + + for (auto& sibling : childrenOfType<Element>(*parent)) { + // FIXME: Should skip sibling that is the same as the node. + if (equalIgnoringASCIICase(sibling.fastGetAttribute(roleAttr), role)) + return &sibling; } - - return 0; + + return nullptr; } Element* AccessibilityNodeObject::menuElementForMenuButton() const { if (ariaRoleAttribute() != MenuButtonRole) - return 0; + return nullptr; - return siblingWithAriaRole("menu", node()); + return siblingWithAriaRole(node(), "menu"); } AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const { - return axObjectCache()->getOrCreate(menuElementForMenuButton()); + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(menuElementForMenuButton()); + return nullptr; } Element* AccessibilityNodeObject::menuItemElementForMenu() const { if (ariaRoleAttribute() != MenuRole) - return 0; + return nullptr; - return siblingWithAriaRole("menuitem", node()); + return siblingWithAriaRole(node(), "menuitem"); } AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + Element* menuItem = menuItemElementForMenu(); if (menuItem) { // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem - AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem); + AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem); if (menuItemAX && menuItemAX->isMenuButton()) return menuItemAX; } - return 0; + return nullptr; } -void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) +bool AccessibilityNodeObject::usesAltTagForTextComputation() const +{ + return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag)); +} + +void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const { Node* node = this->node(); if (!node) return; - bool isInputTag = isHTMLInputElement(node); + bool isInputTag = is<HTMLInputElement>(*node); if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label) { + if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) { AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label); - textOrder.append(AccessibilityText(label->innerText(), LabelByElementText, labelObject)); + String innerText = label->innerText(); + // Only use the <label> text if there's no ARIA override. + if (!innerText.isEmpty() && !ariaAccessibilityDescription()) + textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject)); return; } } @@ -1208,11 +1274,20 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd if (!ariaLabel.isEmpty()) textOrder.append(AccessibilityText(ariaLabel, AlternativeText)); - if (isImage() || isInputImage() || isNativeImage() || isCanvas()) { + if (usesAltTagForTextComputation()) { + if (is<RenderImage>(renderer())) { + String renderAltText = downcast<RenderImage>(*renderer()).altText(); + + // RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText. + if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) { + textOrder.append(AccessibilityText(renderAltText, AlternativeText)); + return; + } + } // Images should use alt as long as the attribute is present, even if empty. // Otherwise, it should fallback to other methods, like the title attribute. const AtomicString& alt = getAttribute(altAttr); - if (!alt.isNull()) + if (!alt.isEmpty()) textOrder.append(AccessibilityText(alt, AlternativeText)); } @@ -1220,14 +1295,19 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd if (!node) return; -#if ENABLE(SVG) + // The fieldset element derives its alternative text from the first associated legend element if one is available. + if (is<HTMLFieldSetElement>(*node)) { + AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend()); + if (object && !object->isHidden()) + textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText)); + } + // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. - if (node->isSVGElement() && toSVGElement(node)->isSVGStyledElement()) - textOrder.append(AccessibilityText(toSVGStyledElement(node)->title(), AlternativeText)); -#endif + if (node->isSVGElement()) + textOrder.append(AccessibilityText(downcast<SVGElement>(*node).title(), AlternativeText)); #if ENABLE(MATHML) - if (node->isElementNode() && toElement(node)->isMathMLElement()) + if (node->isMathMLElement()) textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText)); #endif } @@ -1238,11 +1318,11 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) if (!node) return; - bool isInputTag = isHTMLInputElement(node); + bool isInputTag = is<HTMLInputElement>(*node); if (isInputTag) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextButton()) { - textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText)); + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextButton()) { + textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText)); return; } } @@ -1258,16 +1338,22 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue(). if (node->hasTagName(selectTag)) break; + FALLTHROUGH; case ButtonRole: case ToggleButtonRole: case CheckBoxRole: case ListBoxOptionRole: + // MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that. +#if !PLATFORM(COCOA) case ListItemRole: +#endif case MenuButtonRole: case MenuItemRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: case RadioButtonRole: + case SwitchRole: case TabRole: - case ProgressIndicatorRole: useTextUnderElement = true; break; default: @@ -1276,11 +1362,17 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) // If it's focusable but it's not content editable or a known control type, then it will appear to // the user as a single atomic object, so we should use its text as the default title. - if (isHeading() || isLink() || isGenericFocusableElement()) + if (isHeading() || isLink()) useTextUnderElement = true; if (useTextUnderElement) { - String text = textUnderElement(); + AccessibilityTextUnderElementMode mode; + + // Headings often include links as direct children. Those links need to be included in text under element. + if (isHeading()) + mode.includeFocusableContent = true; + + String text = textUnderElement(mode); if (!text.isEmpty()) textOrder.append(AccessibilityText(text, ChildrenText)); } @@ -1295,27 +1387,23 @@ void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) con String describedBy = ariaDescribedByAttribute(); if (!describedBy.isEmpty()) textOrder.append(AccessibilityText(describedBy, SummaryText)); - - // Add help type text that is derived from ancestors. - for (Node* curr = node(); curr; curr = curr->parentNode()) { - const AtomicString& summary = getAttribute(summaryAttr); - if (!summary.isEmpty()) - textOrder.append(AccessibilityText(summary, SummaryText)); - - // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = getAttribute(titleAttr); - if (!title.isEmpty()) + + // Summary attribute used as help text on tables. + const AtomicString& summary = getAttribute(summaryAttr); + if (!summary.isEmpty()) + textOrder.append(AccessibilityText(summary, SummaryText)); + + // The title attribute should be used as help text unless it is already being used as descriptive text. + // However, when the title attribute is the only text alternative provided, it may be exposed as the + // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors + // can expose units through this attribute. Therefore, if the element is a meter, change its source + // type to HelpText. + const AtomicString& title = getAttribute(titleAttr); + if (!title.isEmpty()) { + if (!isMeter()) textOrder.append(AccessibilityText(title, TitleTagText)); - - // Only take help text from an ancestor element if its a group or an unknown role. If help was - // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); - if (!axObj) - return; - - AccessibilityRole role = axObj->roleValue(); - if (role != GroupRole && role != UnknownRole) - break; + else + textOrder.append(AccessibilityText(title, HelpText)); } } @@ -1338,14 +1426,13 @@ void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textO Vector<Element*> elements; ariaLabeledByElements(elements); - Vector<RefPtr<AccessibilityObject> > axElements; - unsigned length = elements.size(); - for (unsigned k = 0; k < length; k++) { - RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(elements[k]); + Vector<RefPtr<AccessibilityObject>> axElements; + for (const auto& element : elements) { + RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(element); axElements.append(axElement); } - textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements)); + textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTFMove(axElements))); } } @@ -1367,30 +1454,26 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const // Check if the HTML element has an aria-label for the webpage. if (Element* documentElement = document->documentElement()) { - const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr); + const AtomicString& ariaLabel = documentElement->fastGetAttribute(aria_labelAttr); if (!ariaLabel.isEmpty()) return ariaLabel; } - Node* owner = document->ownerElement(); - if (owner) { + if (auto* owner = document->ownerElement()) { if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { - const AtomicString& title = toElement(owner)->getAttribute(titleAttr); + const AtomicString& title = owner->fastGetAttribute(titleAttr); if (!title.isEmpty()) return title; - return toElement(owner)->getNameAttribute(); } - if (owner->isHTMLElement()) - return toHTMLElement(owner)->getNameAttribute(); + return owner->getNameAttribute(); } String documentTitle = document->title(); if (!documentTitle.isEmpty()) return documentTitle; - owner = document->body(); - if (owner && owner->isHTMLElement()) - return toHTMLElement(owner)->getNameAttribute(); + if (auto* body = document->bodyOrFrameset()) + return body->getNameAttribute(); return String(); } @@ -1405,7 +1488,7 @@ String AccessibilityNodeObject::accessibilityDescription() const if (!ariaDescription.isEmpty()) return ariaDescription; - if (isImage() || isInputImage() || isNativeImage() || isCanvas()) { + if (usesAltTagForTextComputation()) { // Images should use alt as long as the attribute is present, even if empty. // Otherwise, it should fallback to other methods, like the title attribute. const AtomicString& alt = getAttribute(altAttr); @@ -1413,14 +1496,12 @@ String AccessibilityNodeObject::accessibilityDescription() const return alt; } -#if ENABLE(SVG) // SVG elements all can have a <svg:title> element inside which should act as the descriptive text. - if (m_node && m_node->isSVGElement() && toSVGElement(m_node)->isSVGStyledElement()) - return toSVGStyledElement(m_node)->title(); -#endif + if (m_node && m_node->isSVGElement()) + return downcast<SVGElement>(*m_node).title(); #if ENABLE(MATHML) - if (m_node && m_node->isElementNode() && toElement(m_node)->isMathMLElement()) + if (is<MathMLElement>(m_node)) return getAttribute(MathMLNames::alttextAttr); #endif @@ -1449,21 +1530,22 @@ String AccessibilityNodeObject::helpText() const return describedBy; String description = accessibilityDescription(); - for (Node* curr = node; curr; curr = curr->parentNode()) { - if (curr->isHTMLElement()) { - const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr); + for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { + if (is<HTMLElement>(*ancestor)) { + HTMLElement& element = downcast<HTMLElement>(*ancestor); + const AtomicString& summary = element.getAttribute(summaryAttr); if (!summary.isEmpty()) return summary; // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = toElement(curr)->getAttribute(titleAttr); + const AtomicString& title = element.getAttribute(titleAttr); if (!title.isEmpty() && description != title) return title; } // Only take help text from an ancestor element if its a group or an unknown role. If help was // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor); if (axObj) { AccessibilityRole role = axObj->roleValue(); if (role != GroupRole && role != UnknownRole) @@ -1477,10 +1559,10 @@ String AccessibilityNodeObject::helpText() const unsigned AccessibilityNodeObject::hierarchicalLevel() const { Node* node = this->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return 0; - Element* element = toElement(node); - String ariaLevel = element->getAttribute(aria_levelAttr); + Element& element = downcast<Element>(*node); + const AtomicString& ariaLevel = element.fastGetAttribute(aria_levelAttr); if (!ariaLevel.isEmpty()) return ariaLevel.toInt(); @@ -1502,12 +1584,25 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const return level; } +void AccessibilityNodeObject::setIsExpanded(bool expand) +{ +#if ENABLE(DETAILS_ELEMENT) + if (is<HTMLDetailsElement>(node())) { + auto& details = downcast<HTMLDetailsElement>(*node()); + if (expand != details.isOpen()) + details.toggleOpen(); + } +#else + UNUSED_PARAM(expand); +#endif +} + // When building the textUnderElement for an object, determine whether or not // we should include the inner text of this given descendant object or skip it. -static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode) +static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode) { // Do not use any heuristic if we are explicitly asking to include all the children. - if (mode == TextUnderElementModeIncludeAllChildren) + if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) return true; // Consider this hypothetical example: @@ -1527,7 +1622,12 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces // quite long. As a heuristic, skip links, controls, and elements that are usually // containers with lots of children. - if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true")) + // ARIA states that certain elements are not allowed to expose their children content for name calculation. + if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren + && !obj->accessibleNameDerivesFromContent()) + return false; + + if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true")) return false; // If something doesn't expose any children, then we can always take the inner text content. @@ -1536,47 +1636,73 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces return true; // Skip focusable children, so we don't include the text of links and controls. - if (obj->canSetFocusAttribute()) + if (obj->canSetFocusAttribute() && !mode.includeFocusableContent) return false; // Skip big container elements like lists, tables, etc. - if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas()) + if (is<AccessibilityList>(*obj)) + return false; + + if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility()) + return false; + + if (obj->isTree() || obj->isCanvas()) return false; return true; } +static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText) +{ + if (!builder.length() || !childText.length()) + return false; + + // We don't need to add an additional space before or after a line break. + return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1])); +} + +static void appendNameToStringBuilder(StringBuilder& builder, const String& text) +{ + if (shouldAddSpaceBeforeAppendingNextElement(builder, text)) + builder.append(' '); + builder.append(text); +} + String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const { Node* node = this->node(); - if (node && node->isTextNode()) - return toText(node)->wholeText(); + if (is<Text>(node)) + return downcast<Text>(*node).wholeText(); StringBuilder builder; for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) { - if (!shouldUseAccessiblityObjectInnerText(child, mode)) + if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode) + continue; + + bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent()); + if (shouldDeriveNameFromAuthor) { + appendNameToStringBuilder(builder, accessibleNameForNode(child->node())); + continue; + } + + if (!shouldUseAccessibilityObjectInnerText(child, mode)) continue; - if (child->isAccessibilityNodeObject()) { + if (is<AccessibilityNodeObject>(*child)) { Vector<AccessibilityText> textOrder; - toAccessibilityNodeObject(child)->alternativeText(textOrder); + downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder); if (textOrder.size() > 0 && textOrder[0].text.length()) { - if (builder.length()) - builder.append(' '); - builder.append(textOrder[0].text); + appendNameToStringBuilder(builder, textOrder[0].text); continue; } } - + String childText = child->textUnderElement(mode); - if (childText.length()) { - if (builder.length()) - builder.append(' '); - builder.append(childText); - } + if (childText.length()) + appendNameToStringBuilder(builder, childText); } - return builder.toString().stripWhiteSpace().simplifyWhiteSpace(); + return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak); } String AccessibilityNodeObject::title() const @@ -1585,16 +1711,17 @@ String AccessibilityNodeObject::title() const if (!node) return String(); - bool isInputTag = isHTMLInputElement(node); + bool isInputTag = is<HTMLInputElement>(*node); if (isInputTag) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isTextButton()) - return input->valueWithDefault(); + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextButton()) + return input.valueWithDefault(); } if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label && !exposesTitleUIElement()) + HTMLLabelElement* label = labelForElement(downcast<Element>(node)); + // Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override. + if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length()) return label->innerText(); } @@ -1607,6 +1734,7 @@ String AccessibilityNodeObject::title() const // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue(). if (node->hasTagName(selectTag)) return String(); + FALLTHROUGH; case ButtonRole: case ToggleButtonRole: case CheckBoxRole: @@ -1614,7 +1742,10 @@ String AccessibilityNodeObject::title() const case ListItemRole: case MenuButtonRole: case MenuItemRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: case RadioButtonRole: + case SwitchRole: case TabRole: return textUnderElement(); // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>. @@ -1624,13 +1755,10 @@ String AccessibilityNodeObject::title() const break; } - if (isHeading() || isLink()) - return textUnderElement(); - - // If it's focusable but it's not content editable or a known control type, then it will appear to - // the user as a single atomic object, so we should use its text as the default title. - if (isGenericFocusableElement()) + if (isLink()) return textUnderElement(); + if (isHeading()) + return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true)); return String(); } @@ -1638,8 +1766,12 @@ String AccessibilityNodeObject::title() const String AccessibilityNodeObject::text() const { // If this is a user defined static text, use the accessible name computation. - if (ariaRoleAttribute() == StaticTextRole) - return ariaAccessibilityDescription(); + if (ariaRoleAttribute() == StaticTextRole) { + Vector<AccessibilityText> textOrder; + alternativeText(textOrder); + if (textOrder.size() > 0 && textOrder[0].text.length()) + return textOrder[0].text; + } if (!isTextControl()) return String(); @@ -1648,13 +1780,13 @@ String AccessibilityNodeObject::text() const if (!node) return String(); - if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node))) - return toHTMLTextFormControlElement(node)->value(); + if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node)) + return downcast<HTMLTextFormControlElement>(*node).value(); if (!node->isElementNode()) return String(); - return toElement(node)->innerText(); + return downcast<Element>(node)->innerText(); } String AccessibilityNodeObject::stringValue() const @@ -1674,16 +1806,16 @@ String AccessibilityNodeObject::stringValue() const return textUnderElement(); if (node->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(node); - int selectedIndex = selectElement->selectedIndex(); - const Vector<HTMLElement*> listItems = selectElement->listItems(); + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node); + int selectedIndex = selectElement.selectedIndex(); + const Vector<HTMLElement*>& listItems = selectElement.listItems(); if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); if (!overriddenDescription.isNull()) return overriddenDescription; } - if (!selectElement->multiple()) - return selectElement->value(); + if (!selectElement.multiple()) + return selectElement.value(); return String(); } @@ -1703,57 +1835,103 @@ void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const g = 0; b = 0; +#if ENABLE(INPUT_TYPE_COLOR) if (!isColorWell()) return; - if (!node() || !isHTMLInputElement(node())) + if (!is<HTMLInputElement>(node())) return; - HTMLInputElement* input = toHTMLInputElement(node()); - const AtomicString& type = input->getAttribute(typeAttr); - if (!equalIgnoringCase(type, "color")) + auto& input = downcast<HTMLInputElement>(*node()); + if (!input.isColorControl()) return; // HTMLInputElement::value always returns a string parseable by Color(). - Color color(input->value()); + Color color(input.value()); r = color.red(); g = color.green(); b = color.blue(); +#endif } // This function implements the ARIA accessible name as described by the Mozilla // ARIA Implementer's Guide. -static String accessibleNameForNode(Node* node) +static String accessibleNameForNode(Node* node, Node* labelledbyNode) { - if (node->isTextNode()) - return toText(node)->data(); + ASSERT(node); + if (!is<Element>(node)) + return String(); + + Element& element = downcast<Element>(*node); + const AtomicString& ariaLabel = element.fastGetAttribute(aria_labelAttr); + if (!ariaLabel.isEmpty()) + return ariaLabel; + + const AtomicString& alt = element.fastGetAttribute(altAttr); + if (!alt.isEmpty()) + return alt; + + // If the node can be turned into an AX object, we can use standard name computation rules. + // If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath. + AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node); + if (axObject) { + String valueDescription = axObject->valueDescription(); + if (!valueDescription.isEmpty()) + return valueDescription; + } + + if (is<HTMLInputElement>(*node)) + return downcast<HTMLInputElement>(*node).value(); + + String text; + if (axObject) { + if (axObject->accessibleNameDerivesFromContent()) + text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode)); + } else + text = element.innerText(); - if (isHTMLInputElement(node)) - return toHTMLInputElement(node)->value(); + if (!text.isEmpty()) + return text; + + const AtomicString& title = element.fastGetAttribute(titleAttr); + if (!title.isEmpty()) + return title; + + return String(); +} - if (node->isHTMLElement()) { - const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr); - if (!alt.isEmpty()) - return alt; +String AccessibilityNodeObject::accessibilityDescriptionForChildren() const +{ + Node* node = this->node(); + if (!node) + return String(); + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return String(); + + StringBuilder builder; + for (Node* child = node->firstChild(); child; child = child->nextSibling()) { + if (!is<Element>(child)) + continue; + + if (AccessibilityObject* axObject = cache->getOrCreate(child)) { + String description = axObject->ariaLabeledByAttribute(); + if (description.isEmpty()) + description = accessibleNameForNode(child); + appendNameToStringBuilder(builder, description); + } } - return String(); + return builder.toString(); } String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const { StringBuilder builder; unsigned size = elements.size(); - for (unsigned i = 0; i < size; ++i) { - Element* idElement = elements[i]; - - builder.append(accessibleNameForNode(idElement)); - for (Node* n = idElement->firstChild(); n; n = NodeTraversal::next(n, idElement)) - builder.append(accessibleNameForNode(n)); - - if (i != size - 1) - builder.append(' '); - } + for (unsigned i = 0; i < size; ++i) + appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node())); return builder.toString(); } @@ -1765,39 +1943,11 @@ String AccessibilityNodeObject::ariaDescribedByAttribute() const return accessibilityDescriptionForElements(elements); } -void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const -{ - Node* node = this->node(); - if (!node || !node->isElementNode()) - return; - - TreeScope* scope = node->treeScope(); - if (!scope) - return; - - String idList = getAttribute(attribute).string(); - if (idList.isEmpty()) - return; - - idList.replace('\n', ' '); - Vector<String> idVector; - idList.split(' ', idVector); - - unsigned size = idVector.size(); - for (unsigned i = 0; i < size; ++i) { - AtomicString idName(idVector[i]); - Element* idElement = scope->getElementById(idName); - if (idElement) - elements.append(idElement); - } -} - - void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const { - elementsFromAttribute(elements, aria_labeledbyAttr); + elementsFromAttribute(elements, aria_labelledbyAttr); if (!elements.size()) - elementsFromAttribute(elements, aria_labelledbyAttr); + elementsFromAttribute(elements, aria_labeledbyAttr); } @@ -1809,6 +1959,17 @@ String AccessibilityNodeObject::ariaLabeledByAttribute() const return accessibilityDescriptionForElements(elements); } +bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const +{ + if (AccessibilityObject::hasAttributesRequiredForInclusion()) + return true; + + if (!ariaAccessibilityDescription().isEmpty()) + return true; + + return false; +} + bool AccessibilityNodeObject::canSetFocusAttribute() const { Node* node = this->node(); @@ -1821,18 +1982,66 @@ bool AccessibilityNodeObject::canSetFocusAttribute() const // NOTE: It would be more accurate to ask the document whether setFocusedElement() would // do anything. For example, setFocusedElement() will do nothing if the current focused // node will not relinquish the focus. - if (!node) + if (!is<Element>(node)) return false; - if (!node->isElementNode()) + Element& element = downcast<Element>(*node); + + if (element.isDisabledFormControl()) return false; - Element* element = toElement(node); + return element.supportsFocus(); +} - if (element->isDisabledFormControl()) +bool AccessibilityNodeObject::canSetValueAttribute() const +{ + Node* node = this->node(); + if (!node) return false; - return element->supportsFocus(); + // The host-language readonly attribute trumps aria-readonly. + if (is<HTMLTextAreaElement>(*node)) + return !downcast<HTMLTextAreaElement>(*node).isReadOnly(); + if (is<HTMLInputElement>(*node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isTextField()) + return !input.isReadOnly(); + } + + String readOnly = ariaReadOnlyValue(); + if (!readOnly.isEmpty()) + return readOnly == "true" ? false : true; + + if (isNonNativeTextControl()) + return true; + + if (isMeter()) + return false; + + if (isProgressIndicator() || isSlider()) + return true; + +#if PLATFORM(GTK) || PLATFORM(EFL) + // In ATK, input types which support aria-readonly are treated as having a + // settable value if the user can modify the widget's value or its state. + if (supportsARIAReadOnly() || isRadioButton()) + return true; +#endif + + if (isWebArea()) { + Document* document = this->document(); + if (!document) + return false; + + if (HTMLElement* body = document->bodyOrFrameset()) { + if (body->hasEditableStyle()) + return true; + } + + return document->hasEditableStyle(); + } + + return node->hasEditableStyle(); } AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const @@ -1855,6 +2064,10 @@ AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const role = remapAriaRoleDueToParent(role); + // Presentational roles are invalidated by the presence of ARIA attributes. + if (role == PresentationalRole && supportsARIAAttributes()) + role = UnknownRole; + if (role) return role; @@ -1894,22 +2107,12 @@ AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(Accessibilit return role; } -// If you call node->rendererIsEditable() since that will return true if an ancestor is editable. -// This only returns true if this is the element that actually has the contentEditable attribute set. -bool AccessibilityNodeObject::hasContentEditableAttributeSet() const -{ - if (!hasAttribute(contenteditableAttr)) - return false; - const AtomicString& contentEditableValue = getAttribute(contenteditableAttr); - // Both "true" (case-insensitive) and the empty string count as true. - return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true"); -} - bool AccessibilityNodeObject::canSetSelectedAttribute() const { // Elements that can be selected switch (roleValue()) { case CellRole: + case GridCellRole: case RadioButtonRole: case RowHeaderRole: case RowRole: diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.h b/Source/WebCore/accessibility/AccessibilityNodeObject.h index bc7088919..7b63eaa67 100644 --- a/Source/WebCore/accessibility/AccessibilityNodeObject.h +++ b/Source/WebCore/accessibility/AccessibilityNodeObject.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -54,105 +54,112 @@ class RenderTextControl; class RenderView; class VisibleSelection; class Widget; - + +enum MouseButtonListenerResultFilter { + ExcludeBodyElement = 1, + IncludeBodyElement, +}; + class AccessibilityNodeObject : public AccessibilityObject { -protected: - explicit AccessibilityNodeObject(Node*); public: - static PassRefPtr<AccessibilityNodeObject> create(Node*); + static Ref<AccessibilityNodeObject> create(Node*); virtual ~AccessibilityNodeObject(); - virtual void init(); - - virtual bool isAccessibilityNodeObject() const { return true; } - - virtual bool canvasHasFallbackContent() const; - - virtual bool isAnchor() const; - virtual bool isControl() const; - virtual bool isFieldset() const; - virtual bool isGroup() const; - virtual bool isHeading() const; - virtual bool isHovered() const; - virtual bool isImage() const; - virtual bool isImageButton() const; - virtual bool isInputImage() const; - virtual bool isLink() const; - virtual bool isMenu() const; - virtual bool isMenuBar() const; - virtual bool isMenuButton() const; - virtual bool isMenuItem() const; - virtual bool isMenuRelated() const; - virtual bool isMultiSelectable() const; + virtual void init() override; + + virtual bool canvasHasFallbackContent() const override; + + virtual bool isControl() const override; + virtual bool isFieldset() const override; + virtual bool isGroup() const override; + virtual bool isHeading() const override; + virtual bool isHovered() const override; + virtual bool isImage() const override; + virtual bool isImageButton() const override; + virtual bool isInputImage() const override; + virtual bool isLink() const override; + virtual bool isMenu() const override; + virtual bool isMenuBar() const override; + virtual bool isMenuButton() const override; + virtual bool isMenuItem() const override; + virtual bool isMenuRelated() const override; + virtual bool isMultiSelectable() const override; virtual bool isNativeCheckboxOrRadio() const; - virtual bool isNativeImage() const; - virtual bool isNativeTextControl() const; - virtual bool isPasswordField() const; - virtual bool isProgressIndicator() const; - virtual bool isSearchField() const; - virtual bool isSlider() const; - - virtual bool isChecked() const; - virtual bool isEnabled() const; - virtual bool isIndeterminate() const; - virtual bool isPressed() const; - virtual bool isReadOnly() const; - virtual bool isRequired() const; - virtual bool supportsRequiredAttribute() const; - - virtual bool canSetSelectedAttribute() const OVERRIDE; + virtual bool isNativeImage() const override; + virtual bool isNativeTextControl() const override; + virtual bool isPasswordField() const override; + virtual AccessibilityObject* passwordFieldOrContainingPasswordField() override; + virtual bool isProgressIndicator() const override; + virtual bool isSearchField() const override; + virtual bool isSlider() const override; + + virtual bool isChecked() const override; + virtual bool isEnabled() const override; + virtual bool isIndeterminate() const override; + virtual bool isPressed() const override; + virtual bool isRequired() const override; + virtual bool supportsRequiredAttribute() const override; + + virtual bool canSetSelectedAttribute() const override; void setNode(Node*); - virtual Node* node() const { return m_node; } - virtual Document* document() const; - - virtual bool canSetFocusAttribute() const; - virtual int headingLevel() const; - - virtual String valueDescription() const; - virtual float valueForRange() const; - virtual float maxValueForRange() const; - virtual float minValueForRange() const; - virtual float stepValueForRange() const; - - virtual AccessibilityObject* selectedRadioButton(); - virtual AccessibilityObject* selectedTabItem(); - virtual AccessibilityButtonState checkboxOrRadioValue() const; - - virtual unsigned hierarchicalLevel() const; - virtual String textUnderElement(AccessibilityTextUnderElementMode = TextUnderElementModeSkipIgnoredChildren) const; - virtual String accessibilityDescription() const; - virtual String helpText() const; - virtual String title() const; - virtual String text() const; - virtual String stringValue() const; - virtual void colorValue(int& r, int& g, int& b) const; - virtual String ariaLabeledByAttribute() const; - - virtual Element* actionElement() const; - Element* mouseButtonListener() const; - virtual Element* anchorElement() const; + virtual Node* node() const override { return m_node; } + virtual Document* document() const override; + + virtual bool canSetFocusAttribute() const override; + virtual int headingLevel() const override; + + bool canSetValueAttribute() const override; + + virtual String valueDescription() const override; + virtual float valueForRange() const override; + virtual float maxValueForRange() const override; + virtual float minValueForRange() const override; + virtual float stepValueForRange() const override; + + virtual AccessibilityObject* selectedRadioButton() override; + virtual AccessibilityObject* selectedTabItem() override; + virtual AccessibilityButtonState checkboxOrRadioValue() const override; + + virtual unsigned hierarchicalLevel() const override; + virtual String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; + virtual String accessibilityDescriptionForChildren() const; + virtual String accessibilityDescription() const override; + virtual String helpText() const override; + virtual String title() const override; + virtual String text() const override; + virtual String stringValue() const override; + virtual void colorValue(int& r, int& g, int& b) const override; + virtual String ariaLabeledByAttribute() const override; + virtual bool hasAttributesRequiredForInclusion() const override final; + virtual void setIsExpanded(bool) override; + + virtual Element* actionElement() const override; + Element* mouseButtonListener(MouseButtonListenerResultFilter = ExcludeBodyElement) const; + virtual Element* anchorElement() const override; AccessibilityObject* menuForMenuButton() const; virtual void changeValueByPercent(float percentChange); - virtual AccessibilityObject* firstChild() const; - virtual AccessibilityObject* lastChild() const; - virtual AccessibilityObject* previousSibling() const; - virtual AccessibilityObject* nextSibling() const; - virtual AccessibilityObject* parentObject() const; - virtual AccessibilityObject* parentObjectIfExists() const; + virtual AccessibilityObject* firstChild() const override; + virtual AccessibilityObject* lastChild() const override; + virtual AccessibilityObject* previousSibling() const override; + virtual AccessibilityObject* nextSibling() const override; + virtual AccessibilityObject* parentObject() const override; + virtual AccessibilityObject* parentObjectIfExists() const override; - virtual void detach(); - virtual void childrenChanged(); - virtual void updateAccessibilityRole(); + virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; + virtual void childrenChanged() override; + virtual void updateAccessibilityRole() override; - virtual void increment(); - virtual void decrement(); + virtual void increment() override; + virtual void decrement() override; - virtual LayoutRect elementRect() const; + virtual LayoutRect elementRect() const override; protected: + explicit AccessibilityNodeObject(Node*); + AccessibilityRole m_ariaRole; bool m_childrenDirty; mutable AccessibilityRole m_roleForMSAA; @@ -160,19 +167,18 @@ protected: bool m_initialized; #endif - virtual bool isDetached() const { return !m_node; } + virtual bool isDetached() const override { return !m_node; } virtual AccessibilityRole determineAccessibilityRole(); - virtual void addChildren(); - virtual void addChild(AccessibilityObject*); - virtual void insertChild(AccessibilityObject*, unsigned index); + virtual void addChildren() override; + virtual void addChild(AccessibilityObject*) override; + virtual void insertChild(AccessibilityObject*, unsigned index) override; - virtual bool canHaveChildren() const; - AccessibilityRole ariaRoleAttribute() const; + virtual bool canHaveChildren() const override; + virtual AccessibilityRole ariaRoleAttribute() const override; AccessibilityRole determineAriaRoleAttribute() const; AccessibilityRole remapAriaRoleDueToParent(AccessibilityRole) const; - bool hasContentEditableAttributeSet() const; - virtual bool isDescendantOfBarrenParent() const; + virtual bool isDescendantOfBarrenParent() const override; void alterSliderValue(bool increase); void changeValueByStep(bool increase); // This returns true if it's focusable but it's not content editable and it's not a control or ARIA control. @@ -181,42 +187,30 @@ protected: String ariaAccessibilityDescription() const; void ariaLabeledByElements(Vector<Element*>& elements) const; String accessibilityDescriptionForElements(Vector<Element*> &elements) const; - void elementsFromAttribute(Vector<Element*>& elements, const QualifiedName&) const; - virtual LayoutRect boundingBoxRect() const; - String ariaDescribedByAttribute() const; + virtual LayoutRect boundingBoxRect() const override; + virtual String ariaDescribedByAttribute() const override; Element* menuElementForMenuButton() const; Element* menuItemElementForMenu() const; AccessibilityObject* menuButtonForMenu() const; private: - Node* m_node; - - virtual void accessibilityText(Vector<AccessibilityText>&); - void titleElementText(Vector<AccessibilityText>&); + virtual bool isAccessibilityNodeObject() const override final { return true; } + virtual void accessibilityText(Vector<AccessibilityText>&) override; + virtual void titleElementText(Vector<AccessibilityText>&) const; void alternativeText(Vector<AccessibilityText>&) const; void visibleText(Vector<AccessibilityText>&) const; void helpText(Vector<AccessibilityText>&) const; String alternativeTextForWebArea() const; void ariaLabeledByText(Vector<AccessibilityText>&) const; - virtual bool computeAccessibilityIsIgnored() const; -}; - -inline AccessibilityNodeObject* toAccessibilityNodeObject(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isAccessibilityNodeObject()); - return static_cast<AccessibilityNodeObject*>(object); -} + virtual bool computeAccessibilityIsIgnored() const override; + bool usesAltTagForTextComputation() const; -inline const AccessibilityNodeObject* toAccessibilityNodeObject(const AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isAccessibilityNodeObject()); - return static_cast<const AccessibilityNodeObject*>(object); -} - -// This will catch anyone doing an unnecessary cast. -void toAccessibilityNodeObject(const AccessibilityNodeObject*); + Node* m_node; +}; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityNodeObject, isAccessibilityNodeObject()) + #endif // AccessibilityNodeObject_h diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index ff473e649..a5203012a 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,23 +31,34 @@ #include "AXObjectCache.h" #include "AccessibilityRenderObject.h" +#include "AccessibilityScrollView.h" #include "AccessibilityTable.h" +#include "DOMTokenList.h" #include "Editor.h" +#include "ElementIterator.h" +#include "EventHandler.h" #include "FloatRect.h" #include "FocusController.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" +#include "HTMLDetailsElement.h" +#include "HTMLInputElement.h" #include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "HitTestResult.h" #include "LocalizedStrings.h" +#include "MainFrame.h" +#include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" -#include "NotImplemented.h" #include "Page.h" #include "RenderImage.h" +#include "RenderLayer.h" #include "RenderListItem.h" #include "RenderListMarker.h" #include "RenderMenuList.h" +#include "RenderText.h" #include "RenderTextControl.h" #include "RenderTheme.h" #include "RenderView.h" @@ -60,13 +71,12 @@ #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> #include <wtf/text/StringBuilder.h> #include <wtf/text/WTFString.h> #include <wtf/unicode/CharacterNames.h> -using namespace std; - namespace WebCore { using namespace HTMLNames; @@ -77,7 +87,7 @@ AccessibilityObject::AccessibilityObject() , m_role(UnknownRole) , m_lastKnownIsIgnoredValue(DefaultBehavior) #if PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY)) - , m_wrapper(0) + , m_wrapper(nullptr) #endif { } @@ -87,14 +97,18 @@ AccessibilityObject::~AccessibilityObject() ASSERT(isDetached()); } -void AccessibilityObject::detach() +void AccessibilityObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { + // Menu close events need to notify the platform. No element is used in the notification because it's a destruction event. + if (detachmentType == ElementDestroyed && roleValue() == MenuRole && cache) + cache->postNotification(nullptr, &cache->document(), AXObjectCache::AXMenuClosed); + // Clear any children and call detachFromParent on them so that // no children are left with dangling pointers to their parent. clearChildren(); #if HAVE(ACCESSIBILITY) - setWrapper(0); + setWrapper(nullptr); #endif } @@ -199,6 +213,9 @@ bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityO case MisspelledWordSearchKey: return axObject->hasMisspelling(); + case OutlineSearchKey: + return axObject->isTree(); + case PlainTextSearchKey: return axObject->hasPlainText(); @@ -218,11 +235,11 @@ bool AccessibilityObject::isAccessibilityObjectSearchMatchAtIndex(AccessibilityO case TableSameLevelSearchKey: return criteria->startObject - && axObject->isAccessibilityTable() - && axObject->tableLevel() == criteria->startObject->tableLevel(); + && is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility() + && downcast<AccessibilityTable>(*axObject).tableLevel() == criteria->startObject->tableLevel(); case TableSearchKey: - return axObject->isAccessibilityTable(); + return is<AccessibilityTable>(*axObject) && downcast<AccessibilityTable>(*axObject).isExposableThroughAccessibility(); case TextFieldSearchKey: return axObject->isTextControl(); @@ -262,7 +279,7 @@ bool AccessibilityObject::isAccessibilityTextSearchMatch(AccessibilityObject* ax if (!axObject || !criteria) return false; - return axObject->accessibilityObjectContainsText(criteria->searchText); + return axObject->accessibilityObjectContainsText(&criteria->searchText); } bool AccessibilityObject::accessibilityObjectContainsText(String* text) const @@ -275,17 +292,95 @@ bool AccessibilityObject::accessibilityObjectContainsText(String* text) const || stringValue().contains(*text, false); } +// ARIA marks elements as having their accessible name derive from either their contents, or their author provide name. +bool AccessibilityObject::accessibleNameDerivesFromContent() const +{ + // First check for objects specifically identified by ARIA. + switch (ariaRoleAttribute()) { + case ApplicationAlertRole: + case ApplicationAlertDialogRole: + case ApplicationDialogRole: + case ApplicationLogRole: + case ApplicationMarqueeRole: + case ApplicationStatusRole: + case ApplicationTimerRole: + case ComboBoxRole: + case DefinitionRole: + case DocumentRole: + case DocumentArticleRole: + case DocumentMathRole: + case DocumentNoteRole: + case DocumentRegionRole: + case FormRole: + case GridRole: + case GroupRole: + case ImageRole: + case ListRole: + case ListBoxRole: + case LandmarkApplicationRole: + case LandmarkBannerRole: + case LandmarkComplementaryRole: + case LandmarkContentInfoRole: + case LandmarkNavigationRole: + case LandmarkMainRole: + case LandmarkSearchRole: + case MenuRole: + case MenuBarRole: + case ProgressIndicatorRole: + case RadioGroupRole: + case ScrollBarRole: + case SliderRole: + case SpinButtonRole: + case SplitterRole: + case TableRole: + case TabListRole: + case TabPanelRole: + case TextAreaRole: + case TextFieldRole: + case ToolbarRole: + case TreeGridRole: + case TreeRole: + return false; + default: + break; + } + + // Now check for generically derived elements now that we know the element does not match a specific ARIA role. + switch (roleValue()) { + case SliderRole: + return false; + default: + break; + } + + return true; +} + +String AccessibilityObject::computedLabel() +{ + // This method is being called by WebKit inspector, which may happen at any time, so we need to update our backing store now. + // Also hold onto this object in case updateBackingStore deletes this node. + RefPtr<AccessibilityObject> protector(this); + updateBackingStore(); + Vector<AccessibilityText> text; + accessibilityText(text); + if (text.size()) + return text[0].text; + return String(); +} + bool AccessibilityObject::isBlockquote() const { - return node() && node()->hasTagName(blockquoteTag); + return roleValue() == BlockquoteRole; } bool AccessibilityObject::isTextControl() const { switch (roleValue()) { + case ComboBoxRole: + case SearchFieldRole: case TextAreaRole: case TextFieldRole: - case ComboBoxRole: return true; default: return false; @@ -294,7 +389,12 @@ bool AccessibilityObject::isTextControl() const bool AccessibilityObject::isARIATextControl() const { - return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole; + return ariaRoleAttribute() == TextAreaRole || ariaRoleAttribute() == TextFieldRole || ariaRoleAttribute() == SearchFieldRole; +} + +bool AccessibilityObject::isNonNativeTextControl() const +{ + return (isARIATextControl() || hasContentEditableAttributeSet()) && !isNativeTextControl(); } bool AccessibilityObject::isLandmark() const @@ -315,11 +415,7 @@ bool AccessibilityObject::hasMisspelling() const if (!node()) return false; - Document* document = node()->document(); - if (!document) - return false; - - Frame* frame = document->frame(); + Frame* frame = node()->document().frame(); if (!frame) return false; @@ -329,13 +425,11 @@ bool AccessibilityObject::hasMisspelling() const if (!textChecker) return false; - const UChar* chars = stringValue().characters(); - int charsLength = stringValue().length(); bool isMisspelled = false; if (unifiedTextCheckerEnabled(frame)) { Vector<TextCheckingResult> results; - checkTextOfParagraph(textChecker, chars, charsLength, TextCheckingTypeSpelling, results); + checkTextOfParagraph(*textChecker, stringValue(), TextCheckingTypeSpelling, results); if (!results.isEmpty()) isMisspelled = true; return isMisspelled; @@ -343,7 +437,7 @@ bool AccessibilityObject::hasMisspelling() const int misspellingLength = 0; int misspellingLocation = -1; - textChecker->checkSpellingOfString(chars, charsLength, &misspellingLocation, &misspellingLength); + textChecker->checkSpellingOfString(stringValue(), &misspellingLocation, &misspellingLength); if (misspellingLength || misspellingLocation != -1) isMisspelled = true; @@ -370,26 +464,48 @@ AccessibilityObject* AccessibilityObject::parentObjectUnignored() const return parent; } -AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) +AccessibilityObject* AccessibilityObject::previousSiblingUnignored(int limit) const { - if (!node) - return 0; + AccessibilityObject* previous; + ASSERT(limit >= 0); + for (previous = previousSibling(); previous && previous->accessibilityIsIgnored(); previous = previous->previousSibling()) { + limit--; + if (limit <= 0) + break; + } + return previous; +} - Document* document = node->document(); - if (!document) - return 0; +AccessibilityObject* AccessibilityObject::nextSiblingUnignored(int limit) const +{ + AccessibilityObject* next; + ASSERT(limit >= 0); + for (next = nextSibling(); next && next->accessibilityIsIgnored(); next = next->nextSibling()) { + limit--; + if (limit <= 0) + break; + } + return next; +} - AXObjectCache* cache = document->axObjectCache(); +AccessibilityObject* AccessibilityObject::firstAccessibleObjectFromNode(const Node* node) +{ + if (!node) + return nullptr; + AXObjectCache* cache = node->document().axObjectCache(); + if (!cache) + return nullptr; + AccessibilityObject* accessibleObject = cache->getOrCreate(node->renderer()); while (accessibleObject && accessibleObject->accessibilityIsIgnored()) { - node = NodeTraversal::next(node); + node = NodeTraversal::next(*node); while (node && !node->renderer()) - node = NodeTraversal::nextSkippingChildren(node); + node = NodeTraversal::nextSkippingChildren(*node); if (!node) - return 0; + return nullptr; accessibleObject = cache->getOrCreate(node->renderer()); } @@ -402,14 +518,14 @@ static void appendAccessibilityObject(AccessibilityObject* object, Accessibility // Find the next descendant of this attachment object so search can continue through frames. if (object->isAttachment()) { Widget* widget = object->widgetForAttachmentView(); - if (!widget || !widget->isFrameView()) + if (!is<FrameView>(widget)) return; - Document* doc = toFrameView(widget)->frame()->document(); - if (!doc || !doc->renderer()) + Document* document = downcast<FrameView>(*widget).frame().document(); + if (!document || !document->hasLivingRenderTree()) return; - object = object->axObjectCache()->getOrCreate(doc); + object = object->axObjectCache()->getOrCreate(document); } if (object) @@ -418,13 +534,9 @@ static void appendAccessibilityObject(AccessibilityObject* object, Accessibility static void appendChildrenToArray(AccessibilityObject* object, bool isForward, AccessibilityObject* startObject, AccessibilityObject::AccessibilityChildrenVector& results) { - AccessibilityObject::AccessibilityChildrenVector searchChildren; // A table's children includes elements whose own children are also the table's children (due to the way the Mac exposes tables). // The rows from the table should be queried, since those are direct descendants of the table, and they contain content. - if (object->isAccessibilityTable()) - searchChildren = toAccessibilityTable(object)->rows(); - else - searchChildren = object->children(); + const auto& searchChildren = is<AccessibilityTable>(*object) && downcast<AccessibilityTable>(*object).isExposableThroughAccessibility() ? downcast<AccessibilityTable>(*object).rows() : object->children(); size_t childrenSize = searchChildren.size(); @@ -470,7 +582,8 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite if (!criteria) return; - axObjectCache()->startCachingComputedObjectAttributesUntilTreeMutates(); + if (AXObjectCache* cache = axObjectCache()) + cache->startCachingComputedObjectAttributesUntilTreeMutates(); // This search mechanism only searches the elements before/after the starting object. // It does this by stepping up the parent chain and at each level doing a DFS. @@ -482,10 +595,11 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite bool isForward = criteria->searchDirection == SearchDirectionNext; - // In the first iteration of the loop, it will examine the children of the start object for matches. - // However, when going backwards, those children should not be considered, so the loop is skipped ahead. - AccessibilityObject* previousObject = 0; - if (!isForward) { + // The first iteration of the outer loop will examine the children of the start object for matches. However, when + // iterating backwards, the start object children should not be considered, so the loop is skipped ahead. We make an + // exception when no start object was specified because we want to search everything regardless of search direction. + AccessibilityObject* previousObject = nullptr; + if (!isForward && startObject != this) { previousObject = startObject; startObject = startObject->parentObjectUnignored(); } @@ -496,7 +610,8 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite // Only append the children after/before the previous element, so that the search does not check elements that are // already behind/ahead of start element. AccessibilityChildrenVector searchStack; - appendChildrenToArray(startObject, isForward, previousObject, searchStack); + if (!criteria->immediateDescendantsOnly || startObject == this) + appendChildrenToArray(startObject, isForward, previousObject, searchStack); // This now does a DFS at the current level of the parent. while (!searchStack.isEmpty()) { @@ -506,23 +621,214 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite if (objectMatchesSearchCriteriaWithResultLimit(searchObject, criteria, results)) break; - appendChildrenToArray(searchObject, isForward, 0, searchStack); + if (!criteria->immediateDescendantsOnly) + appendChildrenToArray(searchObject, isForward, 0, searchStack); } if (results.size() >= criteria->resultsLimit) break; // When moving backwards, the parent object needs to be checked, because technically it's "before" the starting element. - if (!isForward && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results)) + if (!isForward && startObject != this && objectMatchesSearchCriteriaWithResultLimit(startObject, criteria, results)) break; previousObject = startObject; } } +// Returns the range that is fewer positions away from the reference range. +// NOTE: The after range is expected to ACTUALLY be after the reference range and the before +// range is expected to ACTUALLY be before. These are not checked for performance reasons. +static RefPtr<Range> rangeClosestToRange(Range* referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange) +{ + if (!referenceRange) + return nullptr; + + // The treeScope for shadow nodes may not be the same scope as another element in a document. + // Comparisons may fail in that case, which are expected behavior and should not assert. + if (afterRange && ((afterRange->startPosition().anchorNode()->compareDocumentPosition(referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)) + return nullptr; + ASSERT(!afterRange || afterRange->startPosition() >= referenceRange->endPosition()); + + if (beforeRange && ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)) + return nullptr; + ASSERT(!beforeRange || beforeRange->endPosition() <= referenceRange->startPosition()); + + if (!afterRange && !beforeRange) + return nullptr; + if (afterRange && !beforeRange) + return afterRange; + if (!afterRange && beforeRange) + return beforeRange; + + unsigned positionsToAfterRange = Position::positionCountBetweenPositions(afterRange->startPosition(), referenceRange->endPosition()); + unsigned positionsToBeforeRange = Position::positionCountBetweenPositions(beforeRange->endPosition(), referenceRange->startPosition()); + + return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange; +} + +RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String>& searchStrings) const +{ + Frame* frame = this->frame(); + if (!frame) + return nullptr; + + if (!referenceRange) + return nullptr; + + bool isBackwardSearch = searchDirection == SearchDirectionPrevious; + FindOptions findOptions = AtWordStarts | AtWordEnds | CaseInsensitive | StartInSelection; + if (isBackwardSearch) + findOptions |= Backwards; + + RefPtr<Range> closestStringRange = nullptr; + for (const auto& searchString : searchStrings) { + if (RefPtr<Range> searchStringRange = frame->editor().rangeOfString(searchString, referenceRange, findOptions)) { + if (!closestStringRange) + closestStringRange = searchStringRange; + else { + // If searching backward, use the trailing range edges to correctly determine which + // range is closest. Similarly, if searching forward, use the leading range edges. + Position closestStringPosition = isBackwardSearch ? closestStringRange->endPosition() : closestStringRange->startPosition(); + Position searchStringPosition = isBackwardSearch ? searchStringRange->endPosition() : searchStringRange->startPosition(); + + int closestPositionOffset = closestStringPosition.computeOffsetInContainerNode(); + int searchPositionOffset = searchStringPosition.computeOffsetInContainerNode(); + Node* closestContainerNode = closestStringPosition.containerNode(); + Node* searchContainerNode = searchStringPosition.containerNode(); + + short result = Range::compareBoundaryPoints(closestContainerNode, closestPositionOffset, searchContainerNode, searchPositionOffset, ASSERT_NO_EXCEPTION); + if ((!isBackwardSearch && result > 0) || (isBackwardSearch && result < 0)) + closestStringRange = searchStringRange; + } + } + } + return closestStringRange; +} + +// Returns the range of the entire document if there is no selection. +RefPtr<Range> AccessibilityObject::selectionRange() const +{ + Frame* frame = this->frame(); + if (!frame) + return nullptr; + + const VisibleSelection& selection = frame->selection().selection(); + if (!selection.isNone()) + return selection.firstRange(); + + return Range::create(*frame->document()); +} + +RefPtr<Range> AccessibilityObject::elementRange() const +{ + return AXObjectCache::rangeForNodeContents(node()); +} + +String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria) +{ + ASSERT(criteria); + + if (!criteria) + return String(); + + Frame* frame = this->frame(); + if (!frame) + return String(); + + AccessibilitySelectTextActivity& activity = criteria->activity; + AccessibilitySelectTextAmbiguityResolution& ambiguityResolution = criteria->ambiguityResolution; + String& replacementString = criteria->replacementString; + Vector<String>& searchStrings = criteria->searchStrings; + + RefPtr<Range> selectedStringRange = selectionRange(); + // When starting our search again, make this a zero length range so that search forwards will find this selected range if its appropriate. + selectedStringRange->setEnd(&selectedStringRange->startContainer(), selectedStringRange->startOffset()); + + RefPtr<Range> closestAfterStringRange = nullptr; + RefPtr<Range> closestBeforeStringRange = nullptr; + // Search forward if necessary. + if (ambiguityResolution == ClosestAfterSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution) + closestAfterStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionNext, searchStrings); + // Search backward if necessary. + if (ambiguityResolution == ClosestBeforeSelectionAmbiguityResolution || ambiguityResolution == ClosestToSelectionAmbiguityResolution) + closestBeforeStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), SearchDirectionPrevious, searchStrings); + + // Determine which candidate is closest to the selection and perform the activity. + if (RefPtr<Range> closestStringRange = rangeClosestToRange(selectedStringRange.get(), WTFMove(closestAfterStringRange), WTFMove(closestBeforeStringRange))) { + // If the search started within a text control, ensure that the result is inside that element. + if (element() && element()->isTextFormControl()) { + if (!closestStringRange->startContainer().isDescendantOrShadowDescendantOf(element()) || !closestStringRange->endContainer().isDescendantOrShadowDescendantOf(element())) + return String(); + } + + String closestString = closestStringRange->text(); + bool replaceSelection = false; + if (frame->selection().setSelectedRange(closestStringRange.get(), DOWNSTREAM, true)) { + switch (activity) { + case FindAndCapitalize: + replacementString = closestString; + makeCapitalized(&replacementString, 0); + replaceSelection = true; + break; + case FindAndUppercase: + replacementString = closestString.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + replaceSelection = true; + break; + case FindAndLowercase: + replacementString = closestString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + replaceSelection = true; + break; + case FindAndReplaceActivity: { + replaceSelection = true; + // When applying find and replace activities, we want to match the capitalization of the replaced text, + // (unless we're replacing with an abbreviation.) + if (closestString.length() > 0 && replacementString.length() > 2 && replacementString != replacementString.convertToUppercaseWithoutLocale()) { + if (closestString[0] == u_toupper(closestString[0])) + makeCapitalized(&replacementString, 0); + else + replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly. + } + break; + } + case FindAndSelectActivity: + break; + } + + // A bit obvious, but worth noting the API contract for this method is that we should + // return the replacement string when replacing, but the selected string if not. + if (replaceSelection) { + frame->editor().replaceSelectionWithText(replacementString, true, true); + return replacementString; + } + + return closestString; + } + } + + return String(); +} + +bool AccessibilityObject::hasAttributesRequiredForInclusion() const +{ + // These checks are simplified in the interest of execution speed. + if (!getAttribute(aria_helpAttr).isEmpty() + || !getAttribute(aria_describedbyAttr).isEmpty() + || !getAttribute(altAttr).isEmpty() + || !getAttribute(titleAttr).isEmpty()) + return true; + +#if ENABLE(MATHML) + if (!getAttribute(MathMLNames::alttextAttr).isEmpty()) + return true; +#endif + + return false; +} + bool AccessibilityObject::isARIAInput(AccessibilityRole ariaRole) { - return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole; + return ariaRole == RadioButtonRole || ariaRole == CheckBoxRole || ariaRole == TextFieldRole || ariaRole == SwitchRole || ariaRole == SearchFieldRole; } bool AccessibilityObject::isARIAControl(AccessibilityRole ariaRole) @@ -544,6 +850,16 @@ bool AccessibilityObject::isRangeControl() const } } +bool AccessibilityObject::isMeter() const +{ +#if ENABLE(METER_ELEMENT) + RenderObject* renderer = this->renderer(); + return renderer && renderer->isMeter(); +#else + return false; +#endif +} + IntPoint AccessibilityObject::clickPoint() { LayoutRect rect = elementRect(); @@ -556,35 +872,105 @@ IntRect AccessibilityObject::boundingBoxForQuads(RenderObject* obj, const Vector if (!obj) return IntRect(); - size_t count = quads.size(); - if (!count) - return IntRect(); - - IntRect result; - for (size_t i = 0; i < count; ++i) { - IntRect r = quads[i].enclosingBoundingBox(); + FloatRect result; + for (const auto& quad : quads) { + FloatRect r = quad.enclosingBoundingBox(); if (!r.isEmpty()) { - if (obj->style()->hasAppearance()) - obj->theme()->adjustRepaintRect(obj, r); + if (obj->style().hasAppearance()) + obj->theme().adjustRepaintRect(*obj, r); result.unite(r); } } - return result; + return snappedIntRect(LayoutRect(result)); } -bool AccessibilityObject::press() const +bool AccessibilityObject::press() { + // The presence of the actionElement will confirm whether we should even attempt a press. Element* actionElem = actionElement(); if (!actionElem) return false; - if (Frame* f = actionElem->document()->frame()) - f->loader()->resetMultipleFormSubmissionProtection(); + if (Frame* f = actionElem->document().frame()) + f->loader().resetMultipleFormSubmissionProtection(); + + // Hit test at this location to determine if there is a sub-node element that should act + // as the target of the action. + Element* hitTestElement = nullptr; + Document* document = this->document(); + if (document) { + HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); + HitTestResult hitTestResult(clickPoint()); + document->renderView()->hitTest(request, hitTestResult); + if (hitTestResult.innerNode()) { + Node* innerNode = hitTestResult.innerNode()->deprecatedShadowAncestorNode(); + if (is<Element>(*innerNode)) + hitTestElement = downcast<Element>(innerNode); + else if (innerNode) + hitTestElement = innerNode->parentElement(); + } + } + + + // Prefer the actionElement instead of this node, if the actionElement is inside this node. + Element* pressElement = this->element(); + if (!pressElement || actionElem->isDescendantOf(pressElement)) + pressElement = actionElem; + + // Prefer the hit test element, if it is inside the target element. + if (hitTestElement && hitTestElement->isDescendantOf(pressElement)) + pressElement = hitTestElement; + + UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture, document); + + bool dispatchedTouchEvent = dispatchTouchEvent(); + if (!dispatchedTouchEvent) + pressElement->accessKeyAction(true); - UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); - actionElem->accessKeyAction(true); return true; } +bool AccessibilityObject::dispatchTouchEvent() +{ + bool handled = false; +#if ENABLE(IOS_TOUCH_EVENTS) + MainFrame* frame = mainFrame(); + if (!frame) + return false; + + frame->eventHandler().dispatchSimulatedTouchEvent(clickPoint()); +#endif + return handled; +} + +Frame* AccessibilityObject::frame() const +{ + Node* node = this->node(); + if (!node) + return nullptr; + + return node->document().frame(); +} + +MainFrame* AccessibilityObject::mainFrame() const +{ + Document* document = topDocument(); + if (!document) + return nullptr; + + Frame* frame = document->frame(); + if (!frame) + return nullptr; + + return &frame->mainFrame(); +} + +Document* AccessibilityObject::topDocument() const +{ + if (!document()) + return nullptr; + return &document()->topDocument(); +} + String AccessibilityObject::language() const { const AtomicString& lang = getAttribute(langAttr); @@ -609,6 +995,10 @@ VisiblePositionRange AccessibilityObject::visiblePositionRangeForUnorderedPositi if (visiblePos1.isNull() || visiblePos2.isNull()) return VisiblePositionRange(); + // If there's no common tree scope between positions, return early. + if (!commonTreeScope(visiblePos1.deepEquivalent().deprecatedNode(), visiblePos2.deepEquivalent().deprecatedNode())) + return VisiblePositionRange(); + VisiblePosition startPos; VisiblePosition endPos; bool alreadyInOrder; @@ -750,16 +1140,16 @@ static VisiblePosition startOfStyleRange(const VisiblePosition& visiblePos) { RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); RenderObject* startRenderer = renderer; - RenderStyle* style = renderer->style(); + RenderStyle* style = &renderer->style(); // traverse backward by renderer to look for style change for (RenderObject* r = renderer->previousInPreOrder(); r; r = r->previousInPreOrder()) { // skip non-leaf nodes - if (r->firstChild()) + if (r->firstChildSlow()) continue; // stop at style change - if (r->style() != style) + if (&r->style() != style) break; // remember match @@ -773,16 +1163,16 @@ static VisiblePosition endOfStyleRange(const VisiblePosition& visiblePos) { RenderObject* renderer = visiblePos.deepEquivalent().deprecatedNode()->renderer(); RenderObject* endRenderer = renderer; - RenderStyle* style = renderer->style(); + const RenderStyle& style = renderer->style(); // traverse forward by renderer to look for style change for (RenderObject* r = renderer->nextInPreOrder(); r; r = r->nextInPreOrder()) { // skip non-leaf nodes - if (r->firstChild()) + if (r->firstChildSlow()) continue; // stop at style change - if (r->style() != style) + if (&r->style() != &style) break; // remember match @@ -813,15 +1203,22 @@ VisiblePositionRange AccessibilityObject::visiblePositionRangeForRange(const Pla return VisiblePositionRange(startPosition, endPosition); } -static bool replacedNodeNeedsCharacter(Node* replacedNode) +VisiblePositionRange AccessibilityObject::lineRangeForPosition(const VisiblePosition& visiblePosition) const +{ + VisiblePosition startPosition = startOfLine(visiblePosition); + VisiblePosition endPosition = endOfLine(visiblePosition); + return VisiblePositionRange(startPosition, endPosition); +} + +bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode) { // we should always be given a rendered node and a replaced node, but be safe // replaced nodes are either attachments (widgets) or images - if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) + if (!replacedNode || !isRendererReplacedElement(replacedNode->renderer()) || replacedNode->isTextNode()) return false; // create an AX object, but skip it if it is not supposed to be seen - AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode); + AccessibilityObject* object = replacedNode->renderer()->document().axObjectCache()->getOrCreate(replacedNode); if (object->accessibilityIsIgnored()) return false; @@ -833,12 +1230,24 @@ static RenderListItem* renderListItemContainerForNode(Node* node) { for (; node; node = node->parentNode()) { RenderBoxModelObject* renderer = node->renderBoxModelObject(); - if (renderer && renderer->isListItem()) - return toRenderListItem(renderer); + if (is<RenderListItem>(renderer)) + return downcast<RenderListItem>(renderer); } - return 0; + return nullptr; } + +static String listMarkerTextForNode(Node* node) +{ + RenderListItem* listItem = renderListItemContainerForNode(node); + if (!listItem) + return String(); + // If this is in a list item, we need to manually add the text for the list marker + // because a RenderListMarker does not have a Node equivalent and thus does not appear + // when iterating text. + return listItem->markerTextWithSuffix(); +} + // Returns the text associated with a list marker if this node is contained within a list item. String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const { @@ -846,16 +1255,38 @@ String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const V if (!isStartOfLine(visiblePositionStart)) return String(); - RenderListItem* listItem = renderListItemContainerForNode(node); - if (!listItem) - return String(); - - // If this is in a list item, we need to manually add the text for the list marker - // because a RenderListMarker does not have a Node equivalent and thus does not appear - // when iterating text. - return listItem->markerTextWithSuffix(); + return listMarkerTextForNode(node); } + +String AccessibilityObject::stringForRange(RefPtr<Range> range) const +{ + if (!range) + return String(); + + TextIterator it(range.get()); + if (it.atEnd()) + return String(); + + StringBuilder builder; + for (; !it.atEnd(); it.advance()) { + // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) + if (it.text().length()) { + // Add a textual representation for list marker text. + builder.append(listMarkerTextForNode(it.node())); + it.appendTextToStringBuilder(builder); + } else { + // locate the node and starting offset for this replaced range + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + builder.append(objectReplacementCharacter); + } + } + return builder.toString(); +} + String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const { if (visiblePositionRange.isNull()) @@ -865,21 +1296,16 @@ String AccessibilityObject::stringForVisiblePositionRange(const VisiblePositionR RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length()) { - // Add a textual representation for list marker text - String listMarkerText = listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start); - if (!listMarkerText.isEmpty()) - builder.append(listMarkerText); - + if (it.text().length()) { + // Add a textual representation for list marker text. + builder.append(listMarkerTextForNodeAndPosition(it.node(), visiblePositionRange.start)); it.appendTextToStringBuilder(builder); } else { // locate the node and starting offset for this replaced range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); - - if (replacedNodeNeedsCharacter(node->childNode(offset))) + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) builder.append(objectReplacementCharacter); } } @@ -897,23 +1323,54 @@ int AccessibilityObject::lengthForVisiblePositionRange(const VisiblePositionRang RefPtr<Range> range = makeRange(visiblePositionRange.start, visiblePositionRange.end); for (TextIterator it(range.get()); !it.atEnd(); it.advance()) { // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length()) - length += it.length(); + if (it.text().length()) + length += it.text().length(); else { // locate the node and starting offset for this replaced range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); + Node& node = it.range()->startContainer(); + ASSERT(&node == &it.range()->endContainer()); + int offset = it.range()->startOffset(); - if (replacedNodeNeedsCharacter(node->childNode(offset))) - length++; + if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset))) + ++length; } } return length; } +VisiblePosition AccessibilityObject::visiblePositionForBounds(const IntRect& rect, AccessibilityVisiblePositionForBounds visiblePositionForBounds) const +{ + if (rect.isEmpty()) + return VisiblePosition(); + + MainFrame* mainFrame = this->mainFrame(); + if (!mainFrame) + return VisiblePosition(); + + // FIXME: Add support for right-to-left languages. + IntPoint corner = (visiblePositionForBounds == FirstVisiblePositionForBounds) ? rect.minXMinYCorner() : rect.maxXMaxYCorner(); + VisiblePosition position = mainFrame->visiblePositionForPoint(corner); + + if (rect.contains(position.absoluteCaretBounds().center())) + return position; + + // If the initial position is located outside the bounds adjust it incrementally as needed. + VisiblePosition nextPosition = position.next(); + VisiblePosition previousPosition = position.previous(); + while (nextPosition.isNotNull() || previousPosition.isNotNull()) { + if (rect.contains(nextPosition.absoluteCaretBounds().center())) + return nextPosition; + if (rect.contains(previousPosition.absoluteCaretBounds().center())) + return previousPosition; + + nextPosition = nextPosition.next(); + previousPosition = previousPosition.previous(); + } + + return VisiblePosition(); +} + VisiblePosition AccessibilityObject::nextWordEnd(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) @@ -1065,15 +1522,66 @@ VisiblePosition AccessibilityObject::previousParagraphStartPosition(const Visibl AccessibilityObject* AccessibilityObject::accessibilityObjectForPosition(const VisiblePosition& visiblePos) const { if (visiblePos.isNull()) - return 0; + return nullptr; RenderObject* obj = visiblePos.deepEquivalent().deprecatedNode()->renderer(); if (!obj) - return 0; + return nullptr; - return obj->document()->axObjectCache()->getOrCreate(obj); + return obj->document().axObjectCache()->getOrCreate(obj); +} + +// If you call node->hasEditableStyle() since that will return true if an ancestor is editable. +// This only returns true if this is the element that actually has the contentEditable attribute set. +bool AccessibilityObject::hasContentEditableAttributeSet() const +{ + return contentEditableAttributeIsEnabled(element()); +} + +bool AccessibilityObject::supportsARIAReadOnly() const +{ + AccessibilityRole role = roleValue(); + + return role == CheckBoxRole + || role == ColumnHeaderRole + || role == ComboBoxRole + || role == GridRole + || role == GridCellRole + || role == ListBoxRole + || role == MenuItemCheckboxRole + || role == MenuItemRadioRole + || role == RadioGroupRole + || role == RowHeaderRole + || role == SearchFieldRole + || role == SliderRole + || role == SpinButtonRole + || role == SwitchRole + || role == TextFieldRole + || role == TreeGridRole + || isPasswordField(); } +String AccessibilityObject::ariaReadOnlyValue() const +{ + if (!hasAttribute(aria_readonlyAttr)) + return ariaRoleAttribute() != UnknownRole && supportsARIAReadOnly() ? "false" : String(); + + return getAttribute(aria_readonlyAttr).string().convertToASCIILowercase(); +} + +bool AccessibilityObject::contentEditableAttributeIsEnabled(Element* element) +{ + if (!element) + return false; + + const AtomicString& contentEditableValue = element->fastGetAttribute(contenteditableAttr); + if (contentEditableValue.isNull()) + return false; + + // Both "true" (case-insensitive) and the empty string count as true. + return contentEditableValue.isEmpty() || equalLettersIgnoringASCIICase(contentEditableValue, "true"); +} + #if HAVE(ACCESSIBILITY) int AccessibilityObject::lineForPosition(const VisiblePosition& visiblePos) const { @@ -1148,25 +1656,41 @@ unsigned AccessibilityObject::doAXLineForIndex(unsigned index) void AccessibilityObject::updateBackingStore() { // Updating the layout may delete this object. - if (Document* document = this->document()) - document->updateLayoutIgnorePendingStylesheets(); + RefPtr<AccessibilityObject> protector(this); + + if (Document* document = this->document()) { + if (!document->view()->isInRenderTreeLayout()) + document->updateLayoutIgnorePendingStylesheets(); + } + + updateChildrenIfNecessary(); } #endif - + +ScrollView* AccessibilityObject::scrollViewAncestor() const +{ + for (const AccessibilityObject* scrollParent = this; scrollParent; scrollParent = scrollParent->parentObject()) { + if (is<AccessibilityScrollView>(*scrollParent)) + return downcast<AccessibilityScrollView>(*scrollParent).scrollView(); + } + + return nullptr; +} + Document* AccessibilityObject::document() const { FrameView* frameView = documentFrameView(); if (!frameView) - return 0; + return nullptr; - return frameView->frame()->document(); + return frameView->frame().document(); } Page* AccessibilityObject::page() const { Document* document = this->document(); if (!document) - return 0; + return nullptr; return document->page(); } @@ -1177,15 +1701,16 @@ FrameView* AccessibilityObject::documentFrameView() const object = object->parentObject(); if (!object) - return 0; + return nullptr; return object->documentFrameView(); } #if HAVE(ACCESSIBILITY) -const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children() +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool updateChildrenIfNeeded) { - updateChildrenIfNecessary(); + if (updateChildrenIfNeeded) + updateChildrenIfNecessary(); return m_children; } @@ -1193,16 +1718,18 @@ const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::chi void AccessibilityObject::updateChildrenIfNecessary() { - if (!hasChildren()) - addChildren(); + if (!hasChildren()) { + // Enable the cache in case we end up adding a lot of children, we don't want to recompute axIsIgnored each time. + AXAttributeCacheEnabler enableCache(axObjectCache()); + addChildren(); + } } void AccessibilityObject::clearChildren() { // Some objects have weak pointers to their parents and those associations need to be detached. - size_t length = m_children.size(); - for (size_t i = 0; i < length; i++) - m_children[i]->detachFromParent(); + for (const auto& child : m_children) + child->detachFromParent(); m_children.clear(); m_haveChildren = false; @@ -1212,58 +1739,62 @@ AccessibilityObject* AccessibilityObject::anchorElementForNode(Node* node) { RenderObject* obj = node->renderer(); if (!obj) - return 0; + return nullptr; - RefPtr<AccessibilityObject> axObj = obj->document()->axObjectCache()->getOrCreate(obj); + RefPtr<AccessibilityObject> axObj = obj->document().axObjectCache()->getOrCreate(obj); Element* anchor = axObj->anchorElement(); if (!anchor) - return 0; + return nullptr; RenderObject* anchorRenderer = anchor->renderer(); if (!anchorRenderer) - return 0; + return nullptr; - return anchorRenderer->document()->axObjectCache()->getOrCreate(anchorRenderer); + return anchorRenderer->document().axObjectCache()->getOrCreate(anchorRenderer); } + +AccessibilityObject* AccessibilityObject::headingElementForNode(Node* node) +{ + if (!node) + return nullptr; + + RenderObject* renderObject = node->renderer(); + if (!renderObject) + return nullptr; + AccessibilityObject* axObject = renderObject->document().axObjectCache()->getOrCreate(renderObject); + for (; axObject && axObject->roleValue() != HeadingRole; axObject = axObject->parentObject()) { } + + return axObject; +} + void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result) { - AccessibilityChildrenVector axChildren = children(); - unsigned count = axChildren.size(); - for (unsigned k = 0; k < count; ++k) { - AccessibilityObject* obj = axChildren[k].get(); - + for (const auto& child : children()) { // Add tree items as the rows. - if (obj->roleValue() == TreeItemRole) - result.append(obj); + if (child->roleValue() == TreeItemRole) + result.append(child); // Now see if this item also has rows hiding inside of it. - obj->ariaTreeRows(result); + child->ariaTreeRows(result); } } void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result) { // The ARIA tree item content are the item that are not other tree items or their containing groups. - AccessibilityChildrenVector axChildren = children(); - unsigned count = axChildren.size(); - for (unsigned k = 0; k < count; ++k) { - AccessibilityObject* obj = axChildren[k].get(); - AccessibilityRole role = obj->roleValue(); + for (const auto& child : children()) { + AccessibilityRole role = child->roleValue(); if (role == TreeItemRole || role == GroupRole) continue; - result.append(obj); + result.append(child); } } void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result) { - AccessibilityChildrenVector axChildren = children(); - unsigned count = axChildren.size(); - for (unsigned k = 0; k < count; ++k) { - AccessibilityObject* obj = axChildren[k].get(); - + for (const auto& obj : children()) { // Add tree items as the rows. if (obj->roleValue() == TreeItemRole) result.append(obj); @@ -1272,21 +1803,38 @@ void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& obj->ariaTreeRows(result); } } - + +const String AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityRole role) +{ + switch (role) { + case ApplicationAlertDialogRole: + case ApplicationAlertRole: + return ASCIILiteral("assertive"); + case ApplicationLogRole: + case ApplicationStatusRole: + return ASCIILiteral("polite"); + case ApplicationTimerRole: + case ApplicationMarqueeRole: + return ASCIILiteral("off"); + default: + return nullAtom; + } +} + #if HAVE(ACCESSIBILITY) const String& AccessibilityObject::actionVerb() const { +#if !PLATFORM(IOS) // FIXME: Need to add verbs for select elements. - DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); - DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); - DEFINE_STATIC_LOCAL(const String, menuListAction, (AXMenuListActionVerb())); - DEFINE_STATIC_LOCAL(const String, menuListPopupAction, (AXMenuListPopupActionVerb())); - DEFINE_STATIC_LOCAL(const String, listItemAction, (AXListItemActionVerb())); - DEFINE_STATIC_LOCAL(const String, noAction, ()); + static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); + static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); + static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); + static NeverDestroyed<const String> checkedCheckBoxAction(AXCheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); + static NeverDestroyed<const String> menuListAction(AXMenuListActionVerb()); + static NeverDestroyed<const String> menuListPopupAction(AXMenuListPopupActionVerb()); + static NeverDestroyed<const String> listItemAction(AXListItemActionVerb()); switch (roleValue()) { case ButtonRole: @@ -1298,6 +1846,7 @@ const String& AccessibilityObject::actionVerb() const case RadioButtonRole: return radioButtonAction; case CheckBoxRole: + case SwitchRole: return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; case LinkRole: case WebCoreLinkRole: @@ -1309,54 +1858,126 @@ const String& AccessibilityObject::actionVerb() const case ListItemRole: return listItemAction; default: - return noAction; + return nullAtom; } +#else + return nullAtom; +#endif } #endif bool AccessibilityObject::ariaIsMultiline() const { - return equalIgnoringCase(getAttribute(aria_multilineAttr), "true"); + return equalLettersIgnoringASCIICase(getAttribute(aria_multilineAttr), "true"); } -const AtomicString& AccessibilityObject::invalidStatus() const +String AccessibilityObject::invalidStatus() const +{ + String grammarValue = ASCIILiteral("grammar"); + String falseValue = ASCIILiteral("false"); + String spellingValue = ASCIILiteral("spelling"); + String trueValue = ASCIILiteral("true"); + String undefinedValue = ASCIILiteral("undefined"); + + // aria-invalid can return false (default), grammar, spelling, or true. + String ariaInvalid = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_invalidAttr)); + + // If "false", "undefined" [sic, string value], empty, or missing, return "false". + if (ariaInvalid.isEmpty() || ariaInvalid == falseValue || ariaInvalid == undefinedValue) + return falseValue; + // Besides true/false/undefined, the only tokens defined by WAI-ARIA 1.0... + // ...for @aria-invalid are "grammar" and "spelling". + if (ariaInvalid == grammarValue) + return grammarValue; + if (ariaInvalid == spellingValue) + return spellingValue; + // Any other non empty string should be treated as "true". + return trueValue; +} + +AccessibilityARIACurrentState AccessibilityObject::ariaCurrentState() const { - DEFINE_STATIC_LOCAL(const AtomicString, invalidStatusFalse, ("false", AtomicString::ConstructFromLiteral)); + // aria-current can return false (default), true, page, step, location, date or time. + String currentStateValue = stripLeadingAndTrailingHTMLSpaces(getAttribute(aria_currentAttr)); - // aria-invalid can return false (default), grammer, spelling, or true. - const AtomicString& ariaInvalid = getAttribute(aria_invalidAttr); + // If "false", empty, or missing, return false state. + if (currentStateValue.isEmpty() || currentStateValue == "false") + return ARIACurrentFalse; - // If empty or not present, it should return false. - if (ariaInvalid.isEmpty()) - return invalidStatusFalse; + if (currentStateValue == "page") + return ARIACurrentPage; + if (currentStateValue == "step") + return ARIACurrentStep; + if (currentStateValue == "location") + return ARIACurrentLocation; + if (currentStateValue == "date") + return ARIACurrentDate; + if (currentStateValue == "time") + return ARIACurrentTime; - return ariaInvalid; + // Any value not included in the list of allowed values should be treated as "true". + return ARIACurrentTrue; } - -bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const + +bool AccessibilityObject::isAriaModalDescendant(Node* ariaModalNode) const { - Node* elementNode = node(); - if (!elementNode) + if (!ariaModalNode || !this->element()) + return false; + + if (this->element() == ariaModalNode) + return true; + + // ARIA 1.1 aria-modal, indicates whether an element is modal when displayed. + // For the decendants of the modal object, they should also be considered as aria-modal=true. + for (auto& ancestor : elementAncestors(this->element())) { + if (&ancestor == ariaModalNode) + return true; + } + return false; +} + +bool AccessibilityObject::ignoredFromARIAModalPresence() const +{ + // We shouldn't ignore the top node. + if (!node() || !node()->parentNode()) + return false; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return false; + + // ariaModalNode is the current displayed modal dialog. + Node* ariaModalNode = cache->ariaModalNode(); + if (!ariaModalNode) return false; - if (!elementNode->isElementNode()) + // We only want to ignore the objects within the same frame as the modal dialog. + if (ariaModalNode->document().frame() != this->frame()) return false; - Element* element = toElement(elementNode); - return element->fastHasAttribute(attribute); + return !isAriaModalDescendant(ariaModalNode); +} + +bool AccessibilityObject::hasTagName(const QualifiedName& tagName) const +{ + Node* node = this->node(); + return is<Element>(node) && downcast<Element>(*node).hasTagName(tagName); } -const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const +bool AccessibilityObject::hasAttribute(const QualifiedName& attribute) const { - Node* elementNode = node(); - if (!elementNode) - return nullAtom; + Node* node = this->node(); + if (!is<Element>(node)) + return false; - if (!elementNode->isElementNode()) - return nullAtom; + return downcast<Element>(*node).fastHasAttribute(attribute); +} - Element* element = toElement(elementNode); - return element->fastGetAttribute(attribute); +const AtomicString& AccessibilityObject::getAttribute(const QualifiedName& attribute) const +{ + if (Element* element = this->element()) + return element->fastGetAttribute(attribute); + return nullAtom; } // Lacking concrete evidence of orientation, horizontal means width > height. vertical is height > width; @@ -1368,8 +1989,7 @@ AccessibilityOrientation AccessibilityObject::orientation() const if (bounds.size().height() > bounds.size().width()) return AccessibilityOrientationVertical; - // A tie goes to horizontal. - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationUndefined; } bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const @@ -1398,18 +2018,26 @@ AccessibilityObject* AccessibilityObject::firstAnonymousBlockChild() const if (child->renderer() && child->renderer()->isAnonymousBlock()) return child; } - return 0; + return nullptr; } -typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; +typedef HashMap<String, AccessibilityRole, ASCIICaseInsensitiveHash> ARIARoleMap; +typedef HashMap<AccessibilityRole, String, DefaultHash<int>::Hash, WTF::UnsignedWithZeroKeyHashTraits<int>> ARIAReverseRoleMap; + +static ARIARoleMap* gAriaRoleMap = nullptr; +static ARIAReverseRoleMap* gAriaReverseRoleMap = nullptr; struct RoleEntry { String ariaRole; AccessibilityRole webcoreRole; }; -static ARIARoleMap* createARIARoleMap() +static void initializeRoleMap() { + if (gAriaRoleMap) + return; + ASSERT(!gAriaReverseRoleMap); + const RoleEntry roles[] = { { "alert", ApplicationAlertRole }, { "alertdialog", ApplicationAlertDialogRole }, @@ -1422,12 +2050,15 @@ static ARIARoleMap* createARIARoleMap() { "contentinfo", LandmarkContentInfoRole }, { "dialog", ApplicationDialogRole }, { "directory", DirectoryRole }, - { "grid", TableRole }, - { "gridcell", CellRole }, + { "grid", GridRole }, + { "gridcell", GridCellRole }, + { "table", TableRole }, + { "cell", CellRole }, { "columnheader", ColumnHeaderRole }, { "combobox", ComboBoxRole }, { "definition", DefinitionRole }, { "document", DocumentRole }, + { "form", FormRole }, { "rowheader", RowHeaderRole }, { "group", GroupRole }, { "heading", HeadingRole }, @@ -1444,8 +2075,9 @@ static ARIARoleMap* createARIARoleMap() { "menu", MenuRole }, { "menubar", MenuBarRole }, { "menuitem", MenuItemRole }, - { "menuitemcheckbox", MenuItemRole }, - { "menuitemradio", MenuItemRole }, + { "menuitemcheckbox", MenuItemCheckboxRole }, + { "menuitemradio", MenuItemRadioRole }, + { "none", PresentationalRole }, { "note", DocumentNoteRole }, { "navigation", LandmarkNavigationRole }, { "option", ListBoxOptionRole }, @@ -1455,12 +2087,15 @@ static ARIARoleMap* createARIARoleMap() { "radiogroup", RadioGroupRole }, { "region", DocumentRegionRole }, { "row", RowRole }, + { "rowgroup", RowGroupRole }, { "scrollbar", ScrollBarRole }, { "search", LandmarkSearchRole }, + { "searchbox", SearchFieldRole }, { "separator", SplitterRole }, { "slider", SliderRole }, { "spinbutton", SpinButtonRole }, { "status", ApplicationStatusRole }, + { "switch", SwitchRole }, { "tab", TabRole }, { "tablist", TabListRole }, { "tabpanel", TabPanelRole }, @@ -1473,26 +2108,37 @@ static ARIARoleMap* createARIARoleMap() { "treegrid", TreeGridRole }, { "treeitem", TreeItemRole } }; - ARIARoleMap* roleMap = new ARIARoleMap; - for (size_t i = 0; i < WTF_ARRAY_LENGTH(roles); ++i) - roleMap->set(roles[i].ariaRole, roles[i].webcoreRole); - return roleMap; + gAriaRoleMap = new ARIARoleMap; + gAriaReverseRoleMap = new ARIAReverseRoleMap; + size_t roleLength = WTF_ARRAY_LENGTH(roles); + for (size_t i = 0; i < roleLength; ++i) { + gAriaRoleMap->set(roles[i].ariaRole, roles[i].webcoreRole); + gAriaReverseRoleMap->set(roles[i].webcoreRole, roles[i].ariaRole); + } +} + +static ARIARoleMap& ariaRoleMap() +{ + initializeRoleMap(); + return *gAriaRoleMap; +} + +static ARIAReverseRoleMap& reverseAriaRoleMap() +{ + initializeRoleMap(); + return *gAriaReverseRoleMap; } AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value) { ASSERT(!value.isEmpty()); - static const ARIARoleMap* roleMap = createARIARoleMap(); - Vector<String> roleVector; value.split(' ', roleVector); AccessibilityRole role = UnknownRole; - unsigned size = roleVector.size(); - for (unsigned i = 0; i < size; ++i) { - String roleName = roleVector[i]; - role = roleMap->get(roleName); + for (const auto& roleName : roleVector) { + role = ariaRoleMap().get(roleName); if (role) return role; } @@ -1500,6 +2146,16 @@ AccessibilityRole AccessibilityObject::ariaRoleToWebCoreRole(const String& value return role; } +String AccessibilityObject::computedRoleString() const +{ + // FIXME: Need a few special cases that aren't in the RoleMap: option, etc. http://webkit.org/b/128296 + AccessibilityRole role = roleValue(); + if (role == HorizontalRuleRole) + role = SplitterRole; + + return reverseAriaRoleMap().get(role); +} + bool AccessibilityObject::hasHighlighting() const { for (Node* node = this->node(); node; node = node->parentNode()) { @@ -1510,8 +2166,77 @@ bool AccessibilityObject::hasHighlighting() const return false; } +const AtomicString& AccessibilityObject::roleDescription() const +{ + return getAttribute(aria_roledescriptionAttr); +} + +static bool nodeHasPresentationRole(Node* node) +{ + return nodeHasRole(node, "presentation") || nodeHasRole(node, "none"); +} + +bool AccessibilityObject::supportsPressAction() const +{ + if (isButton()) + return true; + if (roleValue() == DetailsRole) + return true; + + Element* actionElement = this->actionElement(); + if (!actionElement) + return false; + + // [Bug: 136247] Heuristic: element handlers that have more than one accessible descendant should not be exposed as supporting press. + if (actionElement != element()) { + if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(actionElement)) { + AccessibilityChildrenVector results; + // Search within for immediate descendants that are static text. If we find more than one + // then this is an event delegator actionElement and we should expose the press action. + Vector<AccessibilitySearchKey> keys({ StaticTextSearchKey, ControlSearchKey, GraphicSearchKey, HeadingSearchKey, LinkSearchKey }); + AccessibilitySearchCriteria criteria(axObj, SearchDirectionNext, "", 2, false, false); + criteria.searchKeys = keys; + axObj->findMatchingObjects(&criteria, results); + if (results.size() > 1) + return false; + } + } + + // [Bug: 133613] Heuristic: If the action element is presentational, we shouldn't expose press as a supported action. + return !nodeHasPresentationRole(actionElement); +} + +bool AccessibilityObject::supportsDatetimeAttribute() const +{ + return hasTagName(insTag) || hasTagName(delTag) || hasTagName(timeTag); +} + +Element* AccessibilityObject::element() const +{ + Node* node = this->node(); + if (is<Element>(node)) + return downcast<Element>(node); + return nullptr; +} + +bool AccessibilityObject::isValueAutofilled() const +{ + if (!isNativeTextControl()) + return false; + + Node* node = this->node(); + if (!node || !is<HTMLInputElement>(*node)) + return false; + + return downcast<HTMLInputElement>(*node).isAutoFilled(); +} + const AtomicString& AccessibilityObject::placeholderValue() const { + const AtomicString& ariaPlaceholder = getAttribute(aria_placeholderAttr); + if (!ariaPlaceholder.isEmpty()) + return ariaPlaceholder; + const AtomicString& placeholder = getAttribute(placeholderAttr); if (!placeholder.isEmpty()) return placeholder; @@ -1534,18 +2259,32 @@ bool AccessibilityObject::isInsideARIALiveRegion() const bool AccessibilityObject::supportsARIAAttributes() const { + // This returns whether the element supports any global ARIA attributes. return supportsARIALiveRegion() || supportsARIADragging() || supportsARIADropping() || supportsARIAFlowTo() || supportsARIAOwns() - || hasAttribute(aria_labelAttr); + || hasAttribute(aria_atomicAttr) + || hasAttribute(aria_busyAttr) + || hasAttribute(aria_controlsAttr) + || hasAttribute(aria_describedbyAttr) + || hasAttribute(aria_disabledAttr) + || hasAttribute(aria_haspopupAttr) + || hasAttribute(aria_invalidAttr) + || hasAttribute(aria_labelAttr) + || hasAttribute(aria_labelledbyAttr) + || hasAttribute(aria_relevantAttr); +} + +bool AccessibilityObject::liveRegionStatusIsEnabled(const AtomicString& liveRegionStatus) +{ + return equalLettersIgnoringASCIICase(liveRegionStatus, "polite") || equalLettersIgnoringASCIICase(liveRegionStatus, "assertive"); } bool AccessibilityObject::supportsARIALiveRegion() const { - const AtomicString& liveRegion = ariaLiveRegionStatus(); - return equalIgnoringCase(liveRegion, "polite") || equalIgnoringCase(liveRegion, "assertive"); + return liveRegionStatusIsEnabled(ariaLiveRegionStatus()); } AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntPoint& point) const @@ -1554,15 +2293,16 @@ AccessibilityObject* AccessibilityObject::elementAccessibilityHitTest(const IntP if (isAttachment()) { Widget* widget = widgetForAttachmentView(); // Normalize the point for the widget's bounds. - if (widget && widget->isFrameView()) - return axObjectCache()->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); + if (widget && widget->isFrameView()) { + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(widget)->accessibilityHitTest(IntPoint(point - widget->frameRect().location())); + } } // Check if there are any mock elements that need to be handled. - size_t count = m_children.size(); - for (size_t k = 0; k < count; k++) { - if (m_children[k]->isMockObject() && m_children[k]->elementRect().contains(point)) - return m_children[k]->elementAccessibilityHitTest(point); + for (const auto& child : m_children) { + if (child->isMockObject() && child->elementRect().contains(point)) + return child->elementAccessibilityHitTest(point); } return const_cast<AccessibilityObject*>(this); @@ -1573,18 +2313,18 @@ AXObjectCache* AccessibilityObject::axObjectCache() const Document* doc = document(); if (doc) return doc->axObjectCache(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityObject::focusedUIElement() const { Document* doc = document(); if (!doc) - return 0; + return nullptr; Page* page = doc->page(); if (!page) - return 0; + return nullptr; return AXObjectCache::focusedUIElementForPage(page); } @@ -1592,10 +2332,12 @@ AccessibilityObject* AccessibilityObject::focusedUIElement() const AccessibilitySortDirection AccessibilityObject::sortDirection() const { const AtomicString& sortAttribute = getAttribute(aria_sortAttr); - if (equalIgnoringCase(sortAttribute, "ascending")) + if (equalLettersIgnoringASCIICase(sortAttribute, "ascending")) return SortDirectionAscending; - if (equalIgnoringCase(sortAttribute, "descending")) + if (equalLettersIgnoringASCIICase(sortAttribute, "descending")) return SortDirectionDescending; + if (equalLettersIgnoringASCIICase(sortAttribute, "other")) + return SortDirectionOther; return SortDirectionNone; } @@ -1628,28 +2370,98 @@ int AccessibilityObject::ariaPosInSet() const return getAttribute(aria_posinsetAttr).toInt(); } -bool AccessibilityObject::supportsARIAExpanded() const +String AccessibilityObject::identifierAttribute() const +{ + return getAttribute(idAttr); +} + +void AccessibilityObject::classList(Vector<String>& classList) const { - return !getAttribute(aria_expandedAttr).isEmpty(); + Node* node = this->node(); + if (!is<Element>(node)) + return; + + Element* element = downcast<Element>(node); + DOMTokenList& list = element->classList(); + unsigned length = list.length(); + for (unsigned k = 0; k < length; k++) + classList.append(list.item(k).string()); +} + +bool AccessibilityObject::supportsARIAPressed() const +{ + const AtomicString& expanded = getAttribute(aria_pressedAttr); + return equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false"); +} + +bool AccessibilityObject::supportsExpanded() const +{ + // Undefined values should not result in this attribute being exposed to ATs according to ARIA. + const AtomicString& expanded = getAttribute(aria_expandedAttr); + if (equalLettersIgnoringASCIICase(expanded, "true") || equalLettersIgnoringASCIICase(expanded, "false")) + return true; + switch (roleValue()) { + case ComboBoxRole: + case DisclosureTriangleRole: + case DetailsRole: + return true; + default: + return false; + } } bool AccessibilityObject::isExpanded() const { - if (equalIgnoringCase(getAttribute(aria_expandedAttr), "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_expandedAttr), "true")) return true; + if (is<HTMLDetailsElement>(node())) + return downcast<HTMLDetailsElement>(node())->isOpen(); + return false; } - + +bool AccessibilityObject::supportsChecked() const +{ + switch (roleValue()) { + case CheckBoxRole: + case MenuItemCheckboxRole: + case MenuItemRadioRole: + case RadioButtonRole: + case SwitchRole: + return true; + default: + return false; + } +} + AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const { // If this is a real checkbox or radio button, AccessibilityRenderObject will handle. - // If it's an ARIA checkbox or radio, the aria-checked attribute should be used. - + // If it's an ARIA checkbox, radio, or switch the aria-checked attribute should be used. + // If it's a toggle button, the aria-pressed attribute is consulted. + + if (isToggleButton()) { + const AtomicString& ariaPressed = getAttribute(aria_pressedAttr); + if (equalLettersIgnoringASCIICase(ariaPressed, "true")) + return ButtonStateOn; + if (equalLettersIgnoringASCIICase(ariaPressed, "mixed")) + return ButtonStateMixed; + return ButtonStateOff; + } + const AtomicString& result = getAttribute(aria_checkedAttr); - if (equalIgnoringCase(result, "true")) + if (equalLettersIgnoringASCIICase(result, "true")) return ButtonStateOn; - if (equalIgnoringCase(result, "mixed")) + if (equalLettersIgnoringASCIICase(result, "mixed")) { + // ARIA says that radio, menuitemradio, and switch elements must NOT expose button state mixed. + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == RadioButtonRole || ariaRole == MenuItemRadioRole || ariaRole == SwitchRole) + return ButtonStateOff; + return ButtonStateMixed; + } + + if (equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true")) return ButtonStateMixed; return ButtonStateOff; @@ -1660,71 +2472,89 @@ AccessibilityButtonState AccessibilityObject::checkboxOrRadioValue() const // logic is the same. The goal is to compute the best scroll offset // in order to make an object visible within a viewport. // +// If the object is already fully visible, returns the same scroll +// offset. +// // In case the whole object cannot fit, you can specify a // subfocus - a smaller region within the object that should // be prioritized. If the whole object can fit, the subfocus is // ignored. // -// Example: the viewport is scrolled to the right just enough -// that the object is in view. +// If possible, the object and subfocus are centered within the +// viewport. +// +// Example 1: the object is already visible, so nothing happens. +// +----------Viewport---------+ +// +---Object---+ +// +--SubFocus--+ +// +// Example 2: the object is not fully visible, so it's centered +// within the viewport. // Before: // +----------Viewport---------+ // +---Object---+ // +--SubFocus--+ // // After: -// +----------Viewport---------+ +// +----------Viewport---------+ // +---Object---+ // +--SubFocus--+ // +// Example 3: the object is larger than the viewport, so the +// viewport moves to show as much of the object as possible, +// while also trying to center the subfocus. +// Before: +// +----------Viewport---------+ +// +---------------Object--------------+ +// +-SubFocus-+ +// +// After: +// +----------Viewport---------+ +// +---------------Object--------------+ +// +-SubFocus-+ +// // When constraints cannot be fully satisfied, the min // (left/top) position takes precedence over the max (right/bottom). // // Note that the return value represents the ideal new scroll offset. // This may be out of range - the calling function should clip this // to the available range. -static int computeBestScrollOffset(int currentScrollOffset, - int subfocusMin, int subfocusMax, - int objectMin, int objectMax, - int viewportMin, int viewportMax) { +static int computeBestScrollOffset(int currentScrollOffset, int subfocusMin, int subfocusMax, int objectMin, int objectMax, int viewportMin, int viewportMax) +{ int viewportSize = viewportMax - viewportMin; - - // If the focus size is larger than the viewport size, shrink it in the - // direction of subfocus. + + // If the object size is larger than the viewport size, consider + // only a portion that's as large as the viewport, centering on + // the subfocus as much as possible. if (objectMax - objectMin > viewportSize) { - // Subfocus must be within focus: + // Since it's impossible to fit the whole object in the + // viewport, exit now if the subfocus is already within the viewport. + if (subfocusMin - currentScrollOffset >= viewportMin && subfocusMax - currentScrollOffset <= viewportMax) + return currentScrollOffset; + + // Subfocus must be within focus. subfocusMin = std::max(subfocusMin, objectMin); subfocusMax = std::min(subfocusMax, objectMax); - + // Subfocus must be no larger than the viewport size; favor top/left. if (subfocusMax - subfocusMin > viewportSize) subfocusMax = subfocusMin + viewportSize; + + // Compute the size of an object centered on the subfocus, the size of the viewport. + int centeredObjectMin = (subfocusMin + subfocusMax - viewportSize) / 2; + int centeredObjectMax = centeredObjectMin + viewportSize; - if (subfocusMin + viewportSize > objectMax) - objectMin = objectMax - viewportSize; - else { - objectMin = subfocusMin; - objectMax = subfocusMin + viewportSize; - } + objectMin = std::max(objectMin, centeredObjectMin); + objectMax = std::min(objectMax, centeredObjectMax); } // Exit now if the focus is already within the viewport. if (objectMin - currentScrollOffset >= viewportMin && objectMax - currentScrollOffset <= viewportMax) return currentScrollOffset; - - // Scroll left if we're too far to the right. - if (objectMax - currentScrollOffset > viewportMax) - return objectMax - viewportMax; - - // Scroll right if we're too far to the left. - if (objectMin - currentScrollOffset < viewportMin) - return objectMin - viewportMin; - - ASSERT_NOT_REACHED(); - - // This shouldn't happen. - return currentScrollOffset; + + // Center the object in the viewport. + return (objectMin + objectMax - viewportMin - viewportMax) / 2; } bool AccessibilityObject::isOnscreen() const @@ -1748,8 +2578,9 @@ bool AccessibilityObject::isOnscreen() const for (size_t i = levels; i >= 1; i--) { const AccessibilityObject* outer = objects[i]; const AccessibilityObject* inner = objects[i - 1]; - const IntRect outerRect = i < levels ? pixelSnappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(); - const IntRect innerRect = pixelSnappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect()); + // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. + const IntRect outerRect = i < levels ? snappedIntRect(outer->boundingBoxRect()) : outer->getScrollableAreaIfScrollable()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + const IntRect innerRect = snappedIntRect(inner->isAccessibilityScrollView() ? inner->parentObject()->boundingBoxRect() : inner->boundingBoxRect()); if (!outerRect.intersects(innerRect)) { isOnscreen = false; @@ -1762,7 +2593,7 @@ bool AccessibilityObject::isOnscreen() const void AccessibilityObject::scrollToMakeVisible() const { - IntRect objectRect = pixelSnappedIntRect(boundingBoxRect()); + IntRect objectRect = snappedIntRect(boundingBoxRect()); objectRect.setLocation(IntPoint()); scrollToMakeVisibleWithSubFocus(objectRect); } @@ -1772,7 +2603,7 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu // Search up the parent chain until we find the first one that's scrollable. AccessibilityObject* scrollParent = parentObject(); ScrollableArea* scrollableArea; - for (scrollableArea = 0; + for (scrollableArea = nullptr; scrollParent && !(scrollableArea = scrollParent->getScrollableAreaIfScrollable()); scrollParent = scrollParent->parentObject()) { } if (!scrollableArea) @@ -1780,8 +2611,14 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu LayoutRect objectRect = boundingBoxRect(); IntPoint scrollPosition = scrollableArea->scrollPosition(); - IntRect scrollVisibleRect = scrollableArea->visibleContentRect(); + // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. + IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + if (!scrollParent->isScrollView()) { + objectRect.moveBy(scrollPosition); + objectRect.moveBy(-snappedIntRect(scrollParent->elementRect()).location()); + } + int desiredX = computeBestScrollOffset( scrollPosition.x(), objectRect.x() + subfocus.x(), objectRect.x() + subfocus.maxX(), @@ -1795,9 +2632,16 @@ void AccessibilityObject::scrollToMakeVisibleWithSubFocus(const IntRect& subfocu scrollParent->scrollTo(IntPoint(desiredX, desiredY)); + // Convert the subfocus into the coordinates of the scroll parent. + IntRect newSubfocus = subfocus; + IntRect newElementRect = snappedIntRect(elementRect()); + IntRect scrollParentRect = snappedIntRect(scrollParent->elementRect()); + newSubfocus.move(newElementRect.x(), newElementRect.y()); + newSubfocus.move(-scrollParentRect.x(), -scrollParentRect.y()); + // Recursively make sure the scroll parent itself is visible. if (scrollParent->parentObject()) - scrollParent->scrollToMakeVisible(); + scrollParent->scrollToMakeVisibleWithSubFocus(newSubfocus); } void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const @@ -1862,6 +2706,102 @@ void AccessibilityObject::scrollToGlobalPoint(const IntPoint& globalPoint) const } } } + +void AccessibilityObject::scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>& scrollers) const +{ + // Search up the parent chain until we find the first one that's scrollable. + scrollers.first = nullptr; + for (scrollers.second = parentObject(); scrollers.second; scrollers.second = scrollers.second->parentObject()) { + if ((scrollers.first = scrollers.second->getScrollableAreaIfScrollable())) + break; + } +} + +ScrollableArea* AccessibilityObject::scrollableAreaAncestor() const +{ + std::pair<ScrollableArea*, AccessibilityObject*> scrollers; + scrollAreaAndAncestor(scrollers); + return scrollers.first; +} + +IntPoint AccessibilityObject::scrollPosition() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->scrollPosition(); + + return IntPoint(); +} + +IntRect AccessibilityObject::scrollVisibleContentRect() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + + return IntRect(); +} + +IntSize AccessibilityObject::scrollContentsSize() const +{ + if (auto scroller = scrollableAreaAncestor()) + return scroller->contentsSize(); + + return IntSize(); +} + +bool AccessibilityObject::scrollByPage(ScrollByPageDirection direction) const +{ + std::pair<ScrollableArea*, AccessibilityObject*> scrollers; + scrollAreaAndAncestor(scrollers); + ScrollableArea* scrollableArea = scrollers.first; + AccessibilityObject* scrollParent = scrollers.second; + + if (!scrollableArea) + return false; + + IntPoint scrollPosition = scrollableArea->scrollPosition(); + IntPoint newScrollPosition = scrollPosition; + IntSize scrollSize = scrollableArea->contentsSize(); + IntRect scrollVisibleRect = scrollableArea->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + switch (direction) { + case Right: { + int scrollAmount = scrollVisibleRect.size().width(); + int newX = scrollPosition.x() - scrollAmount; + newScrollPosition.setX(std::max(newX, 0)); + break; + } + case Left: { + int scrollAmount = scrollVisibleRect.size().width(); + int newX = scrollAmount + scrollPosition.x(); + int maxX = scrollSize.width() - scrollAmount; + newScrollPosition.setX(std::min(newX, maxX)); + break; + } + case Up: { + int scrollAmount = scrollVisibleRect.size().height(); + int newY = scrollPosition.y() - scrollAmount; + newScrollPosition.setY(std::max(newY, 0)); + break; + } + case Down: { + int scrollAmount = scrollVisibleRect.size().height(); + int newY = scrollAmount + scrollPosition.y(); + int maxY = scrollSize.height() - scrollAmount; + newScrollPosition.setY(std::min(newY, maxY)); + break; + } + default: + break; + } + + if (newScrollPosition != scrollPosition) { + scrollParent->scrollTo(newScrollPosition); + document()->updateLayoutIgnorePendingStylesheets(); + return true; + } + + return false; +} + bool AccessibilityObject::lastKnownIsIgnoredValue() { @@ -1880,7 +2820,8 @@ void AccessibilityObject::notifyIfIgnoredValueChanged() { bool isIgnored = accessibilityIsIgnored(); if (lastKnownIsIgnoredValue() != isIgnored) { - axObjectCache()->childrenChanged(parentObject()); + if (AXObjectCache* cache = axObjectCache()) + cache->childrenChanged(parentObject()); setLastKnownIsIgnoredValue(isIgnored); } } @@ -1894,7 +2835,7 @@ TextIteratorBehavior AccessibilityObject::textIteratorBehaviorForTextRange() con { TextIteratorBehavior behavior = TextIteratorIgnoresStyleVisibility; -#if PLATFORM(GTK) +#if PLATFORM(GTK) || PLATFORM(EFL) // We need to emit replaced elements for GTK, and present // them with the 'object replacement character' (0xFFFC). behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsObjectReplacementCharacters); @@ -1929,22 +2870,35 @@ bool AccessibilityObject::accessibilityIsIgnoredByDefault() const return defaultObjectInclusion() == IgnoreObject; } -bool AccessibilityObject::ariaIsHidden() const +// ARIA component of hidden definition. +// http://www.w3.org/TR/wai-aria/terms#def_hidden +bool AccessibilityObject::isARIAHidden() const { - if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "true")) - return true; - - for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) { - if (equalIgnoringCase(object->getAttribute(aria_hiddenAttr), "true")) + for (const AccessibilityObject* object = this; object; object = object->parentObject()) { + if (equalLettersIgnoringASCIICase(object->getAttribute(aria_hiddenAttr), "true")) return true; } - return false; } +// DOM component of hidden definition. +// http://www.w3.org/TR/wai-aria/terms#def_hidden +bool AccessibilityObject::isDOMHidden() const +{ + RenderObject* renderer = this->renderer(); + if (!renderer) + return true; + + const RenderStyle& style = renderer->style(); + return style.display() == NONE || style.visibility() != VISIBLE; +} + AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const { - if (ariaIsHidden()) + if (isARIAHidden()) + return IgnoreObject; + + if (ignoredFromARIAModalPresence()) return IgnoreObject; if (isPresentationalChildOfAriaRole()) @@ -1955,7 +2909,11 @@ AccessibilityObjectInclusion AccessibilityObject::defaultObjectInclusion() const bool AccessibilityObject::accessibilityIsIgnored() const { - AXComputedObjectAttributeCache* attributeCache = axObjectCache()->computedObjectAttributeCache(); + AXComputedObjectAttributeCache* attributeCache = nullptr; + AXObjectCache* cache = axObjectCache(); + if (cache) + attributeCache = cache->computedObjectAttributeCache(); + if (attributeCache) { AccessibilityObjectInclusion ignored = attributeCache->getIgnored(axObjectID()); switch (ignored) { @@ -1970,10 +2928,74 @@ bool AccessibilityObject::accessibilityIsIgnored() const bool result = computeAccessibilityIsIgnored(); - if (attributeCache) + // In case computing axIsIgnored disables attribute caching, we should refetch the object to see if it exists. + if (cache && (attributeCache = cache->computedObjectAttributeCache())) attributeCache->setIgnored(axObjectID(), result ? IgnoreObject : IncludeObject); return result; } +void AccessibilityObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const +{ + Node* node = this->node(); + if (!node || !node->isElementNode()) + return; + + TreeScope& treeScope = node->treeScope(); + + String idList = getAttribute(attribute).string(); + if (idList.isEmpty()) + return; + + idList.replace('\n', ' '); + Vector<String> idVector; + idList.split(' ', idVector); + + for (const auto& idName : idVector) { + if (Element* idElement = treeScope.getElementById(idName)) + elements.append(idElement); + } +} + +#if PLATFORM(COCOA) +bool AccessibilityObject::preventKeyboardDOMEventDispatch() const +{ + Frame* frame = this->frame(); + return frame && frame->settings().preventKeyboardDOMEventDispatch(); +} + +void AccessibilityObject::setPreventKeyboardDOMEventDispatch(bool on) +{ + Frame* frame = this->frame(); + if (!frame) + return; + frame->settings().setPreventKeyboardDOMEventDispatch(on); +} +#endif + +bool AccessibilityObject::isStyleFormatGroup() const +{ + Node* node = this->node(); + if (!node) + return false; + + return node->hasTagName(kbdTag) || node->hasTagName(codeTag) + || node->hasTagName(preTag) || node->hasTagName(sampTag) + || node->hasTagName(varTag) || node->hasTagName(citeTag) + || node->hasTagName(insTag) || node->hasTagName(delTag); +} + +bool AccessibilityObject::isContainedByPasswordField() const +{ + Node* node = this->node(); + if (!node) + return false; + + if (ariaRoleAttribute() != UnknownRole) + return false; + + Element* element = node->shadowHost(); + return is<HTMLInputElement>(element) && downcast<HTMLInputElement>(*element).isPasswordField(); +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h index 680e367d1..d629778ae 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ b/Source/WebCore/accessibility/AccessibilityObject.h @@ -11,7 +11,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -33,21 +33,21 @@ #include "FloatQuad.h" #include "LayoutRect.h" #include "Path.h" -#include "TextIterator.h" +#include "TextIteratorBehavior.h" #include "VisiblePosition.h" #include "VisibleSelection.h" #include <wtf/Forward.h> #include <wtf/RefPtr.h> #include <wtf/Vector.h> -#if PLATFORM(MAC) +#if PLATFORM(COCOA) #include <wtf/RetainPtr.h> #elif PLATFORM(WIN) #include "AccessibilityObjectWrapperWin.h" #include "COMPtr.h" #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) typedef struct _NSRange NSRange; @@ -80,12 +80,13 @@ class HTMLAnchorElement; class HTMLAreaElement; class IntPoint; class IntSize; +class MainFrame; class Node; class Page; class RenderObject; class RenderListItem; class ScrollableArea; -class VisibleSelection; +class ScrollView; class Widget; typedef unsigned AXID; @@ -99,11 +100,14 @@ enum AccessibilityRole { ApplicationLogRole, ApplicationMarqueeRole, ApplicationStatusRole, - ApplicationTimerRole, + ApplicationTimerRole, + AudioRole, + BlockquoteRole, BrowserRole, BusyIndicatorRole, ButtonRole, CanvasRole, + CaptionRole, CellRole, CheckBoxRole, ColorWellRole, @@ -114,6 +118,7 @@ enum AccessibilityRole { DescriptionListRole, DescriptionListTermRole, DescriptionListDetailRole, + DetailsRole, DirectoryRole, DisclosureTriangleRole, DivRole, @@ -127,12 +132,14 @@ enum AccessibilityRole { FooterRole, FormRole, GridRole, + GridCellRole, GroupRole, GrowAreaRole, HeadingRole, HelpTagRole, HorizontalRuleRole, IgnoredRole, + InlineRole, ImageRole, ImageMapRole, ImageMapLinkRole, @@ -158,22 +165,31 @@ enum AccessibilityRole { MenuBarRole, MenuButtonRole, MenuItemRole, + MenuItemCheckboxRole, + MenuItemRadioRole, MenuListPopupRole, MenuListOptionRole, OutlineRole, ParagraphRole, PopUpButtonRole, + PreRole, PresentationalRole, ProgressIndicatorRole, RadioButtonRole, RadioGroupRole, RowHeaderRole, RowRole, + RowGroupRole, + RubyBaseRole, + RubyBlockRole, + RubyInlineRole, + RubyRunRole, + RubyTextRole, RulerRole, RulerMarkerRole, ScrollAreaRole, ScrollBarRole, - SeamlessWebAreaRole, + SearchFieldRole, SheetRole, SliderRole, SliderThumbRole, @@ -182,6 +198,8 @@ enum AccessibilityRole { SplitGroupRole, SplitterRole, StaticTextRole, + SummaryRole, + SwitchRole, SystemWideRole, SVGRootRole, TabGroupRole, @@ -199,7 +217,8 @@ enum AccessibilityRole { ToolbarRole, UnknownRole, UserInterfaceTooltipRole, - ValueIndicatorRole, + ValueIndicatorRole, + VideoRole, WebAreaRole, WebCoreLinkRole, WindowRole, @@ -219,17 +238,17 @@ enum AccessibilityTextSource { struct AccessibilityText { String text; AccessibilityTextSource textSource; - Vector<RefPtr<AccessibilityObject> > textElements; + Vector<RefPtr<AccessibilityObject>> textElements; AccessibilityText(const String& t, const AccessibilityTextSource& s) : text(t) , textSource(s) { } - AccessibilityText(const String& t, const AccessibilityTextSource& s, const Vector<RefPtr<AccessibilityObject> > elements) + AccessibilityText(const String& t, const AccessibilityTextSource& s, Vector<RefPtr<AccessibilityObject>> elements) : text(t) , textSource(s) - , textElements(elements) + , textElements(WTFMove(elements)) { } AccessibilityText(const String& t, const AccessibilityTextSource& s, const RefPtr<AccessibilityObject> element) @@ -240,14 +259,28 @@ struct AccessibilityText { } }; -enum AccessibilityTextUnderElementMode { - TextUnderElementModeSkipIgnoredChildren, - TextUnderElementModeIncludeAllChildren +struct AccessibilityTextUnderElementMode { + enum ChildrenInclusion { + TextUnderElementModeSkipIgnoredChildren, + TextUnderElementModeIncludeAllChildren, + TextUnderElementModeIncludeNameFromContentsChildren, // This corresponds to ARIA concept: nameFrom + }; + + ChildrenInclusion childrenInclusion; + bool includeFocusableContent; + Node* ignoredChildNode; + + AccessibilityTextUnderElementMode(ChildrenInclusion c = TextUnderElementModeSkipIgnoredChildren, bool i = false, Node* ignored = nullptr) + : childrenInclusion(c) + , includeFocusableContent(i) + , ignoredChildNode(ignored) + { } }; enum AccessibilityOrientation { AccessibilityOrientationVertical, AccessibilityOrientationHorizontal, + AccessibilityOrientationUndefined, }; enum AccessibilityObjectInclusion { @@ -266,6 +299,7 @@ enum AccessibilitySortDirection { SortDirectionNone, SortDirectionAscending, SortDirectionDescending, + SortDirectionOther }; enum AccessibilitySearchDirection { @@ -301,6 +335,7 @@ enum AccessibilitySearchKey { ListSearchKey, LiveRegionSearchKey, MisspelledWordSearchKey, + OutlineSearchKey, PlainTextSearchKey, RadioGroupSearchKey, SameTypeSearchKey, @@ -314,22 +349,31 @@ enum AccessibilitySearchKey { VisitedLinkSearchKey }; +enum AccessibilityVisiblePositionForBounds { + FirstVisiblePositionForBounds, + LastVisiblePositionForBounds +}; + struct AccessibilitySearchCriteria { AccessibilityObject* startObject; AccessibilitySearchDirection searchDirection; Vector<AccessibilitySearchKey> searchKeys; - String* searchText; + String searchText; unsigned resultsLimit; bool visibleOnly; + bool immediateDescendantsOnly; - AccessibilitySearchCriteria(AccessibilityObject* o, AccessibilitySearchDirection d, String* t, unsigned l, bool v) - : startObject(o) - , searchDirection(d) - , searchText(t) - , resultsLimit(l) - , visibleOnly(v) + AccessibilitySearchCriteria(AccessibilityObject* startObject, AccessibilitySearchDirection searchDirection, String searchText, unsigned resultsLimit, bool visibleOnly, bool immediateDescendantsOnly) + : startObject(startObject) + , searchDirection(searchDirection) + , searchText(searchText) + , resultsLimit(resultsLimit) + , visibleOnly(visibleOnly) + , immediateDescendantsOnly(immediateDescendantsOnly) { } }; + +enum AccessibilityDetachmentType { CacheDestroyed, ElementDestroyed }; struct VisiblePositionRange { @@ -364,6 +408,38 @@ struct PlainTextRange { bool isNull() const { return !start && !length; } }; +enum AccessibilitySelectTextActivity { + FindAndReplaceActivity, + FindAndSelectActivity, + FindAndCapitalize, + FindAndLowercase, + FindAndUppercase +}; + +enum AccessibilitySelectTextAmbiguityResolution { + ClosestAfterSelectionAmbiguityResolution, + ClosestBeforeSelectionAmbiguityResolution, + ClosestToSelectionAmbiguityResolution +}; + +struct AccessibilitySelectTextCriteria { + AccessibilitySelectTextActivity activity; + AccessibilitySelectTextAmbiguityResolution ambiguityResolution; + String replacementString; + Vector<String> searchStrings; + + AccessibilitySelectTextCriteria(AccessibilitySelectTextActivity activity, AccessibilitySelectTextAmbiguityResolution ambiguityResolution, const String& replacementString) + : activity(activity) + , ambiguityResolution(ambiguityResolution) + , replacementString(replacementString) + { } +}; + +enum AccessibilityMathScriptObjectType { Subscript, Superscript }; +enum AccessibilityMathMultiscriptObjectType { PreSubscript, PreSuperscript, PostSubscript, PostSuperscript }; + +enum AccessibilityARIACurrentState { ARIACurrentFalse, ARIACurrentTrue, ARIACurrentPage, ARIACurrentStep, ARIACurrentLocation, ARIACurrentDate, ARIACurrentTime }; + class AccessibilityObject : public RefCounted<AccessibilityObject> { protected: AccessibilityObject(); @@ -379,10 +455,10 @@ public: // When the corresponding WebCore object that this AccessibilityObject // wraps is deleted, it must be detached. - virtual void detach(); + virtual void detach(AccessibilityDetachmentType, AXObjectCache* cache = nullptr); virtual bool isDetached() const; - typedef Vector<RefPtr<AccessibilityObject> > AccessibilityChildrenVector; + typedef Vector<RefPtr<AccessibilityObject>> AccessibilityChildrenVector; virtual bool isAccessibilityNodeObject() const { return false; } virtual bool isAccessibilityRenderObject() const { return false; } @@ -391,22 +467,24 @@ public: virtual bool isAccessibilitySVGRoot() const { return false; } bool accessibilityObjectContainsText(String *) const; - - virtual bool isAnchor() const { return false; } + virtual bool isAttachment() const { return false; } virtual bool isHeading() const { return false; } virtual bool isLink() const { return false; } virtual bool isImage() const { return false; } + virtual bool isImageMap() const { return roleValue() == ImageMapRole; } virtual bool isNativeImage() const { return false; } virtual bool isImageButton() const { return false; } virtual bool isPasswordField() const { return false; } + bool isContainedByPasswordField() const; + virtual AccessibilityObject* passwordFieldOrContainingPasswordField() { return nullptr; } virtual bool isNativeTextControl() const { return false; } virtual bool isSearchField() const { return false; } bool isWebArea() const { return roleValue() == WebAreaRole; } - bool isSeamlessWebArea() const { return roleValue() == SeamlessWebAreaRole; } virtual bool isCheckbox() const { return roleValue() == CheckBoxRole; } virtual bool isRadioButton() const { return roleValue() == RadioButtonRole; } virtual bool isListBox() const { return roleValue() == ListBoxRole; } + virtual bool isListBoxOption() const { return false; } virtual bool isMediaTimeline() const { return false; } virtual bool isMenuRelated() const { return false; } virtual bool isMenu() const { return false; } @@ -417,11 +495,11 @@ public: virtual bool isInputImage() const { return false; } virtual bool isProgressIndicator() const { return false; } virtual bool isSlider() const { return false; } + virtual bool isSliderThumb() const { return false; } virtual bool isInputSlider() const { return false; } virtual bool isControl() const { return false; } virtual bool isList() const { return false; } virtual bool isTable() const { return false; } - virtual bool isAccessibilityTable() const { return false; } virtual bool isDataTable() const { return false; } virtual bool isTableRow() const { return false; } virtual bool isTableColumn() const { return false; } @@ -438,8 +516,11 @@ public: virtual bool isSpinButtonPart() const { return false; } virtual bool isMockObject() const { return false; } virtual bool isMediaControlLabel() const { return false; } + bool isSwitch() const { return roleValue() == SwitchRole; } + bool isToggleButton() const { return roleValue() == ToggleButtonRole; } bool isTextControl() const; bool isARIATextControl() const; + bool isNonNativeTextControl() const; bool isTabList() const { return roleValue() == TabListRole; } bool isTabItem() const { return roleValue() == TabRole; } bool isRadioGroup() const { return roleValue() == RadioGroupRole; } @@ -457,7 +538,11 @@ public: bool isLandmark() const; bool isColorWell() const { return roleValue() == ColorWellRole; } bool isRangeControl() const; - + bool isMeter() const; + bool isSplitter() const { return roleValue() == SplitterRole; } + bool isToolbar() const { return roleValue() == ToolbarRole; } + bool isStyleFormatGroup() const; + virtual bool isChecked() const { return false; } virtual bool isEnabled() const { return false; } virtual bool isSelected() const { return false; } @@ -468,7 +553,6 @@ public: virtual bool isMultiSelectable() const { return false; } virtual bool isOffScreen() const { return false; } virtual bool isPressed() const { return false; } - virtual bool isReadOnly() const { return false; } virtual bool isUnvisited() const { return false; } virtual bool isVisited() const { return false; } virtual bool isRequired() const { return false; } @@ -493,6 +577,8 @@ public: virtual bool hasUnderline() const { return false; } bool hasHighlighting() const; + bool supportsDatetimeAttribute() const; + virtual bool canSetFocusAttribute() const { return false; } virtual bool canSetTextRangeAttributes() const { return false; } virtual bool canSetValueAttribute() const { return false; } @@ -501,8 +587,9 @@ public: virtual bool canSetSelectedChildrenAttribute() const { return false; } virtual bool canSetExpandedAttribute() const { return false; } - virtual Node* node() const { return 0; } - virtual RenderObject* renderer() const { return 0; } + virtual Element* element() const; + virtual Node* node() const { return nullptr; } + virtual RenderObject* renderer() const { return nullptr; } virtual bool accessibilityIsIgnored() const; virtual AccessibilityObjectInclusion defaultObjectInclusion() const; bool accessibilityIsIgnoredByDefault() const; @@ -516,8 +603,8 @@ public: virtual float maxValueForRange() const { return 0.0f; } virtual float minValueForRange() const { return 0.0f; } virtual float stepValueForRange() const { return 0.0f; } - virtual AccessibilityObject* selectedRadioButton() { return 0; } - virtual AccessibilityObject* selectedTabItem() { return 0; } + virtual AccessibilityObject* selectedRadioButton() { return nullptr; } + virtual AccessibilityObject* selectedTabItem() { return nullptr; } virtual int layoutCount() const { return 0; } virtual double estimatedLoadingProgress() const { return 0; } static bool isARIAControl(AccessibilityRole); @@ -526,15 +613,29 @@ public: virtual void ariaOwnsElements(AccessibilityChildrenVector&) const { } virtual bool supportsARIAFlowTo() const { return false; } virtual void ariaFlowToElements(AccessibilityChildrenVector&) const { } + virtual bool supportsARIADescribedBy() const { return false; } + virtual void ariaDescribedByElements(AccessibilityChildrenVector&) const { } + virtual bool supportsARIAControls() const { return false; } + virtual void ariaControlsElements(AccessibilityChildrenVector&) const { } virtual bool ariaHasPopup() const { return false; } - virtual bool ariaPressedIsPresent() const; + bool ariaPressedIsPresent() const; bool ariaIsMultiline() const; - virtual const AtomicString& invalidStatus() const; - bool supportsARIAExpanded() const; + String invalidStatus() const; + bool supportsARIAPressed() const; + bool supportsExpanded() const; + bool supportsChecked() const; AccessibilitySortDirection sortDirection() const; virtual bool canvasHasFallbackContent() const { return false; } bool supportsRangeValue() const; - + String identifierAttribute() const; + void classList(Vector<String>&) const; + const AtomicString& roleDescription() const; + AccessibilityARIACurrentState ariaCurrentState() const; + + // This function checks if the object should be ignored when there's a modal dialog displayed. + bool ignoredFromARIAModalPresence() const; + bool isAriaModalDescendant(Node*) const; + bool supportsARIASetSize() const; bool supportsARIAPosInSet() const; int ariaSetSize() const; @@ -548,40 +649,51 @@ public: virtual void determineARIADropEffects(Vector<String>&) { } // Called on the root AX object to return the deepest available element. - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const { return 0; } + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const { return nullptr; } // Called on the AX object after the render tree determines which is the right AccessibilityRenderObject. virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; virtual AccessibilityObject* focusedUIElement() const; - virtual AccessibilityObject* firstChild() const { return 0; } - virtual AccessibilityObject* lastChild() const { return 0; } - virtual AccessibilityObject* previousSibling() const { return 0; } - virtual AccessibilityObject* nextSibling() const { return 0; } + virtual AccessibilityObject* firstChild() const { return nullptr; } + virtual AccessibilityObject* lastChild() const { return nullptr; } + virtual AccessibilityObject* previousSibling() const { return nullptr; } + virtual AccessibilityObject* nextSibling() const { return nullptr; } + virtual AccessibilityObject* nextSiblingUnignored(int limit) const; + virtual AccessibilityObject* previousSiblingUnignored(int limit) const; virtual AccessibilityObject* parentObject() const = 0; virtual AccessibilityObject* parentObjectUnignored() const; - virtual AccessibilityObject* parentObjectIfExists() const { return 0; } + virtual AccessibilityObject* parentObjectIfExists() const { return nullptr; } static AccessibilityObject* firstAccessibleObjectFromNode(const Node*); void findMatchingObjects(AccessibilitySearchCriteria*, AccessibilityChildrenVector&); virtual bool isDescendantOfBarrenParent() const { return false; } - virtual AccessibilityObject* observableObject() const { return 0; } + // Text selection + RefPtr<Range> rangeOfStringClosestToRangeInDirection(Range*, AccessibilitySearchDirection, Vector<String>&) const; + RefPtr<Range> selectionRange() const; + String selectText(AccessibilitySelectTextCriteria*); + + virtual AccessibilityObject* observableObject() const { return nullptr; } virtual void linkedUIElements(AccessibilityChildrenVector&) const { } - virtual AccessibilityObject* titleUIElement() const { return 0; } + virtual AccessibilityObject* titleUIElement() const { return nullptr; } virtual bool exposesTitleUIElement() const { return true; } - virtual AccessibilityObject* correspondingLabelForControlElement() const { return 0; } - virtual AccessibilityObject* correspondingControlForLabelElement() const { return 0; } - virtual AccessibilityObject* scrollBar(AccessibilityOrientation) { return 0; } + virtual AccessibilityObject* correspondingLabelForControlElement() const { return nullptr; } + virtual AccessibilityObject* correspondingControlForLabelElement() const { return nullptr; } + virtual AccessibilityObject* scrollBar(AccessibilityOrientation) { return nullptr; } virtual AccessibilityRole ariaRoleAttribute() const { return UnknownRole; } virtual bool isPresentationalChildOfAriaRole() const { return false; } virtual bool ariaRoleHasPresentationalChildren() const { return false; } + virtual bool inheritsPresentationalRole() const { return false; } // Accessibility Text virtual void accessibilityText(Vector<AccessibilityText>&) { }; - + // A single method for getting a computed label for an AXObject. It condenses the nuances of accessibilityText. Used by Inspector. + String computedLabel(); + // A programmatic way to set a name on an AccessibleObject. virtual void setAccessibleName(const AtomicString&) { } + virtual bool hasAttributesRequiredForInclusion() const; // Accessibility Text - (To be deprecated). virtual String accessibilityDescription() const { return String(); } @@ -590,31 +702,37 @@ public: // Methods for determining accessibility text. virtual String stringValue() const { return String(); } - virtual String textUnderElement(AccessibilityTextUnderElementMode = TextUnderElementModeSkipIgnoredChildren) const { return String(); } + virtual String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const { return String(); } virtual String text() const { return String(); } virtual int textLength() const { return 0; } virtual String ariaLabeledByAttribute() const { return String(); } virtual String ariaDescribedByAttribute() const { return String(); } const AtomicString& placeholderValue() const; + bool accessibleNameDerivesFromContent() const; + + // Abbreviations + virtual String expandedTextValue() const { return String(); } + virtual bool supportsExpandedTextValue() const { return false; } + + void elementsFromAttribute(Vector<Element*>&, const QualifiedName&) const; // Only if isColorWell() virtual void colorValue(int& r, int& g, int& b) const { r = 0; g = 0; b = 0; } - void setRoleValue(AccessibilityRole role) { m_role = role; } virtual AccessibilityRole roleValue() const { return m_role; } virtual AXObjectCache* axObjectCache() const; AXID axObjectID() const { return m_id; } static AccessibilityObject* anchorElementForNode(Node*); - virtual Element* anchorElement() const { return 0; } - virtual Element* actionElement() const { return 0; } + static AccessibilityObject* headingElementForNode(Node*); + virtual Element* anchorElement() const { return nullptr; } + bool supportsPressAction() const; + virtual Element* actionElement() const { return nullptr; } virtual LayoutRect boundingBoxRect() const { return LayoutRect(); } - IntRect pixelSnappedBoundingBoxRect() const { return pixelSnappedIntRect(boundingBoxRect()); } + IntRect pixelSnappedBoundingBoxRect() const { return snappedIntRect(boundingBoxRect()); } virtual LayoutRect elementRect() const = 0; - IntRect pixelSnappedElementRect() const { return pixelSnappedIntRect(elementRect()); } LayoutSize size() const { return elementRect().size(); } - IntSize pixelSnappedSize() const { return elementRect().pixelSnappedSize(); } virtual IntPoint clickPoint(); static IntRect boundingBoxForQuads(RenderObject*, const Vector<FloatQuad>&); virtual Path elementPath() const { return Path(); } @@ -625,17 +743,20 @@ public: unsigned selectionStart() const { return selectedTextRange().start; } unsigned selectionEnd() const { return selectedTextRange().length; } - virtual KURL url() const { return KURL(); } + virtual URL url() const { return URL(); } virtual VisibleSelection selection() const { return VisibleSelection(); } virtual String selectedText() const { return String(); } virtual const AtomicString& accessKey() const { return nullAtom; } const String& actionVerb() const; - virtual Widget* widget() const { return 0; } - virtual Widget* widgetForAttachmentView() const { return 0; } + virtual Widget* widget() const { return nullptr; } + virtual Widget* widgetForAttachmentView() const { return nullptr; } Page* page() const; virtual Document* document() const; - virtual FrameView* topDocumentFrameView() const { return 0; } virtual FrameView* documentFrameView() const; + Frame* frame() const; + MainFrame* mainFrame() const; + Document* topDocument() const; + ScrollView* scrollViewAncestor() const; String language() const; // 1-based, to match the aria-level spec. virtual unsigned hierarchicalLevel() const { return 0; } @@ -649,8 +770,8 @@ public: virtual void setSelectedRows(AccessibilityChildrenVector&) { } virtual void makeRangeVisible(const PlainTextRange&) { } - virtual bool press() const; - bool performDefaultAction() const { return press(); } + virtual bool press(); + bool performDefaultAction() { return press(); } virtual AccessibilityOrientation orientation() const; virtual void increment() { } @@ -659,27 +780,30 @@ public: virtual void childrenChanged() { } virtual void textChanged() { } virtual void updateAccessibilityRole() { } - const AccessibilityChildrenVector& children(); + const AccessibilityChildrenVector& children(bool updateChildrenIfNeeded = true); virtual void addChildren() { } virtual void addChild(AccessibilityObject*) { } virtual void insertChild(AccessibilityObject*, unsigned) { } + + virtual bool shouldIgnoreAttributeRole() const { return false; } virtual bool canHaveChildren() const { return true; } virtual bool hasChildren() const { return m_haveChildren; } virtual void updateChildrenIfNecessary(); virtual void setNeedsToUpdateChildren() { } virtual void clearChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) virtual void detachFromParent(); #else virtual void detachFromParent() { } #endif + virtual bool isDetachedFromParent() { return false; } virtual void selectedChildren(AccessibilityChildrenVector&) { } virtual void visibleChildren(AccessibilityChildrenVector&) { } virtual void tabChildren(AccessibilityChildrenVector&) { } virtual bool shouldFocusActiveDescendant() const { return false; } - virtual AccessibilityObject* activeDescendant() const { return 0; } + virtual AccessibilityObject* activeDescendant() const { return nullptr; } virtual void handleActiveDescendantChanged() { } virtual void handleAriaExpandedChanged() { } bool isDescendantOfObject(const AccessibilityObject*) const; @@ -689,10 +813,14 @@ public: static AccessibilityRole ariaRoleToWebCoreRole(const String&); bool hasAttribute(const QualifiedName&) const; const AtomicString& getAttribute(const QualifiedName&) const; + bool hasTagName(const QualifiedName&) const; virtual VisiblePositionRange visiblePositionRange() const { return VisiblePositionRange(); } virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); } + RefPtr<Range> elementRange() const; + static bool replacedNodeNeedsCharacter(Node* replacedNode); + VisiblePositionRange visiblePositionRangeForUnorderedPositions(const VisiblePosition&, const VisiblePosition&) const; VisiblePositionRange positionOfLeftWord(const VisiblePosition&) const; VisiblePositionRange positionOfRightWord(const VisiblePosition&) const; @@ -702,12 +830,15 @@ public: VisiblePositionRange paragraphForPosition(const VisiblePosition&) const; VisiblePositionRange styleRangeForPosition(const VisiblePosition&) const; VisiblePositionRange visiblePositionRangeForRange(const PlainTextRange&) const; + VisiblePositionRange lineRangeForPosition(const VisiblePosition&) const; String stringForVisiblePositionRange(const VisiblePositionRange&) const; + String stringForRange(RefPtr<Range>) const; virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const { return IntRect(); } int lengthForVisiblePositionRange(const VisiblePositionRange&) const; virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const { } + VisiblePosition visiblePositionForBounds(const IntRect&, AccessibilityVisiblePositionForBounds) const; virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const { return VisiblePosition(); } VisiblePosition nextVisiblePosition(const VisiblePosition& visiblePos) const { return visiblePos.next(); } VisiblePosition previousVisiblePosition(const VisiblePosition& visiblePos) const { return visiblePos.previous(); } @@ -741,6 +872,8 @@ public: unsigned doAXLineForIndex(unsigned); + String computedRoleString() const; + virtual String stringValueForMSAA() const { return String(); } virtual String stringRoleForMSAA() const { return String(); } virtual String nameForMSAA() const { return String(); } @@ -748,7 +881,8 @@ public: virtual AccessibilityRole roleValueForMSAA() const { return roleValue(); } virtual String passwordFieldValue() const { return String(); } - + bool isValueAutofilled() const; + // Used by an ARIA tree to get all its rows. void ariaTreeRows(AccessibilityChildrenVector&); // Used by an ARIA tree item to get all of its direct rows that it can disclose. @@ -759,10 +893,17 @@ public: // ARIA live-region features. bool supportsARIALiveRegion() const; bool isInsideARIALiveRegion() const; - virtual const AtomicString& ariaLiveRegionStatus() const { return nullAtom; } + virtual const String ariaLiveRegionStatus() const { return String(); } virtual const AtomicString& ariaLiveRegionRelevant() const { return nullAtom; } virtual bool ariaLiveRegionAtomic() const { return false; } virtual bool ariaLiveRegionBusy() const { return false; } + static const String defaultLiveRegionStatusForRole(AccessibilityRole); + static bool liveRegionStatusIsEnabled(const AtomicString&); + static bool contentEditableAttributeIsEnabled(Element*); + bool hasContentEditableAttributeSet() const; + + bool supportsARIAReadOnly() const; + String ariaReadOnlyValue() const; bool supportsARIAAttributes() const; @@ -775,7 +916,13 @@ public: virtual void scrollToMakeVisibleWithSubFocus(const IntRect&) const; // Scroll this object to a given point in global coordinates of the top-level window. virtual void scrollToGlobalPoint(const IntPoint&) const; - + + enum ScrollByPageDirection { Up, Down, Left, Right }; + bool scrollByPage(ScrollByPageDirection) const; + IntPoint scrollPosition() const; + IntSize scrollContentsSize() const; + IntRect scrollVisibleContentRect() const; + bool lastKnownIsIgnoredValue(); void setLastKnownIsIgnoredValue(bool); @@ -801,23 +948,26 @@ public: virtual bool isMathTableRow() const { return false; } virtual bool isMathTableCell() const { return false; } virtual bool isMathMultiscript() const { return false; } + virtual bool isMathToken() const { return false; } + virtual bool isMathScriptObject(AccessibilityMathScriptObjectType) const { return false; } + virtual bool isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType) const { return false; } // Root components. - virtual AccessibilityObject* mathRadicandObject() { return 0; } - virtual AccessibilityObject* mathRootIndexObject() { return 0; } + virtual AccessibilityObject* mathRadicandObject() { return nullptr; } + virtual AccessibilityObject* mathRootIndexObject() { return nullptr; } // Under over components. - virtual AccessibilityObject* mathUnderObject() { return 0; } - virtual AccessibilityObject* mathOverObject() { return 0; } + virtual AccessibilityObject* mathUnderObject() { return nullptr; } + virtual AccessibilityObject* mathOverObject() { return nullptr; } // Fraction components. - virtual AccessibilityObject* mathNumeratorObject() { return 0; } - virtual AccessibilityObject* mathDenominatorObject() { return 0; } + virtual AccessibilityObject* mathNumeratorObject() { return nullptr; } + virtual AccessibilityObject* mathDenominatorObject() { return nullptr; } // Subscript/superscript components. - virtual AccessibilityObject* mathBaseObject() { return 0; } - virtual AccessibilityObject* mathSubscriptObject() { return 0; } - virtual AccessibilityObject* mathSuperscriptObject() { return 0; } + virtual AccessibilityObject* mathBaseObject() { return nullptr; } + virtual AccessibilityObject* mathSubscriptObject() { return nullptr; } + virtual AccessibilityObject* mathSuperscriptObject() { return nullptr; } // Fenced components. virtual String mathFencedOpenString() const { return String(); } @@ -825,10 +975,15 @@ public: virtual int mathLineThickness() const { return 0; } // Multiscripts components. - typedef Vector<pair<AccessibilityObject*, AccessibilityObject*> > AccessibilityMathMultiscriptPairs; + typedef Vector<std::pair<AccessibilityObject*, AccessibilityObject*>> AccessibilityMathMultiscriptPairs; virtual void mathPrescripts(AccessibilityMathMultiscriptPairs&) { } virtual void mathPostscripts(AccessibilityMathMultiscriptPairs&) { } - + + // Visibility. + bool isARIAHidden() const; + bool isDOMHidden() const; + bool isHidden() const { return isARIAHidden() || isDOMHidden(); } + #if HAVE(ACCESSIBILITY) #if PLATFORM(GTK) || PLATFORM(EFL) AccessibilityObjectWrapper* wrapper() const; @@ -842,7 +997,7 @@ public: #endif #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void overrideAttachmentParent(AccessibilityObject* parent); #else void overrideAttachmentParent(AccessibilityObject*) { } @@ -858,10 +1013,24 @@ public: AccessibilityObjectInclusion accessibilityPlatformIncludesObject() const { return DefaultBehavior; } #endif +#if PLATFORM(IOS) + int accessibilityPasswordFieldLength(); +#endif + // allows for an AccessibilityObject to update its render tree or perform // other operations update type operations void updateBackingStore(); +#if PLATFORM(COCOA) + bool preventKeyboardDOMEventDispatch() const; + void setPreventKeyboardDOMEventDispatch(bool); +#endif + +#if PLATFORM(COCOA) && !PLATFORM(IOS) + bool caretBrowsingEnabled() const; + void setCaretBrowsingEnabled(bool); +#endif + protected: AXID m_id; AccessibilityChildrenVector m_children; @@ -872,18 +1041,20 @@ protected: virtual bool computeAccessibilityIsIgnored() const { return true; } // If this object itself scrolls, return its ScrollableArea. - virtual ScrollableArea* getScrollableAreaIfScrollable() const { return 0; } + virtual ScrollableArea* getScrollableAreaIfScrollable() const { return nullptr; } virtual void scrollTo(const IntPoint&) const { } - + ScrollableArea* scrollableAreaAncestor() const; + void scrollAreaAndAncestor(std::pair<ScrollableArea*, AccessibilityObject*>&) const; + static bool isAccessibilityObjectSearchMatchAtIndex(AccessibilityObject*, AccessibilitySearchCriteria*, size_t); static bool isAccessibilityObjectSearchMatch(AccessibilityObject*, AccessibilitySearchCriteria*); static bool isAccessibilityTextSearchMatch(AccessibilityObject*, AccessibilitySearchCriteria*); static bool objectMatchesSearchCriteriaWithResultLimit(AccessibilityObject*, AccessibilitySearchCriteria*, AccessibilityChildrenVector&); virtual AccessibilityRole buttonRoleType() const; - bool ariaIsHidden() const; bool isOnscreen() const; - -#if PLATFORM(GTK) || (PLATFORM(EFL) && HAVE(ACCESSIBILITY)) + bool dispatchTouchEvent(); + +#if (PLATFORM(GTK) || PLATFORM(EFL)) && HAVE(ACCESSIBILITY) bool allowsTextRanges() const; unsigned getLengthForTextRange() const; #else @@ -891,7 +1062,7 @@ protected: unsigned getLengthForTextRange() const { return text().length(); } #endif -#if PLATFORM(MAC) +#if PLATFORM(COCOA) RetainPtr<WebAccessibilityObjectWrapper> m_wrapper; #elif PLATFORM(WIN) COMPtr<AccessibilityObjectWrapper> m_wrapper; @@ -901,7 +1072,7 @@ protected: }; #if !HAVE(ACCESSIBILITY) -inline const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children() { return m_children; } +inline const AccessibilityObject::AccessibilityChildrenVector& AccessibilityObject::children(bool) { return m_children; } inline const String& AccessibilityObject::actionVerb() const { return emptyString(); } inline int AccessibilityObject::lineForPosition(const VisiblePosition&) const { return -1; } inline void AccessibilityObject::updateBackingStore() { } @@ -909,4 +1080,9 @@ inline void AccessibilityObject::updateBackingStore() { } } // namespace WebCore +#define SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(ToValueTypeName, predicate) \ +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ToValueTypeName) \ + static bool isType(const WebCore::AccessibilityObject& object) { return object.predicate; } \ +SPECIALIZE_TYPE_TRAITS_END() + #endif // AccessibilityObject_h diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp index 1dac10988..19485396f 100644 --- a/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp @@ -21,7 +21,7 @@ #include "config.h" #include "AccessibilityProgressIndicator.h" -#if ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) +#include "AXObjectCache.h" #include "FloatConversion.h" #include "HTMLMeterElement.h" #include "HTMLNames.h" @@ -34,17 +34,15 @@ namespace WebCore { using namespace HTMLNames; -#if ENABLE(PROGRESS_ELEMENT) AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderProgress* renderer) : AccessibilityRenderObject(renderer) { } -PassRefPtr<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer) +Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer) { - return adoptRef(new AccessibilityProgressIndicator(renderer)); + return adoptRef(*new AccessibilityProgressIndicator(renderer)); } -#endif #if ENABLE(METER_ELEMENT) AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderMeter* renderer) @@ -52,9 +50,9 @@ AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderMeter* rend { } -PassRefPtr<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderMeter* renderer) +Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderMeter* renderer) { - return adoptRef(new AccessibilityProgressIndicator(renderer)); + return adoptRef(*new AccessibilityProgressIndicator(renderer)); } #endif @@ -63,18 +61,46 @@ bool AccessibilityProgressIndicator::computeAccessibilityIsIgnored() const return accessibilityIsIgnoredByDefault(); } +String AccessibilityProgressIndicator::valueDescription() const +{ + // If the author has explicitly provided a value through aria-valuetext, use it. + String description = AccessibilityRenderObject::valueDescription(); + if (!description.isEmpty()) + return description; + +#if ENABLE(METER_ELEMENT) + if (!m_renderer->isMeter()) + return String(); + + HTMLMeterElement* meter = meterElement(); + if (!meter) + return String(); + + // The HTML spec encourages authors to include a textual representation of the meter's state in + // the element's contents. We'll fall back on that if there is not a more accessible alternative. + AccessibilityObject* axMeter = axObjectCache()->getOrCreate(meter); + if (is<AccessibilityNodeObject>(axMeter)) { + description = downcast<AccessibilityNodeObject>(axMeter)->accessibilityDescriptionForChildren(); + if (!description.isEmpty()) + return description; + } + + return meter->textContent(); +#endif + + return String(); +} + float AccessibilityProgressIndicator::valueForRange() const { if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) { HTMLProgressElement* progress = progressElement(); if (progress && progress->position() >= 0) return narrowPrecisionToFloat(progress->value()); } -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -92,12 +118,10 @@ float AccessibilityProgressIndicator::maxValueForRange() const if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) { if (HTMLProgressElement* progress = progressElement()) return narrowPrecisionToFloat(progress->max()); } -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -114,10 +138,8 @@ float AccessibilityProgressIndicator::minValueForRange() const if (!m_renderer) return 0.0; -#if ENABLE(PROGRESS_ELEMENT) if (m_renderer->isProgress()) return 0.0; -#endif #if ENABLE(METER_ELEMENT) if (m_renderer->isMeter()) { @@ -129,27 +151,36 @@ float AccessibilityProgressIndicator::minValueForRange() const return 0.0; } -#if ENABLE(PROGRESS_ELEMENT) HTMLProgressElement* AccessibilityProgressIndicator::progressElement() const { - if (!m_renderer->isProgress()) - return 0; + if (!is<RenderProgress>(*m_renderer)) + return nullptr; - return toRenderProgress(m_renderer)->progressElement(); + return downcast<RenderProgress>(*m_renderer).progressElement(); } -#endif #if ENABLE(METER_ELEMENT) HTMLMeterElement* AccessibilityProgressIndicator::meterElement() const { - if (!m_renderer->isMeter()) - return 0; + if (!is<RenderMeter>(*m_renderer)) + return nullptr; - return toRenderMeter(m_renderer)->meterElement(); + return downcast<RenderMeter>(*m_renderer).meterElement(); } #endif -} // namespace WebCore +Element* AccessibilityProgressIndicator::element() const +{ + if (m_renderer->isProgress()) + return progressElement(); -#endif // ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) +#if ENABLE(METER_ELEMENT) + if (m_renderer->isMeter()) + return meterElement(); +#endif + + return AccessibilityObject::element(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityProgressIndicator.h b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h index 3144c8b0d..3833b2c2c 100644 --- a/Source/WebCore/accessibility/AccessibilityProgressIndicator.h +++ b/Source/WebCore/accessibility/AccessibilityProgressIndicator.h @@ -21,7 +21,6 @@ #ifndef AccessibilityProgressIndicator_h #define AccessibilityProgressIndicator_h -#if ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) #include "AccessibilityRenderObject.h" namespace WebCore { @@ -31,44 +30,39 @@ class HTMLMeterElement; class RenderMeter; #endif -#if ENABLE(PROGRESS_ELEMENT) class HTMLProgressElement; class RenderProgress; -#endif -class AccessibilityProgressIndicator : public AccessibilityRenderObject { +class AccessibilityProgressIndicator final : public AccessibilityRenderObject { public: -#if ENABLE(PROGRESS_ELEMENT) - static PassRefPtr<AccessibilityProgressIndicator> create(RenderProgress*); -#endif + static Ref<AccessibilityProgressIndicator> create(RenderProgress*); #if ENABLE(METER_ELEMENT) - static PassRefPtr<AccessibilityProgressIndicator> create(RenderMeter*); + static Ref<AccessibilityProgressIndicator> create(RenderMeter*); #endif + virtual Element* element() const override; private: - virtual AccessibilityRole roleValue() const { return ProgressIndicatorRole; } + virtual AccessibilityRole roleValue() const override { return ProgressIndicatorRole; } - virtual bool isProgressIndicator() const { return true; } + virtual bool isProgressIndicator() const override { return true; } - virtual float valueForRange() const; - virtual float maxValueForRange() const; - virtual float minValueForRange() const; + virtual String valueDescription() const override; + virtual float valueForRange() const override; + virtual float maxValueForRange() const override; + virtual float minValueForRange() const override; -#if ENABLE(PROGRESS_ELEMENT) explicit AccessibilityProgressIndicator(RenderProgress*); HTMLProgressElement* progressElement() const; -#endif + #if ENABLE(METER_ELEMENT) explicit AccessibilityProgressIndicator(RenderMeter*); HTMLMeterElement* meterElement() const; #endif - virtual bool computeAccessibilityIsIgnored() const; + virtual bool computeAccessibilityIsIgnored() const override; }; } // namespace WebCore -#endif // ENABLE(PROGRESS_ELEMENT) || ENABLE(METER_ELEMENT) - #endif // AccessibilityProgressIndicator_h diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp index ebbc190e4..dff12e007 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -37,12 +37,14 @@ #include "AccessibilityTable.h" #include "CachedImage.h" #include "Chrome.h" +#include "ElementIterator.h" #include "EventNames.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "HTMLAreaElement.h" +#include "HTMLAudioElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLImageElement.h" @@ -56,13 +58,13 @@ #include "HTMLSelectElement.h" #include "HTMLTableElement.h" #include "HTMLTextAreaElement.h" +#include "HTMLVideoElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "Image.h" #include "LocalizedStrings.h" #include "MathMLNames.h" #include "NodeList.h" -#include "NodeTraversal.h" #include "Page.h" #include "ProgressTracker.h" #include "RenderButton.h" @@ -71,14 +73,20 @@ #include "RenderHTMLCanvas.h" #include "RenderImage.h" #include "RenderInline.h" +#include "RenderIterator.h" #include "RenderLayer.h" +#include "RenderLineBreak.h" #include "RenderListBox.h" +#include "RenderListItem.h" #include "RenderListMarker.h" #include "RenderMathMLBlock.h" #include "RenderMathMLFraction.h" #include "RenderMathMLOperator.h" +#include "RenderMathMLRoot.h" #include "RenderMenuList.h" +#include "RenderSVGRoot.h" #include "RenderSVGShape.h" +#include "RenderTableCell.h" #include "RenderText.h" #include "RenderTextControl.h" #include "RenderTextControlSingleLine.h" @@ -89,19 +97,18 @@ #include "RenderedPosition.h" #include "SVGDocument.h" #include "SVGImage.h" -#include "SVGImageChromeClient.h" #include "SVGNames.h" #include "SVGSVGElement.h" #include "Text.h" #include "TextControlInnerElements.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> #include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> -using namespace std; - namespace WebCore { using namespace HTMLNames; @@ -125,14 +132,14 @@ void AccessibilityRenderObject::init() AccessibilityNodeObject::init(); } -PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) +Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) { - return adoptRef(new AccessibilityRenderObject(renderer)); + return adoptRef(*new AccessibilityRenderObject(renderer)); } -void AccessibilityRenderObject::detach() +void AccessibilityRenderObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { - AccessibilityNodeObject::detach(); + AccessibilityNodeObject::detach(detachmentType, cache); detachRemoteSVGRoot(); @@ -140,14 +147,14 @@ void AccessibilityRenderObject::detach() if (m_renderer) m_renderer->setHasAXObject(false); #endif - m_renderer = 0; + m_renderer = nullptr; } RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const { - if (!m_renderer || !m_renderer->isBoxModelObject()) - return 0; - return toRenderBoxModelObject(m_renderer); + if (!is<RenderBoxModelObject>(m_renderer)) + return nullptr; + return downcast<RenderBoxModelObject>(m_renderer); } void AccessibilityRenderObject::setRenderer(RenderObject* renderer) @@ -156,64 +163,55 @@ void AccessibilityRenderObject::setRenderer(RenderObject* renderer) setNode(renderer->node()); } -static inline bool isInlineWithContinuation(RenderObject* object) +static inline bool isInlineWithContinuation(RenderObject& object) { - if (!object->isBoxModelObject()) - return false; - - RenderBoxModelObject* renderer = toRenderBoxModelObject(object); - if (!renderer->isRenderInline()) - return false; - - return toRenderInline(renderer)->continuation(); + return is<RenderInline>(object) && downcast<RenderInline>(object).continuation(); } -static inline RenderObject* firstChildInContinuation(RenderObject* renderer) +static inline RenderObject* firstChildInContinuation(RenderInline& renderer) { - RenderObject* r = toRenderInline(renderer)->continuation(); + auto continuation = renderer.continuation(); - while (r) { - if (r->isRenderBlock()) - return r; - if (RenderObject* child = r->firstChild()) + while (continuation) { + if (is<RenderBlock>(*continuation)) + return continuation; + if (RenderObject* child = continuation->firstChild()) return child; - r = toRenderInline(r)->continuation(); + continuation = downcast<RenderInline>(*continuation).continuation(); } - return 0; + return nullptr; } -static inline RenderObject* firstChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* firstChildConsideringContinuation(RenderObject& renderer) { - RenderObject* firstChild = renderer->firstChild(); + RenderObject* firstChild = renderer.firstChildSlow(); if (!firstChild && isInlineWithContinuation(renderer)) - firstChild = firstChildInContinuation(renderer); + firstChild = firstChildInContinuation(downcast<RenderInline>(renderer)); return firstChild; } -static inline RenderObject* lastChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* lastChildConsideringContinuation(RenderObject& renderer) { - RenderObject* lastChild = renderer->lastChild(); - RenderObject* prev; - RenderObject* cur = renderer; - - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - while (cur) { - prev = cur; + RenderObject* lastChild = downcast<RenderBoxModelObject>(renderer).lastChild(); + RenderBoxModelObject* previous; + for (auto* current = &downcast<RenderBoxModelObject>(renderer); current; ) { + previous = current; - if (RenderObject* lc = cur->lastChild()) - lastChild = lc; + if (RenderObject* newLastChild = current->lastChild()) + lastChild = newLastChild; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT_UNUSED(prev, cur || !toRenderInline(prev)->continuation()); + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT_UNUSED(previous, current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } return lastChild; @@ -222,9 +220,9 @@ static inline RenderObject* lastChildConsideringContinuation(RenderObject* rende AccessibilityObject* AccessibilityRenderObject::firstChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); + RenderObject* firstChild = firstChildConsideringContinuation(*m_renderer); // If an object can't have children, then it is using this method to help // calculate some internal property (like its description). @@ -239,9 +237,9 @@ AccessibilityObject* AccessibilityRenderObject::firstChild() const AccessibilityObject* AccessibilityRenderObject::lastChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* lastChild = lastChildConsideringContinuation(m_renderer); + RenderObject* lastChild = lastChildConsideringContinuation(*m_renderer); if (!lastChild && !canHaveChildren()) return AccessibilityNodeObject::lastChild(); @@ -249,103 +247,91 @@ AccessibilityObject* AccessibilityRenderObject::lastChild() const return axObjectCache()->getOrCreate(lastChild); } -static inline RenderInline* startOfContinuations(RenderObject* r) +static inline RenderInline* startOfContinuations(RenderObject& renderer) { - if (r->isInlineElementContinuation()) { -#if ENABLE(MATHML) - // MathML elements make anonymous RenderObjects, then set their node to the parent's node. - // This makes it so that the renderer() != renderer()->node()->renderer() - // (which is what isInlineElementContinuation() uses as a determinant). - if (r->node()->isElementNode() && toElement(r->node())->isMathMLElement()) - return 0; -#endif - - return toRenderInline(r->node()->renderer()); - } + if (renderer.isInlineElementContinuation() && is<RenderInline>(renderer.node()->renderer())) + return downcast<RenderInline>(renderer.node()->renderer()); // Blocks with a previous continuation always have a next continuation - if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) - return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->node()->renderer()); + if (is<RenderBlock>(renderer) && downcast<RenderBlock>(renderer).inlineElementContinuation()) + return downcast<RenderInline>(downcast<RenderBlock>(renderer).inlineElementContinuation()->element()->renderer()); - return 0; + return nullptr; } -static inline RenderObject* endOfContinuations(RenderObject* renderer) +static inline RenderObject* endOfContinuations(RenderObject& renderer) { - RenderObject* prev = renderer; - RenderObject* cur = renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; - - while (cur) { - prev = cur; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT(cur || !toRenderInline(prev)->continuation()); + auto* previous = &downcast<RenderBoxModelObject>(renderer); + for (auto* current = previous; current; ) { + previous = current; + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT(current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } - return prev; + return previous; } -static inline RenderObject* childBeforeConsideringContinuations(RenderInline* r, RenderObject* child) +static inline RenderObject* childBeforeConsideringContinuations(RenderInline* renderer, RenderObject* child) { - RenderBoxModelObject* curContainer = r; - RenderObject* cur = 0; - RenderObject* prev = 0; - - while (curContainer) { - if (curContainer->isRenderInline()) { - cur = curContainer->firstChild(); - while (cur) { - if (cur == child) - return prev; - prev = cur; - cur = cur->nextSibling(); + RenderObject* previous = nullptr; + for (RenderBoxModelObject* currentContainer = renderer; currentContainer; ) { + if (is<RenderInline>(*currentContainer)) { + auto* current = currentContainer->firstChild(); + while (current) { + if (current == child) + return previous; + previous = current; + current = current->nextSibling(); } - curContainer = toRenderInline(curContainer)->continuation(); - } else if (curContainer->isRenderBlock()) { - if (curContainer == child) - return prev; + currentContainer = downcast<RenderInline>(*currentContainer).continuation(); + } else if (is<RenderBlock>(*currentContainer)) { + if (currentContainer == child) + return previous; - prev = curContainer; - curContainer = toRenderBlock(curContainer)->inlineElementContinuation(); + previous = currentContainer; + currentContainer = downcast<RenderBlock>(*currentContainer).inlineElementContinuation(); } } ASSERT_NOT_REACHED(); - - return 0; + return nullptr; } -static inline bool firstChildIsInlineContinuation(RenderObject* renderer) +static inline bool firstChildIsInlineContinuation(RenderElement& renderer) { - return renderer->firstChild() && renderer->firstChild()->isInlineElementContinuation(); + RenderObject* child = renderer.firstChild(); + return child && child->isInlineElementContinuation(); } AccessibilityObject* AccessibilityRenderObject::previousSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* previousSibling = 0; + RenderObject* previousSibling = nullptr; // Case 1: The node is a block and is an inline's continuation. In that case, the inline's // last child is our previous sibling (or further back in the continuation chain) RenderInline* startOfConts; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + if (is<RenderBox>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer); // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before // the parent of the start, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(m_renderer)) { - RenderObject* firstParent = startOfContinuations(m_renderer->firstChild())->parent(); - while (firstChildIsInlineContinuation(firstParent)) - firstParent = startOfContinuations(firstParent->firstChild())->parent(); + else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderBlock& renderBlock = downcast<RenderBlock>(*m_renderer); + auto* firstParent = startOfContinuations(*renderBlock.firstChild())->parent(); + ASSERT(firstParent); + while (firstChildIsInlineContinuation(*firstParent)) + firstParent = startOfContinuations(*firstParent->firstChild())->parent(); previousSibling = firstParent->previousSibling(); } @@ -355,39 +341,41 @@ AccessibilityObject* AccessibilityRenderObject::previousSibling() const // Case 4: This node has no previous siblings, but its parent is an inline, // and is another node's inline continutation. Follow the continuation chain. - else if (m_renderer->parent()->isRenderInline() && (startOfConts = startOfContinuations(m_renderer->parent()))) + else if (is<RenderInline>(*m_renderer->parent()) && (startOfConts = startOfContinuations(*m_renderer->parent()))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild()); if (!previousSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(previousSibling); } -static inline bool lastChildHasContinuation(RenderObject* renderer) +static inline bool lastChildHasContinuation(RenderElement& renderer) { - return renderer->lastChild() && isInlineWithContinuation(renderer->lastChild()); + RenderObject* child = renderer.lastChild(); + return child && isInlineWithContinuation(*child); } AccessibilityObject* AccessibilityRenderObject::nextSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* nextSibling = 0; + RenderObject* nextSibling = nullptr; // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's // first child. RenderInline* inlineContinuation; - if (m_renderer->isRenderBlock() && (inlineContinuation = toRenderBlock(m_renderer)->inlineElementContinuation())) - nextSibling = firstChildConsideringContinuation(inlineContinuation); + if (is<RenderBlock>(*m_renderer) && (inlineContinuation = downcast<RenderBlock>(*m_renderer).inlineElementContinuation())) + nextSibling = firstChildConsideringContinuation(*inlineContinuation); // Case 2: Anonymous block parent of the start of a continuation - skip all the way to // after the parent of the end, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_renderer)) { - RenderObject* lastParent = endOfContinuations(m_renderer->lastChild())->parent(); - while (lastChildHasContinuation(lastParent)) - lastParent = endOfContinuations(lastParent->lastChild())->parent(); + else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderElement* lastParent = endOfContinuations(*downcast<RenderBlock>(*m_renderer).lastChild())->parent(); + ASSERT(lastParent); + while (lastChildHasContinuation(*lastParent)) + lastParent = endOfContinuations(*lastParent->lastChild())->parent(); nextSibling = lastParent->nextSibling(); } @@ -397,54 +385,53 @@ AccessibilityObject* AccessibilityRenderObject::nextSibling() const // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end // of the continuation chain. - else if (isInlineWithContinuation(m_renderer)) - nextSibling = endOfContinuations(m_renderer)->nextSibling(); + else if (isInlineWithContinuation(*m_renderer)) + nextSibling = endOfContinuations(*m_renderer)->nextSibling(); // Case 5: node has no next sibling, and its parent is an inline with a continuation. - else if (isInlineWithContinuation(m_renderer->parent())) { - RenderObject* continuation = toRenderInline(m_renderer->parent())->continuation(); + else if (isInlineWithContinuation(*m_renderer->parent())) { + auto& continuation = *downcast<RenderInline>(*m_renderer->parent()).continuation(); // Case 5a: continuation is a block - in this case the block itself is the next sibling. - if (continuation->isRenderBlock()) - nextSibling = continuation; + if (is<RenderBlock>(continuation)) + nextSibling = &continuation; // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling else nextSibling = firstChildConsideringContinuation(continuation); } if (!nextSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(nextSibling); } -static RenderBoxModelObject* nextContinuation(RenderObject* renderer) +static RenderBoxModelObject* nextContinuation(RenderObject& renderer) { - ASSERT(renderer); - if (renderer->isRenderInline() && !renderer->isReplaced()) - return toRenderInline(renderer)->continuation(); - if (renderer->isRenderBlock()) - return toRenderBlock(renderer)->inlineElementContinuation(); - return 0; + if (is<RenderInline>(renderer) && !renderer.isReplaced()) + return downcast<RenderInline>(renderer).continuation(); + if (is<RenderBlock>(renderer)) + return downcast<RenderBlock>(renderer).inlineElementContinuation(); + return nullptr; } RenderObject* AccessibilityRenderObject::renderParentObject() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* parent = m_renderer->parent(); + RenderElement* parent = m_renderer->parent(); // Case 1: node is a block and is an inline's continuation. Parent // is the start of the continuation chain. - RenderObject* startOfConts = 0; - RenderObject* firstChild = 0; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + RenderInline* startOfConts = nullptr; + RenderObject* firstChild = nullptr; + if (is<RenderBlock>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) parent = startOfConts; // Case 2: node's parent is an inline which is some node's continuation; parent is // the earliest node in the continuation chain. - else if (parent && parent->isRenderInline() && (startOfConts = startOfContinuations(parent))) + else if (is<RenderInline>(parent) && (startOfConts = startOfContinuations(*parent))) parent = startOfConts; // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation. @@ -452,15 +439,16 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const // Get the node's renderer and follow that continuation chain until the first child is found RenderObject* nodeRenderFirstChild = firstChild->node()->renderer(); while (nodeRenderFirstChild != firstChild) { - for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(contsTest)) { + for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(*contsTest)) { if (contsTest == firstChild) { parent = nodeRenderFirstChild->parent(); break; } } - if (firstChild == parent->firstChild()) + RenderObject* parentFirstChild = parent->firstChild(); + if (firstChild == parentFirstChild) break; - firstChild = parent->firstChild(); + firstChild = parentFirstChild; if (!firstChild->node()) break; nodeRenderFirstChild = firstChild->node()->renderer(); @@ -472,17 +460,21 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->get(m_renderer->frame()->view()); + if (isWebArea()) + return cache->get(&m_renderer->view().frameView()); - return axObjectCache()->get(renderParentObject()); + return cache->get(renderParentObject()); } AccessibilityObject* AccessibilityRenderObject::parentObject() const { if (!m_renderer) - return 0; + return nullptr; if (ariaRoleAttribute() == MenuBarRole) return axObjectCache()->getOrCreate(m_renderer->parent()); @@ -494,15 +486,19 @@ AccessibilityObject* AccessibilityRenderObject::parentObject() const return parent; } + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + RenderObject* parentObj = renderParentObject(); if (parentObj) - return axObjectCache()->getOrCreate(parentObj); + return cache->getOrCreate(parentObj); // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->getOrCreate(m_renderer->frame()->view()); + if (isWebArea()) + return cache->getOrCreate(&m_renderer->view().frameView()); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAttachment() const @@ -518,39 +514,20 @@ bool AccessibilityRenderObject::isAttachment() const bool AccessibilityRenderObject::isFileUploadButton() const { - if (m_renderer && m_renderer->node() && isHTMLInputElement(m_renderer->node())) { - HTMLInputElement* input = toHTMLInputElement(m_renderer->node()); - return input->isFileUpload(); + if (m_renderer && is<HTMLInputElement>(m_renderer->node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*m_renderer->node()); + return input.isFileUpload(); } return false; } - -bool AccessibilityRenderObject::isReadOnly() const -{ - ASSERT(m_renderer); - - if (isWebArea()) { - Document* document = m_renderer->document(); - if (!document) - return true; - - HTMLElement* body = document->body(); - if (body && body->rendererIsEditable()) - return false; - - return !document->rendererIsEditable(); - } - - return AccessibilityNodeObject::isReadOnly(); -} bool AccessibilityRenderObject::isOffScreen() const { ASSERT(m_renderer); - IntRect contentRect = pixelSnappedIntRect(m_renderer->absoluteClippedOverflowRect()); - FrameView* view = m_renderer->frame()->view(); - IntRect viewRect = view->visibleContentRect(); + IntRect contentRect = snappedIntRect(m_renderer->absoluteClippedOverflowRect()); + // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. + IntRect viewRect = m_renderer->view().frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); viewRect.intersect(contentRect); return viewRect.isEmpty(); } @@ -558,33 +535,34 @@ bool AccessibilityRenderObject::isOffScreen() const Element* AccessibilityRenderObject::anchorElement() const { if (!m_renderer) - return 0; + return nullptr; AXObjectCache* cache = axObjectCache(); - RenderObject* currRenderer; + if (!cache) + return nullptr; + + RenderObject* currentRenderer; // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. - for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) { - if (currRenderer->isAnonymousBlock()) { - RenderObject* continuation = toRenderBlock(currRenderer)->continuation(); - if (continuation) + for (currentRenderer = m_renderer; currentRenderer && !currentRenderer->node(); currentRenderer = currentRenderer->parent()) { + if (currentRenderer->isAnonymousBlock()) { + if (RenderObject* continuation = downcast<RenderBlock>(*currentRenderer).continuation()) return cache->getOrCreate(continuation)->anchorElement(); } } // bail if none found - if (!currRenderer) - return 0; + if (!currentRenderer) + return nullptr; // search up the DOM tree for an anchor element // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement - Node* node = currRenderer->node(); - for ( ; node; node = node->parentNode()) { - if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) - return toElement(node); + for (Node* node = currentRenderer->node(); node; node = node->parentNode()) { + if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink())) + return downcast<Element>(node); } - return 0; + return nullptr; } String AccessibilityRenderObject::helpText() const @@ -601,21 +579,22 @@ String AccessibilityRenderObject::helpText() const return describedBy; String description = accessibilityDescription(); - for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { - if (curr->node() && curr->node()->isHTMLElement()) { - const AtomicString& summary = toElement(curr->node())->getAttribute(summaryAttr); + for (RenderObject* ancestor = m_renderer; ancestor; ancestor = ancestor->parent()) { + if (is<HTMLElement>(ancestor->node())) { + HTMLElement& element = downcast<HTMLElement>(*ancestor->node()); + const AtomicString& summary = element.getAttribute(summaryAttr); if (!summary.isEmpty()) return summary; // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = toElement(curr->node())->getAttribute(titleAttr); + const AtomicString& title = element.getAttribute(titleAttr); if (!title.isEmpty() && description != title) return title; } // Only take help text from an ancestor element if its a group or an unknown role. If help was // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor); if (axObj) { AccessibilityRole role = axObj->roleValue(); if (role != GroupRole && role != UnknownRole) @@ -631,54 +610,127 @@ String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElement if (!m_renderer) return String(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->buttonValue(); + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).buttonValue(); + // Reflect when a content author has explicitly marked a line break. + if (m_renderer->isBR()) + return ASCIILiteral("\n"); + + bool isRenderText = is<RenderText>(*m_renderer); + #if ENABLE(MATHML) // Math operators create RenderText nodes on the fly that are not tied into the DOM in a reasonable way, // so rangeOfContents does not work for them (nor does regular text selection). - if (m_renderer->isText() && isMathElement()) { - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->isRenderMathMLBlock() && toRenderMathMLBlock(parent)->isRenderMathMLOperator()) - return toRenderText(m_renderer)->text(); - } - } + if (isRenderText && m_renderer->isAnonymous() && ancestorsOfType<RenderMathMLOperator>(*m_renderer).first()) + return downcast<RenderText>(*m_renderer).text(); + if (is<RenderMathMLOperator>(*m_renderer) && !m_renderer->isAnonymous()) + return downcast<RenderMathMLOperator>(*m_renderer).element().textContent(); #endif + if (shouldGetTextFromNode(mode)) + return AccessibilityNodeObject::textUnderElement(mode); + // We use a text iterator for text objects AND for those cases where we are // explicitly asking for the full text under a given element. - if (m_renderer->isText() || mode == TextUnderElementModeIncludeAllChildren) { + bool shouldIncludeAllChildren = mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + if (isRenderText || shouldIncludeAllChildren) { // If possible, use a text iterator to get the text, so that whitespace // is handled consistently. - if (Node* node = this->node()) { - if (Frame* frame = node->document()->frame()) { + Document* nodeDocument = nullptr; + RefPtr<Range> textRange; + if (Node* node = m_renderer->node()) { + nodeDocument = &node->document(); + textRange = rangeOfContents(*node); + } else { + // For anonymous blocks, we work around not having a direct node to create a range from + // defining one based in the two external positions defining the boundaries of the subtree. + RenderObject* firstChildRenderer = m_renderer->firstChildSlow(); + RenderObject* lastChildRenderer = m_renderer->lastChildSlow(); + if (firstChildRenderer && lastChildRenderer) { + ASSERT(firstChildRenderer->node()); + ASSERT(lastChildRenderer->node()); + + // We define the start and end positions for the range as the ones right before and after + // the first and the last nodes in the DOM tree that is wrapped inside the anonymous block. + Node* firstNodeInBlock = firstChildRenderer->node(); + Position startPosition = positionInParentBeforeNode(firstNodeInBlock); + Position endPosition = positionInParentAfterNode(lastChildRenderer->node()); + + nodeDocument = &firstNodeInBlock->document(); + textRange = Range::create(*nodeDocument, startPosition, endPosition); + } + } + + if (nodeDocument && textRange) { + if (Frame* frame = nodeDocument->frame()) { // catch stale WebCoreAXObject (see <rdar://problem/3960196>) - if (frame->document() != node->document()) + if (frame->document() != nodeDocument) return String(); - return plainText(rangeOfContents(node).get(), textIteratorBehaviorForTextRange()); + // The tree should be stable before looking through the children of a non-Render Text object. + // Otherwise, further uses of TextIterator will force a layout update, potentially altering + // the accessibility tree and causing crashes in the loop that computes the result text. + ASSERT((isRenderText || !shouldIncludeAllChildren) || (!nodeDocument->renderView()->layoutState() && !nodeDocument->childNeedsStyleRecalc())); + + return plainText(textRange.get(), textIteratorBehaviorForTextRange()); } } // Sometimes text fragments don't have Nodes associated with them (like when // CSS content is used to insert text or when a RenderCounter is used.) - if (m_renderer->isText()) { - RenderText* renderTextObject = toRenderText(m_renderer); - if (renderTextObject->isTextFragment()) - return String(static_cast<RenderTextFragment*>(m_renderer)->contentString()); + if (is<RenderText>(*m_renderer)) { + RenderText& renderTextObject = downcast<RenderText>(*m_renderer); + if (is<RenderTextFragment>(renderTextObject)) { + RenderTextFragment& renderTextFragment = downcast<RenderTextFragment>(renderTextObject); + // The alt attribute may be set on a text fragment through CSS, which should be honored. + const String& altText = renderTextFragment.altText(); + if (!altText.isEmpty()) + return altText; + return renderTextFragment.contentString(); + } - return String(renderTextObject->text()); + return renderTextObject.text(); } } return AccessibilityNodeObject::textUnderElement(mode); } +bool AccessibilityRenderObject::shouldGetTextFromNode(AccessibilityTextUnderElementMode mode) const +{ + if (!m_renderer) + return false; + + // AccessibilityRenderObject::textUnderElement() gets the text of anonymous blocks by using + // the child nodes to define positions. CSS tables and their anonymous descendants lack + // children with nodes. + if (m_renderer->isAnonymous() && m_renderer->isTablePart()) + return mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + + // AccessibilityRenderObject::textUnderElement() calls rangeOfContents() to create the text + // range. rangeOfContents() does not include CSS-generated content. + if (m_renderer->isBeforeOrAfterContent()) + return true; + if (Node* node = m_renderer->node()) { + Node* firstChild = node->pseudoAwareFirstChild(); + Node* lastChild = node->pseudoAwareLastChild(); + if ((firstChild && firstChild->isPseudoElement()) || (lastChild && lastChild->isPseudoElement())) + return true; + } + + return false; +} + Node* AccessibilityRenderObject::node() const -{ - return m_renderer ? m_renderer->node() : 0; +{ + if (!m_renderer) + return nullptr; + if (m_renderer->isRenderView()) + return &m_renderer->document(); + return m_renderer->node(); } - + String AccessibilityRenderObject::stringValue() const { if (!m_renderer) @@ -696,39 +748,34 @@ String AccessibilityRenderObject::stringValue() const return staticText; } - if (m_renderer->isText()) + if (is<RenderText>(*m_renderer)) return textUnderElement(); - if (cssBox && cssBox->isMenuList()) { + if (is<RenderMenuList>(cssBox)) { // RenderMenuList will go straight to the text() of its selected item. // This has to be overridden in the case where the selected item has an ARIA label. - HTMLSelectElement* selectElement = toHTMLSelectElement(m_renderer->node()); - int selectedIndex = selectElement->selectedIndex(); - const Vector<HTMLElement*> listItems = selectElement->listItems(); + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*m_renderer->node()); + int selectedIndex = selectElement.selectedIndex(); + const Vector<HTMLElement*>& listItems = selectElement.listItems(); if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); if (!overriddenDescription.isNull()) return overriddenDescription; } - return toRenderMenuList(m_renderer)->text(); + return downcast<RenderMenuList>(*m_renderer).text(); } - if (m_renderer->isListMarker()) - return toRenderListMarker(m_renderer)->text(); + if (is<RenderListMarker>(*m_renderer)) + return downcast<RenderListMarker>(*m_renderer).text(); - if (isWebArea()) { - // FIXME: Why would a renderer exist when the Document isn't attached to a frame? - if (m_renderer->frame()) - return String(); - - ASSERT_NOT_REACHED(); - } + if (isWebArea()) + return String(); if (isTextControl()) return text(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->fileTextValue(); + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).fileTextValue(); // FIXME: We might need to implement a value here for more types // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; @@ -740,19 +787,19 @@ String AccessibilityRenderObject::stringValue() const HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const { if (!m_renderer) - return 0; + return nullptr; // the control element should not be considered part of the label if (isControl()) - return 0; + return nullptr; // find if this has a parent that is a label for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) { - if (isHTMLLabelElement(parentNode)) - return toHTMLLabelElement(parentNode); + if (is<HTMLLabelElement>(*parentNode)) + return downcast<HTMLLabelElement>(parentNode); } - return 0; + return nullptr; } // The boundingBox for elements within the remote SVG element needs to be offset by its position @@ -782,28 +829,26 @@ LayoutRect AccessibilityRenderObject::boundingBoxRect() const // We should also use absoluteQuads for SVG elements, otherwise transforms won't be applied. Vector<FloatQuad> quads; bool isSVGRoot = false; -#if ENABLE(SVG) + if (obj->isSVGRoot()) isSVGRoot = true; -#endif - if (obj->isText()) - toRenderText(obj)->absoluteQuads(quads, 0, RenderText::ClipToEllipsis); - else if (isWebArea() || isSeamlessWebArea() || isSVGRoot) + + if (is<RenderText>(*obj)) + quads = downcast<RenderText>(*obj).absoluteQuadsClippedToEllipsis(); + else if (isWebArea() || isSVGRoot) obj->absoluteQuads(quads); else obj->absoluteFocusRingQuads(quads); LayoutRect result = boundingBoxForQuads(obj, quads); -#if ENABLE(SVG) Document* document = this->document(); if (document && document->isSVGDocument()) offsetBoundingBoxForRemoteSVGElement(result); -#endif // The size of the web area should be the content size, not the clipped size. - if ((isWebArea() || isSeamlessWebArea()) && obj->frame()->view()) - result.setSize(obj->frame()->view()->contentsSize()); + if (isWebArea()) + result.setSize(obj->view().frameView().contentsSize()); return result; } @@ -813,7 +858,7 @@ LayoutRect AccessibilityRenderObject::checkboxOrRadioRect() const if (!m_renderer) return LayoutRect(); - HTMLLabelElement* label = labelForElement(toElement(m_renderer->node())); + HTMLLabelElement* label = labelForElement(downcast<Element>(m_renderer->node())); if (!label || !label->renderer()) return boundingBoxRect(); @@ -833,32 +878,22 @@ LayoutRect AccessibilityRenderObject::elementRect() const bool AccessibilityRenderObject::supportsPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape()) - return true; -#endif - - return false; + return is<RenderSVGShape>(m_renderer); } Path AccessibilityRenderObject::elementPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape() && toRenderSVGShape(m_renderer)->hasPath()) { - Path path = toRenderSVGShape(m_renderer)->path(); + if (is<RenderSVGShape>(m_renderer) && downcast<RenderSVGShape>(*m_renderer).hasPath()) { + Path path = downcast<RenderSVGShape>(*m_renderer).path(); // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates. - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->isSVGRoot()) { - LayoutPoint parentOffset = axObjectCache()->getOrCreate(parent)->elementRect().location(); - path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y())); - break; - } + if (auto svgRoot = ancestorsOfType<RenderSVGRoot>(*m_renderer).first()) { + LayoutPoint parentOffset = axObjectCache()->getOrCreate(&*svgRoot)->elementRect().location(); + path.transform(AffineTransform().translate(parentOffset.x(), parentOffset.y())); } return path; } -#endif return Path(); } @@ -870,14 +905,14 @@ IntPoint AccessibilityRenderObject::clickPoint() return children()[0]->clickPoint(); // use the default position unless this is an editable web area, in which case we use the selection bounds. - if (!isWebArea() || isReadOnly()) + if (!isWebArea() || !canSetValueAttribute()) return AccessibilityObject::clickPoint(); VisibleSelection visSelection = selection(); VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd()); IntRect bounds = boundsForVisiblePositionRange(range); -#if PLATFORM(MAC) - bounds.setLocation(m_renderer->document()->view()->screenToContents(bounds.location())); +#if PLATFORM(COCOA) + bounds.setLocation(m_renderer->view().frameView().screenToContents(bounds.location())); #endif return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2)); } @@ -885,27 +920,24 @@ IntPoint AccessibilityRenderObject::clickPoint() AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const { Element* element = anchorElement(); - if (!element) - return 0; - // Right now, we do not support ARIA links as internal link elements - if (!isHTMLAnchorElement(element)) - return 0; - HTMLAnchorElement* anchor = toHTMLAnchorElement(element); + if (!is<HTMLAnchorElement>(element)) + return nullptr; + HTMLAnchorElement& anchor = downcast<HTMLAnchorElement>(*element); - KURL linkURL = anchor->href(); + URL linkURL = anchor.href(); String fragmentIdentifier = linkURL.fragmentIdentifier(); if (fragmentIdentifier.isEmpty()) - return 0; + return nullptr; // check if URL is the same as current URL - KURL documentURL = m_renderer->document()->url(); + URL documentURL = m_renderer->document().url(); if (!equalIgnoringFragmentIdentifier(documentURL, linkURL)) - return 0; + return nullptr; - Node* linkedNode = m_renderer->document()->findAnchor(fragmentIdentifier); + Node* linkedNode = m_renderer->document().findAnchor(fragmentIdentifier); if (!linkedNode) - return 0; + return nullptr; // The element we find may not be accessible, so find the first accessible object. return firstAccessibleObjectFromNode(linkedNode); @@ -916,7 +948,7 @@ ESpeak AccessibilityRenderObject::speakProperty() const if (!m_renderer) return AccessibilityObject::speakProperty(); - return m_renderer->style()->speak(); + return m_renderer->style().speak(); } void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const @@ -925,31 +957,21 @@ void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildren return; Node* node = m_renderer->node(); - if (!node || !isHTMLInputElement(node)) + if (!is<HTMLInputElement>(node)) return; - HTMLInputElement* input = toHTMLInputElement(node); + HTMLInputElement& input = downcast<HTMLInputElement>(*node); // if there's a form, then this is easy - if (input->form()) { - Vector<RefPtr<Node> > formElements; - input->form()->getNamedElements(input->name(), formElements); - - unsigned len = formElements.size(); - for (unsigned i = 0; i < len; ++i) { - Node* associateElement = formElements[i].get(); - if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement)) + if (input.form()) { + for (auto& associateElement : input.form()->namedElements(input.name())) { + if (AccessibilityObject* object = axObjectCache()->getOrCreate(&associateElement.get())) linkedUIElements.append(object); } } else { - RefPtr<NodeList> list = node->document()->getElementsByTagName("input"); - unsigned len = list->length(); - for (unsigned i = 0; i < len; ++i) { - if (isHTMLInputElement(list->item(i))) { - HTMLInputElement* associateElement = toHTMLInputElement(list->item(i)); - if (associateElement->isRadioButton() && associateElement->name() == input->name()) { - if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement)) - linkedUIElements.append(object); - } + for (auto& associateElement : descendantsOfType<HTMLInputElement>(node->document())) { + if (associateElement.isRadioButton() && associateElement.name() == input.name()) { + if (AccessibilityObject* object = axObjectCache()->getOrCreate(&associateElement)) + linkedUIElements.append(object); } } } @@ -961,7 +983,7 @@ void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& li { ariaFlowToElements(linkedUIElements); - if (isAnchor()) { + if (isLink()) { AccessibilityObject* linkedAXElement = internalLinkElement(); if (linkedAXElement) linkedUIElements.append(linkedAXElement); @@ -975,10 +997,7 @@ bool AccessibilityRenderObject::hasTextAlternative() const { // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should // override the "label" element association. - if (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).isEmpty()) - return true; - - return false; + return ariaAccessibilityDescription().length(); } bool AccessibilityRenderObject::ariaHasPopup() const @@ -986,6 +1005,17 @@ bool AccessibilityRenderObject::ariaHasPopup() const return elementAttributeValue(aria_haspopupAttr); } +void AccessibilityRenderObject::ariaElementsFromAttribute(AccessibilityChildrenVector& children, const QualifiedName& attributeName) const +{ + Vector<Element*> elements; + elementsFromAttribute(elements, attributeName); + AXObjectCache* cache = axObjectCache(); + for (const auto& element : elements) { + if (AccessibilityObject* axObject = cache->getOrCreate(element)) + children.append(axObject); + } +} + bool AccessibilityRenderObject::supportsARIAFlowTo() const { return !getAttribute(aria_flowtoAttr).isEmpty(); @@ -993,20 +1023,29 @@ bool AccessibilityRenderObject::supportsARIAFlowTo() const void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const { - Vector<Element*> elements; - elementsFromAttribute(elements, aria_flowtoAttr); - - AXObjectCache* cache = axObjectCache(); - unsigned count = elements.size(); - for (unsigned k = 0; k < count; ++k) { - Element* element = elements[k]; - AccessibilityObject* flowToElement = cache->getOrCreate(element); - if (flowToElement) - flowTo.append(flowToElement); - } - + ariaElementsFromAttribute(flowTo, aria_flowtoAttr); } - + +bool AccessibilityRenderObject::supportsARIADescribedBy() const +{ + return !getAttribute(aria_describedbyAttr).isEmpty(); +} + +void AccessibilityRenderObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const +{ + ariaElementsFromAttribute(ariaDescribedBy, aria_describedbyAttr); +} + +bool AccessibilityRenderObject::supportsARIAControls() const +{ + return !getAttribute(aria_controlsAttr).isEmpty(); +} + +void AccessibilityRenderObject::ariaControlsElements(AccessibilityChildrenVector& ariaControls) const +{ + ariaElementsFromAttribute(ariaControls, aria_controlsAttr); +} + bool AccessibilityRenderObject::supportsARIADropping() const { const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); @@ -1016,7 +1055,7 @@ bool AccessibilityRenderObject::supportsARIADropping() const bool AccessibilityRenderObject::supportsARIADragging() const { const AtomicString& grabbed = getAttribute(aria_grabbedAttr); - return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false"); + return equalLettersIgnoringASCIICase(grabbed, "true") || equalLettersIgnoringASCIICase(grabbed, "false"); } bool AccessibilityRenderObject::isARIAGrabbed() @@ -1047,12 +1086,6 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const if (accessibilityIsIgnored()) return true; - // Checkboxes and radio buttons use the text of their title ui element as their own AXTitle. - // This code controls whether the title ui element should appear in the AX tree (usually, no). - // It should appear if the control already has a label (which will be used as the AXTitle instead). - if (isCheckboxOrRadio()) - return hasTextAlternative(); - // When controls have their own descriptions, the title element should be ignored. if (hasTextAlternative()) return false; @@ -1063,20 +1096,20 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const AccessibilityObject* AccessibilityRenderObject::titleUIElement() const { if (!m_renderer) - return 0; + return nullptr; // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset if (isFieldset()) - return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); + return axObjectCache()->getOrCreate(downcast<RenderFieldset>(*m_renderer).findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) - return 0; - HTMLLabelElement* label = labelForElement(toElement(node)); + if (!is<Element>(node)) + return nullptr; + HTMLLabelElement* label = labelForElement(downcast<Element>(node)); if (label && label->renderer()) return axObjectCache()->getOrCreate(label); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAllowedChildOfTree() const @@ -1084,7 +1117,10 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. AccessibilityObject* axObj = parentObject(); bool isInTree = false; + bool isTreeItemDescendant = false; while (axObj) { + if (axObj->roleValue() == TreeItemRole) + isTreeItemDescendant = true; if (axObj->isTree()) { isInTree = true; break; @@ -1095,12 +1131,25 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // If the object is in a tree, only tree items should be exposed (and the children of tree items). if (isInTree) { AccessibilityRole role = roleValue(); - if (role != TreeItemRole && role != StaticTextRole) + if (role != TreeItemRole && role != StaticTextRole && !isTreeItemDescendant) return false; } return true; } +static AccessibilityObjectInclusion objectInclusionFromAltText(const String& altText) +{ + // Don't ignore an image that has an alt tag. + if (!altText.containsOnlyWhitespace()) + return IncludeObject; + + // The informal standard is to ignore images with zero-length alt strings. + if (!altText.isNull()) + return IgnoreObject; + + return DefaultBehavior; +} + AccessibilityObjectInclusion AccessibilityRenderObject::defaultObjectInclusion() const { // The following cases can apply to any element that's a subclass of AccessibilityRenderObject. @@ -1108,9 +1157,9 @@ AccessibilityObjectInclusion AccessibilityRenderObject::defaultObjectInclusion() if (!m_renderer) return IgnoreObject; - if (m_renderer->style()->visibility() != VISIBLE) { + if (m_renderer->style().visibility() != VISIBLE) { // aria-hidden is meant to override visibility as the determinant in AX hierarchy inclusion. - if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_hiddenAttr), "false")) return DefaultBehavior; return IgnoreObject; @@ -1125,6 +1174,9 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const ASSERT(m_initialized); #endif + if (!m_renderer) + return true; + // Check first if any of the common reasons cause this element to be ignored. // Then process other use cases that need to be applied to all the various roles // that AccessibilityRenderObjects take on. @@ -1153,25 +1205,25 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return accessibilityIgnoreAttachment(); // ignore popup menu items because AppKit does - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->isBoxModelObject() && toRenderBoxModelObject(parent)->isMenuList()) - return true; - } + if (m_renderer && ancestorsOfType<RenderMenuList>(*m_renderer).first()) + return true; // find out if this element is inside of a label element. // if so, it may be ignored because it's the label for a checkbox or radio button AccessibilityObject* controlObject = correspondingControlForLabelElement(); if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio()) return true; - - // NOTE: BRs always have text boxes now, so the text box check here can be removed - if (m_renderer->isText()) { + + if (m_renderer->isBR()) + return true; + + if (is<RenderText>(*m_renderer)) { // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level AccessibilityObject* parent = parentObjectUnignored(); - if (parent && (parent->ariaRoleAttribute() == MenuItemRole || parent->ariaRoleAttribute() == MenuButtonRole)) + if (parent && (parent->isMenuItem() || parent->ariaRoleAttribute() == MenuButtonRole)) return true; - RenderText* renderText = toRenderText(m_renderer); - if (m_renderer->isBR() || !renderText->firstTextBox()) + auto& renderText = downcast<RenderText>(*m_renderer); + if (!renderText.hasRenderedText()) return true; // static text beneath TextControls is reported along with the text control text so it's ignored. @@ -1179,9 +1231,18 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (parent->roleValue() == TextFieldRole) return true; } + + // The alt attribute may be set on a text fragment through CSS, which should be honored. + if (is<RenderTextFragment>(renderText)) { + AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(downcast<RenderTextFragment>(renderText).altText()); + if (altTextInclusion == IgnoreObject) + return true; + if (altTextInclusion == IncludeObject) + return false; + } // text elements that are just empty whitespace should not be returned - return renderText->text()->containsOnlyWhitespace(); + return renderText.text()->containsOnlyWhitespace(); } if (isHeading()) @@ -1190,27 +1251,44 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isLink()) return false; + if (isLandmark()) + return false; + // all controls are accessible if (isControl()) return false; + + switch (roleValue()) { + case AudioRole: + case DescriptionListTermRole: + case DescriptionListDetailRole: + case DetailsRole: + case DocumentArticleRole: + case DocumentRegionRole: + case ListItemRole: + case VideoRole: + return false; + default: + break; + } if (ariaRoleAttribute() != UnknownRole) return false; - + + if (roleValue() == HorizontalRuleRole) + return false; + // don't ignore labels, because they serve as TitleUIElements Node* node = m_renderer->node(); - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return false; // Anything that is content editable should not be ignored. - // However, one cannot just call node->rendererIsEditable() since that will ask if its parents + // However, one cannot just call node->hasEditableStyle() since that will ask if its parents // are also editable. Only the top level content editable region should be exposed. if (hasContentEditableAttributeSet()) return false; - // List items play an important role in defining the structure of lists. They should not be ignored. - if (roleValue() == ListItemRole) - return false; // if this element has aria attributes on it, it should not be ignored. if (supportsARIAAttributes()) @@ -1225,17 +1303,8 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return false; #endif - // <span> tags are inline tags and not meant to convey information if they have no other aria - // information on them. If we don't ignore them, they may emit signals expected to come from - // their parent. In addition, because included spans are GroupRole objects, and GroupRole - // objects are often containers with meaningful information, the inclusion of a span can have - // the side effect of causing the immediate parent accessible to be ignored. This is especially - // problematic for platforms which have distinct roles for textual block elements. - if (node && node->hasTagName(spanTag)) - return true; - - if (m_renderer->isBlockFlow() && m_renderer->childrenInline() && !canSetFocusAttribute()) - return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener(); + if (is<RenderBlockFlow>(*m_renderer) && m_renderer->childrenInline() && !canSetFocusAttribute()) + return !downcast<RenderBlockFlow>(*m_renderer).hasLines() && !mouseButtonListener(); // ignore images seemingly used as spacers if (isImage()) { @@ -1244,29 +1313,33 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (canSetFocusAttribute()) return false; - if (node && node->isElementNode()) { - Element* elt = toElement(node); - const AtomicString& alt = elt->getAttribute(altAttr); - // don't ignore an image that has an alt tag - if (!alt.string().containsOnlyWhitespace()) - return false; - // informal standard is to ignore images with zero-length alt strings - if (!alt.isNull()) - return true; - // If an image has a title attribute on it, accessibility should be lenient and allow it to appear in the hierarchy (according to WAI-ARIA). - if (!getAttribute(titleAttr).isEmpty()) - return false; - } + // First check the RenderImage's altText (which can be set through a style sheet, or come from the Element). + // However, if this is not a native image, fallback to the attribute on the Element. + AccessibilityObjectInclusion altTextInclusion = DefaultBehavior; + bool isRenderImage = is<RenderImage>(m_renderer); + if (isRenderImage) + altTextInclusion = objectInclusionFromAltText(downcast<RenderImage>(*m_renderer).altText()); + else + altTextInclusion = objectInclusionFromAltText(getAttribute(altAttr).string()); + + if (altTextInclusion == IgnoreObject) + return true; + if (altTextInclusion == IncludeObject) + return false; - if (isNativeImage()) { + // If an image has a title attribute on it, accessibility should be lenient and allow it to appear in the hierarchy (according to WAI-ARIA). + if (!getAttribute(titleAttr).isEmpty()) + return false; + + if (isRenderImage) { // check for one-dimensional image - RenderImage* image = toRenderImage(m_renderer); - if (image->height() <= 1 || image->width() <= 1) + RenderImage& image = downcast<RenderImage>(*m_renderer); + if (image.height() <= 1 || image.width() <= 1) return true; // check whether rendered image was stretched from one-dimensional file image - if (image->cachedImage()) { - LayoutSize imageSize = image->cachedImage()->imageSizeForRenderer(m_renderer, image->view()->zoomFactor()); + if (image.cachedImage()) { + LayoutSize imageSize = image.cachedImage()->imageSizeForRenderer(&image, image.view().zoomFactor()); return imageSize.height() <= 1 || imageSize.width() <= 1; } } @@ -1277,26 +1350,34 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (canvasHasFallbackContent()) return false; - if (m_renderer->isBox()) { - RenderBox* canvasBox = toRenderBox(m_renderer); - if (canvasBox->height() <= 1 || canvasBox->width() <= 1) + if (is<RenderBox>(*m_renderer)) { + auto& canvasBox = downcast<RenderBox>(*m_renderer); + if (canvasBox.height() <= 1 || canvasBox.width() <= 1) return true; } // Otherwise fall through; use presence of help text, title, or description to decide. } - if (isWebArea() || isSeamlessWebArea() || m_renderer->isListMarker()) + if (m_renderer->isListMarker()) { + AccessibilityObject* parent = parentObjectUnignored(); + return parent && !parent->isListItem(); + } + + if (isWebArea()) return false; - // Using the help text, title or accessibility description (so we - // check if there's some kind of accessible name for the element) - // to decide an element's visibility is not as definitive as - // previous checks, so this should remain as one of the last. - // - // These checks are simplified in the interest of execution speed; - // for example, any element having an alt attribute will make it - // not ignored, rather than just images. - if (!getAttribute(aria_helpAttr).isEmpty() || !getAttribute(aria_describedbyAttr).isEmpty() || !getAttribute(altAttr).isEmpty() || !getAttribute(titleAttr).isEmpty()) +#if ENABLE(METER_ELEMENT) + // The render tree of meter includes a RenderBlock (meter) and a RenderMeter (div). + // We expose the latter and thus should ignore the former. However, if the author + // includes a title attribute on the element, hasAttributesRequiredForInclusion() + // will return true, potentially resulting in a redundant accessible object. + if (is<HTMLMeterElement>(node)) + return true; +#endif + + // Using the presence of an accessible name to decide an element's visibility is not + // as definitive as previous checks, so this should remain as one of the last. + if (hasAttributesRequiredForInclusion()) return false; // Don't ignore generic focusable elements like <div tabindex=0> @@ -1304,26 +1385,34 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isGenericFocusableElement() && node->firstChild()) return false; - if (!ariaAccessibilityDescription().isEmpty()) - return false; - -#if ENABLE(MATHML) - if (!getAttribute(MathMLNames::alttextAttr).isEmpty()) - return false; -#endif + // <span> tags are inline tags and not meant to convey information if they have no other aria + // information on them. If we don't ignore them, they may emit signals expected to come from + // their parent. In addition, because included spans are GroupRole objects, and GroupRole + // objects are often containers with meaningful information, the inclusion of a span can have + // the side effect of causing the immediate parent accessible to be ignored. This is especially + // problematic for platforms which have distinct roles for textual block elements. + if (node && node->hasTagName(spanTag)) + return true; // Other non-ignored host language elements if (node && node->hasTagName(dfnTag)) return false; - // By default, objects should be ignored so that the AX hierarchy is not + if (isStyleFormatGroup()) + return false; + + // Make sure that ruby containers are not ignored. + if (m_renderer->isRubyRun() || m_renderer->isRubyBlock() || m_renderer->isRubyInline()) + return false; + + // By default, objects should be ignored so that the AX hierarchy is not // filled with unnecessary items. return true; } bool AccessibilityRenderObject::isLoaded() const { - return !m_renderer->document()->parser(); + return !m_renderer->document().parser(); } double AccessibilityRenderObject::estimatedLoadingProgress() const @@ -1334,18 +1423,18 @@ double AccessibilityRenderObject::estimatedLoadingProgress() const if (isLoaded()) return 1.0; - Page* page = m_renderer->document()->page(); + Page* page = m_renderer->document().page(); if (!page) return 0; - return page->progress()->estimatedProgress(); + return page->progress().estimatedProgress(); } int AccessibilityRenderObject::layoutCount() const { - if (!m_renderer->isRenderView()) + if (!is<RenderView>(*m_renderer)) return 0; - return toRenderView(m_renderer)->frameView()->layoutCount(); + return downcast<RenderView>(*m_renderer).frameView().layoutCount(); } String AccessibilityRenderObject::text() const @@ -1361,16 +1450,12 @@ int AccessibilityRenderObject::textLength() const ASSERT(isTextControl()); if (isPasswordField()) -#if PLATFORM(GTK) return passwordFieldValue().length(); -#else - return -1; // need to return something distinct from 0 -#endif return text().length(); } -PlainTextRange AccessibilityRenderObject::ariaSelectedTextRange() const +PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const { Node* node = m_renderer->node(); if (!node) @@ -1395,29 +1480,24 @@ String AccessibilityRenderObject::selectedText() const return String(); // need to return something distinct from empty string if (isNativeTextControl()) { - HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer)->textFormControlElement(); - return textControl->selectedText(); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); + return textControl.selectedText(); } - if (ariaRoleAttribute() == UnknownRole) - return String(); - - return doAXStringForRange(ariaSelectedTextRange()); + return doAXStringForRange(documentBasedSelectedTextRange()); } const AtomicString& AccessibilityRenderObject::accessKey() const { Node* node = m_renderer->node(); - if (!node) - return nullAtom; - if (!node->isElementNode()) + if (!is<Element>(node)) return nullAtom; - return toElement(node)->getAttribute(accesskeyAttr); + return downcast<Element>(*node).getAttribute(accesskeyAttr); } VisibleSelection AccessibilityRenderObject::selection() const { - return m_renderer->frame()->selection()->selection(); + return m_renderer->frame().selection().selection(); } PlainTextRange AccessibilityRenderObject::selectedTextRange() const @@ -1428,65 +1508,78 @@ PlainTextRange AccessibilityRenderObject::selectedTextRange() const return PlainTextRange(); AccessibilityRole ariaRole = ariaRoleAttribute(); - if (isNativeTextControl() && ariaRole == UnknownRole) { - HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer)->textFormControlElement(); - return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); + // Use the text control native range if it's a native object and it has no ARIA role (or has a text based ARIA role). + if (isNativeTextControl() && (ariaRole == UnknownRole || isARIATextControl())) { + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); + return PlainTextRange(textControl.selectionStart(), textControl.selectionEnd() - textControl.selectionStart()); } - if (ariaRole == UnknownRole) - return PlainTextRange(); - - return ariaSelectedTextRange(); + return documentBasedSelectedTextRange(); +} + +static void setTextSelectionIntent(AXObjectCache* cache, AXTextStateChangeType type) +{ + if (!cache) + return; + AXTextStateChangeIntent intent(type, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }); + cache->setTextSelectionIntent(intent); + cache->setIsSynchronizingSelection(true); +} + +static void clearTextSelectionIntent(AXObjectCache* cache) +{ + if (!cache) + return; + cache->setTextSelectionIntent(AXTextStateChangeIntent()); + cache->setIsSynchronizingSelection(false); } void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) { if (isNativeTextControl()) { - HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer)->textFormControlElement(); - textControl->setSelectionRange(range.start, range.start + range.length); + setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeTypeSelectionExtend : AXTextStateChangeTypeSelectionMove); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); + textControl.setSelectionRange(range.start, range.start + range.length); + clearTextSelectionIntent(axObjectCache()); return; } - Document* document = m_renderer->document(); - if (!document) - return; - Frame* frame = document->frame(); - if (!frame) - return; Node* node = m_renderer->node(); - frame->selection()->setSelection(VisibleSelection(Position(node, range.start, Position::PositionIsOffsetInAnchor), - Position(node, range.start + range.length, Position::PositionIsOffsetInAnchor), DOWNSTREAM)); + VisibleSelection newSelection(Position(node, range.start, Position::PositionIsOffsetInAnchor), Position(node, range.start + range.length, Position::PositionIsOffsetInAnchor), DOWNSTREAM); + setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeTypeSelectionExtend : AXTextStateChangeTypeSelectionMove); + m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions()); + clearTextSelectionIntent(axObjectCache()); } -KURL AccessibilityRenderObject::url() const +URL AccessibilityRenderObject::url() const { - if (isAnchor() && isHTMLAnchorElement(m_renderer->node())) { - if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement())) + if (isLink() && is<HTMLAnchorElement>(*m_renderer->node())) { + if (HTMLAnchorElement* anchor = downcast<HTMLAnchorElement>(anchorElement())) return anchor->href(); } if (isWebArea()) - return m_renderer->document()->url(); + return m_renderer->document().url(); - if (isImage() && m_renderer->node() && isHTMLImageElement(m_renderer->node())) - return toHTMLImageElement(m_renderer->node())->src(); + if (isImage() && is<HTMLImageElement>(m_renderer->node())) + return downcast<HTMLImageElement>(*m_renderer->node()).src(); if (isInputImage()) - return toHTMLInputElement(m_renderer->node())->src(); + return downcast<HTMLInputElement>(*m_renderer->node()).src(); - return KURL(); + return URL(); } bool AccessibilityRenderObject::isUnvisited() const { // FIXME: Is it a privacy violation to expose unvisited information to accessibility APIs? - return m_renderer->style()->isLink() && m_renderer->style()->insideLink() == InsideUnvisitedLink; + return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideUnvisitedLink; } bool AccessibilityRenderObject::isVisited() const { // FIXME: Is it a privacy violation to expose visited information to accessibility APIs? - return m_renderer->style()->isLink() && m_renderer->style()->insideLink() == InsideVisitedLink; + return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideVisitedLink; } void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& attributeName, bool value) @@ -1495,11 +1588,10 @@ void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& at return; Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return; - Element* element = toElement(node); - element->setAttribute(attributeName, (value) ? "true" : "false"); + downcast<Element>(*node).setAttribute(attributeName, (value) ? "true" : "false"); } bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const @@ -1507,7 +1599,7 @@ bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attri if (!m_renderer) return false; - return equalIgnoringCase(getAttribute(attributeName), "true"); + return equalLettersIgnoringASCIICase(getAttribute(attributeName), "true"); } bool AccessibilityRenderObject::isSelected() const @@ -1515,12 +1607,10 @@ bool AccessibilityRenderObject::isSelected() const if (!m_renderer) return false; - Node* node = m_renderer->node(); - if (!node) + if (!m_renderer->node()) return false; - const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); - if (equalIgnoringCase(ariaSelected, "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true")) return true; if (isTabItem() && isTabItemSelected()) @@ -1548,10 +1638,12 @@ bool AccessibilityRenderObject::isTabItemSelected() const Vector<Element*> elements; elementsFromAttribute(elements, aria_controlsAttr); - unsigned count = elements.size(); - for (unsigned k = 0; k < count; ++k) { - Element* element = elements[k]; - AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return false; + + for (const auto& element : elements) { + AccessibilityObject* tabPanel = cache->getOrCreate(element); // A tab item should only control tab panels. if (!tabPanel || tabPanel->roleValue() != TabPanelRole) @@ -1574,18 +1666,16 @@ bool AccessibilityRenderObject::isFocused() const if (!m_renderer) return false; - Document* document = m_renderer->document(); - if (!document) - return false; - - Element* focusedElement = document->focusedElement(); + Document& document = m_renderer->document(); + + Element* focusedElement = document.focusedElement(); if (!focusedElement) return false; // A web area is represented by the Document node in the DOM tree, which isn't focusable. // Check instead if the frame's selection controller is focused if (focusedElement == m_renderer->node() - || (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) + || (roleValue() == WebAreaRole && document.frame()->selection().isFocusedAndActive())) return true; return false; @@ -1599,25 +1689,35 @@ void AccessibilityRenderObject::setFocused(bool on) Document* document = this->document(); Node* node = this->node(); - if (!on || !node || !node->isElementNode()) { - document->setFocusedElement(0); + if (!on || !is<Element>(node)) { + document->setFocusedElement(nullptr); return; } + // When a node is told to set focus, that can cause it to be deallocated, which means that doing + // anything else inside this object will crash. To fix this, we added a RefPtr to protect this object + // long enough for duration. + RefPtr<AccessibilityObject> protect(this); + // If this node is already the currently focused node, then calling focus() won't do anything. // That is a problem when focus is removed from the webpage to chrome, and then returns. // In these cases, we need to do what keyboard and mouse focus do, which is reset focus first. if (document->focusedElement() == node) - document->setFocusedElement(0); + document->setFocusedElement(nullptr); - toElement(node)->focus(); + // If we return from setFocusedElement and our element has been removed from a tree, axObjectCache() may be null. + if (AXObjectCache* cache = axObjectCache()) { + cache->setIsSynchronizingSelection(true); + downcast<Element>(*node).focus(); + cache->setIsSynchronizingSelection(false); + } } void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) { // Setting selected only makes sense in trees and tables (and tree-tables). AccessibilityRole role = roleValue(); - if (role != TreeRole && role != TreeGridRole && role != TableRole) + if (role != TreeRole && role != TreeGridRole && role != TableRole && role != GridRole) return; bool isMulti = isMultiSelectable(); @@ -1625,42 +1725,30 @@ void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& sel if (count > 1 && !isMulti) count = 1; - for (unsigned k = 0; k < count; ++k) - selectedRows[k]->setSelected(true); + for (const auto& selectedRow : selectedRows) + selectedRow->setSelected(true); } void AccessibilityRenderObject::setValue(const String& string) { - if (!m_renderer || !m_renderer->node() || !m_renderer->node()->isElementNode()) + if (!m_renderer || !is<Element>(m_renderer->node())) return; - Element* element = toElement(m_renderer->node()); + Element& element = downcast<Element>(*m_renderer->node()); - if (!m_renderer->isBoxModelObject()) + if (!is<RenderBoxModelObject>(*m_renderer)) return; - RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); + RenderBoxModelObject& renderer = downcast<RenderBoxModelObject>(*m_renderer); // FIXME: Do we want to do anything here for ARIA textboxes? - if (renderer->isTextField()) { - // FIXME: This is not safe! Other elements could have a TextField renderer. - toHTMLInputElement(element)->setValue(string); - } else if (renderer->isTextArea()) { - // FIXME: This is not safe! Other elements could have a TextArea renderer. - toHTMLTextAreaElement(element)->setValue(string); - } + if (renderer.isTextField() && is<HTMLInputElement>(element)) + downcast<HTMLInputElement>(element).setValue(string); + else if (renderer.isTextArea() && is<HTMLTextAreaElement>(element)) + downcast<HTMLTextAreaElement>(element).setValue(string); } void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const { - Vector<Element*> elements; - elementsFromAttribute(elements, aria_ownsAttr); - - unsigned count = elements.size(); - for (unsigned k = 0; k < count; ++k) { - RenderObject* render = elements[k]->renderer(); - AccessibilityObject* obj = axObjectCache()->getOrCreate(render); - if (obj) - axObjects.append(obj); - } + ariaElementsFromAttribute(axObjects, aria_ownsAttr); } bool AccessibilityRenderObject::supportsARIAOwns() const @@ -1676,7 +1764,7 @@ RenderView* AccessibilityRenderObject::topRenderer() const { Document* topDoc = topDocument(); if (!topDoc) - return 0; + return nullptr; return topDoc->renderView(); } @@ -1684,66 +1772,53 @@ RenderView* AccessibilityRenderObject::topRenderer() const Document* AccessibilityRenderObject::document() const { if (!m_renderer) - return 0; - return m_renderer->document(); -} - -Document* AccessibilityRenderObject::topDocument() const -{ - if (!document()) - return 0; - return document()->topDocument(); -} - -FrameView* AccessibilityRenderObject::topDocumentFrameView() const -{ - RenderView* renderView = topRenderer(); - if (!renderView || !renderView->view()) - return 0; - return renderView->view()->frameView(); + return nullptr; + return &m_renderer->document(); } Widget* AccessibilityRenderObject::widget() const { - if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isWidget()) - return 0; - return toRenderWidget(m_renderer)->widget(); + if (!is<RenderWidget>(*m_renderer)) + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const { // find an image that is using this map if (!map) - return 0; + return nullptr; HTMLImageElement* imageElement = map->imageElement(); if (!imageElement) - return 0; + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(imageElement); - return axObjectCache()->getOrCreate(imageElement); + return nullptr; } void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) { - Document* document = m_renderer->document(); - RefPtr<HTMLCollection> links = document->links(); - for (unsigned i = 0; Node* curr = links->item(i); i++) { - RenderObject* obj = curr->renderer(); - if (obj) { - RefPtr<AccessibilityObject> axobj = document->axObjectCache()->getOrCreate(obj); - ASSERT(axobj); - if (!axobj->accessibilityIsIgnored() && axobj->isLink()) - result.append(axobj); + Document& document = m_renderer->document(); + Ref<HTMLCollection> links = document.links(); + for (unsigned i = 0; auto* current = links->item(i); ++i) { + if (auto* renderer = current->renderer()) { + RefPtr<AccessibilityObject> axObject = document.axObjectCache()->getOrCreate(renderer); + ASSERT(axObject); + if (!axObject->accessibilityIsIgnored() && axObject->isLink()) + result.append(axObject); } else { - Node* parent = curr->parentNode(); - if (parent && isHTMLAreaElement(curr) && isHTMLMapElement(parent)) { - AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); - HTMLMapElement* map = toHTMLMapElement(parent); - areaObject->setHTMLAreaElement(toHTMLAreaElement(curr)); - areaObject->setHTMLMapElement(map); - areaObject->setParent(accessibilityParentForImageMap(map)); - - result.append(areaObject); + auto* parent = current->parentNode(); + if (is<HTMLAreaElement>(*current) && is<HTMLMapElement>(parent)) { + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + HTMLMapElement& map = downcast<HTMLMapElement>(*parent); + areaObject.setHTMLAreaElement(downcast<HTMLAreaElement>(current)); + areaObject.setHTMLMapElement(&map); + areaObject.setParent(accessibilityParentForImageMap(&map)); + + result.append(&areaObject); } } } @@ -1751,26 +1826,18 @@ void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& re FrameView* AccessibilityRenderObject::documentFrameView() const { - if (!m_renderer || !m_renderer->document()) - return 0; + if (!m_renderer) + return nullptr; // this is the RenderObject's Document's Frame's FrameView - return m_renderer->document()->view(); + return &m_renderer->view().frameView(); } Widget* AccessibilityRenderObject::widgetForAttachmentView() const { if (!isAttachment()) - return 0; - return toRenderWidget(m_renderer)->widget(); -} - -FrameView* AccessibilityRenderObject::frameViewIfRenderView() const -{ - if (!m_renderer->isRenderView()) - return 0; - // this is the RenderObject's Document's renderer's FrameView - return m_renderer->view()->frameView(); + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns @@ -1809,7 +1876,7 @@ VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsi // iterate over the lines // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the // last offset of the last line - VisiblePosition visiblePos = m_renderer->document()->renderer()->positionForPoint(IntPoint()); + VisiblePosition visiblePos = m_renderer->view().positionForPoint(IntPoint(), nullptr); VisiblePosition savedVisiblePos; while (--lineCount) { savedVisiblePos = visiblePos; @@ -1833,33 +1900,24 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) co { if (!m_renderer) return VisiblePosition(); - + if (isNativeTextControl()) - return toRenderTextControl(m_renderer)->visiblePositionForIndex(index); + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().visiblePositionForIndex(index); - if (!allowsTextRanges() && !m_renderer->isText()) + if (!allowsTextRanges() && !is<RenderText>(*m_renderer)) return VisiblePosition(); Node* node = m_renderer->node(); if (!node) return VisiblePosition(); - - if (index <= 0) - return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); - - RefPtr<Range> range = Range::create(m_renderer->document()); - range->selectNodeContents(node, IGNORE_EXCEPTION); - CharacterIterator it(range.get()); - it.advance(index - 1); - return VisiblePosition(Position(it.range()->endContainer(), it.range()->endOffset(), Position::PositionIsOffsetInAnchor), UPSTREAM); + + return visiblePositionForIndexUsingCharacterIterator(node, index); } int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const { - if (isNativeTextControl()) { - HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer)->textFormControlElement(); - return textControl->indexForVisiblePosition(pos); - } + if (isNativeTextControl()) + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().indexForVisiblePosition(pos); if (!isTextControl()) return 0; @@ -1867,28 +1925,26 @@ int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& po Node* node = m_renderer->node(); if (!node) return 0; - + Position indexPosition = pos.deepEquivalent(); if (indexPosition.isNull() || highestEditableRoot(indexPosition, HasEditableAXRole) != node) return 0; - - RefPtr<Range> range = Range::create(m_renderer->document()); - range->setStart(node, 0, IGNORE_EXCEPTION); - range->setEnd(indexPosition, IGNORE_EXCEPTION); #if PLATFORM(GTK) // We need to consider replaced elements for GTK, as they will be // presented with the 'object replacement character' (0xFFFC). - return TextIterator::rangeLength(range.get(), true); + bool forSelectionPreservation = true; #else - return TextIterator::rangeLength(range.get()); + bool forSelectionPreservation = false; #endif + + return WebCore::indexForVisiblePosition(node, pos, forSelectionPreservation); } Element* AccessibilityRenderObject::rootEditableElementForPosition(const Position& position) const { // Find the root editable or pseudo-editable (i.e. having an editable ARIA role) element. - Element* result = 0; + Element* result = nullptr; Element* rootEditableElement = position.rootEditableElement(); @@ -1910,11 +1966,12 @@ bool AccessibilityRenderObject::nodeIsTextControl(const Node* node) const if (!node) return false; - const AccessibilityObject* axObjectForNode = axObjectCache()->getOrCreate(const_cast<Node*>(node)); - if (!axObjectForNode) - return false; + if (AXObjectCache* cache = axObjectCache()) { + if (AccessibilityObject* axObjectForNode = cache->getOrCreate(const_cast<Node*>(node))) + return axObjectForNode->isTextControl(); + } - return axObjectForNode->isTextControl(); + return false; } IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const @@ -1946,16 +2003,16 @@ IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePo // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead if (rect1.maxY() != rect2.maxY()) { RefPtr<Range> dataRange = makeRange(range.start, range.end); - LayoutRect boundingBox = dataRange->boundingBox(); + LayoutRect boundingBox = dataRange->absoluteBoundingBox(); String rangeString = plainText(dataRange.get()); if (rangeString.length() > 1 && !boundingBox.isEmpty()) ourrect = boundingBox; } #if PLATFORM(MAC) - return m_renderer->document()->view()->contentsToScreen(pixelSnappedIntRect(ourrect)); + return m_renderer->view().frameView().contentsToScreen(snappedIntRect(ourrect)); #else - return pixelSnappedIntRect(ourrect); + return snappedIntRect(ourrect); #endif } @@ -1963,14 +2020,19 @@ void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePos { if (range.start.isNull() || range.end.isNull()) return; - + // make selection and tell the document to use it. if it's zero length, then move to that position - if (range.start == range.end) - m_renderer->frame()->selection()->moveTo(range.start, UserTriggered); + if (range.start == range.end) { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionMove); + m_renderer->frame().selection().moveTo(range.start, UserTriggered); + clearTextSelectionIntent(axObjectCache()); + } else { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionExtend); VisibleSelection newSelection = VisibleSelection(range.start, range.end); - m_renderer->frame()->selection()->setSelection(newSelection); - } + m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions()); + clearTextSelectionIntent(axObjectCache()); + } } VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const @@ -1983,11 +2045,11 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin if (!renderView) return VisiblePosition(); - FrameView* frameView = renderView->frameView(); - if (!frameView) - return VisiblePosition(); +#if PLATFORM(COCOA) + FrameView* frameView = &renderView->frameView(); +#endif - Node* innerNode = 0; + Node* innerNode = nullptr; // locate the node containing the point LayoutPoint pointResult; @@ -2013,21 +2075,21 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin pointResult = result.localPoint(); // done if hit something other than a widget - if (!renderer->isWidget()) + if (!is<RenderWidget>(*renderer)) break; // descend into widget (FRAME, IFRAME, OBJECT...) - Widget* widget = toRenderWidget(renderer)->widget(); - if (!widget || !widget->isFrameView()) - break; - Frame* frame = toFrameView(widget)->frame(); - if (!frame) + Widget* widget = downcast<RenderWidget>(*renderer).widget(); + if (!is<FrameView>(widget)) break; - renderView = frame->document()->renderView(); - frameView = toFrameView(widget); + Frame& frame = downcast<FrameView>(*widget).frame(); + renderView = frame.document()->renderView(); +#if PLATFORM(COCOA) + frameView = downcast<FrameView>(widget); +#endif } - return innerNode->renderer()->positionForPoint(pointResult); + return innerNode->renderer()->positionForPoint(pointResult, nullptr); } // NOTE: Consider providing this utility method as AX API @@ -2133,9 +2195,6 @@ String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range return String(); String elementText = isPasswordField() ? passwordFieldValue() : text(); - if (range.start + range.length > elementText.length()) - return String(); - return elementText.substring(range.start, range.length); } @@ -2152,34 +2211,31 @@ IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& rang AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const { if (!area) - return 0; + return nullptr; - AccessibilityObject* parent = 0; + AccessibilityObject* parent = nullptr; for (Element* mapParent = area->parentElement(); mapParent; mapParent = mapParent->parentElement()) { - if (isHTMLMapElement(mapParent)) { - parent = accessibilityParentForImageMap(toHTMLMapElement(mapParent)); + if (is<HTMLMapElement>(*mapParent)) { + parent = accessibilityParentForImageMap(downcast<HTMLMapElement>(mapParent)); break; } } if (!parent) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector children = parent->children(); - - unsigned count = children.size(); - for (unsigned k = 0; k < count; ++k) { - if (children[k]->elementRect().contains(point)) - return children[k].get(); + for (const auto& child : parent->children()) { + if (child->elementRect().contains(point)) + return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::remoteSVGElementHitTest(const IntPoint& point) const { AccessibilityObject* remote = remoteSVGRootElement(); if (!remote) - return 0; + return nullptr; IntSize offset = point - roundedIntPoint(boundingBoxRect().location()); return remote->accessibilityHitTest(IntPoint(offset)); @@ -2196,28 +2252,31 @@ AccessibilityObject* AccessibilityRenderObject::elementAccessibilityHitTest(cons AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const { if (!m_renderer || !m_renderer->hasLayer()) - return 0; + return nullptr; - RenderLayer* layer = toRenderBox(m_renderer)->layer(); + m_renderer->document().updateLayout(); + + RenderLayer* layer = downcast<RenderBox>(*m_renderer).layer(); HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); HitTestResult hitTestResult = HitTestResult(point); layer->hitTest(request, hitTestResult); if (!hitTestResult.innerNode()) - return 0; + return nullptr; Node* node = hitTestResult.innerNode()->deprecatedShadowAncestorNode(); + ASSERT(node); - if (isHTMLAreaElement(node)) - return accessibilityImageMapHitTest(toHTMLAreaElement(node), point); + if (is<HTMLAreaElement>(*node)) + return accessibilityImageMapHitTest(downcast<HTMLAreaElement>(node), point); - if (isHTMLOptionElement(node)) - node = toHTMLOptionElement(node)->ownerSelectElement(); + if (is<HTMLOptionElement>(*node)) + node = downcast<HTMLOptionElement>(*node).ownerSelectElement(); RenderObject* obj = node->renderer(); if (!obj) - return 0; + return nullptr; - AccessibilityObject* result = obj->document()->axObjectCache()->getOrCreate(obj); + AccessibilityObject* result = obj->document().axObjectCache()->getOrCreate(obj); result->updateChildrenIfNecessary(); // Allow the element to perform any hit-testing it might need to do to reach non-render children. @@ -2276,25 +2335,28 @@ bool AccessibilityRenderObject::shouldFocusActiveDescendant() const AccessibilityObject* AccessibilityRenderObject::activeDescendant() const { if (!m_renderer) - return 0; + return nullptr; - if (m_renderer->node() && !m_renderer->node()->isElementNode()) - return 0; - Element* element = toElement(m_renderer->node()); - - const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr); + const AtomicString& activeDescendantAttrStr = getAttribute(aria_activedescendantAttr); if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) - return 0; + return nullptr; + + Element* element = this->element(); + if (!element) + return nullptr; - Element* target = element->treeScope()->getElementById(activeDescendantAttrStr); + Element* target = element->treeScope().getElementById(activeDescendantAttrStr); if (!target) - return 0; + return nullptr; - AccessibilityObject* obj = axObjectCache()->getOrCreate(target); - if (obj && obj->isAccessibilityRenderObject()) - // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification - return obj; - return 0; + if (AXObjectCache* cache = axObjectCache()) { + AccessibilityObject* obj = cache->getOrCreate(target); + if (obj && obj->isAccessibilityRenderObject()) + // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification + return obj; + } + + return nullptr; } void AccessibilityRenderObject::handleAriaExpandedChanged() @@ -2323,41 +2385,45 @@ void AccessibilityRenderObject::handleAriaExpandedChanged() } // Post that the row count changed. + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + if (containerParent) - axObjectCache()->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged, true); + cache->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged); // Post that the specific row either collapsed or expanded. if (roleValue() == RowRole || roleValue() == TreeItemRole) - axObjectCache()->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed, true); + cache->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed); + else + cache->postNotification(this, document(), AXObjectCache::AXExpandedChanged); } void AccessibilityRenderObject::handleActiveDescendantChanged() { - Element* element = toElement(renderer()->node()); + Element* element = downcast<Element>(renderer()->node()); if (!element) return; - Document* doc = renderer()->document(); - if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedElement() != element) - return; - AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); - - if (activedescendant && shouldNotifyActiveDescendant()) - doc->axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged, true); + if (!renderer()->frame().selection().isFocusedAndActive() || renderer()->document().focusedElement() != element) + return; + + if (activeDescendant() && shouldNotifyActiveDescendant()) + renderer()->document().axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged); } AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElement() const { HTMLLabelElement* labelElement = labelElementContainer(); if (!labelElement) - return 0; + return nullptr; HTMLElement* correspondingControl = labelElement->control(); if (!correspondingControl) - return 0; + return nullptr; // Make sure the corresponding control isn't a descendant of this label that's in the middle of being destroyed. if (correspondingControl->renderer() && !correspondingControl->renderer()->parent()) - return 0; + return nullptr; return axObjectCache()->getOrCreate(correspondingControl); } @@ -2365,36 +2431,38 @@ AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElem AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const { if (!m_renderer) - return 0; + return nullptr; // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should // override the "label" element association. if (hasTextAlternative()) - return 0; + return nullptr; Node* node = m_renderer->node(); - if (node && node->isHTMLElement()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label) + if (is<HTMLElement>(node)) { + if (HTMLLabelElement* label = labelForElement(downcast<HTMLElement>(node))) return axObjectCache()->getOrCreate(label); } - return 0; + return nullptr; } -bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject* renderer) const +bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject& renderer) const { // AX clients will listen for AXValueChange on a text control. - if (renderer->isTextControl()) + if (is<RenderTextControl>(renderer)) return true; // AX clients will listen for AXSelectedChildrenChanged on listboxes. - Node* node = renderer->node(); - if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListBox())) + Node* node = renderer.node(); + if (!node) + return false; + + if (nodeHasRole(node, "listbox") || (is<RenderBoxModelObject>(renderer) && downcast<RenderBoxModelObject>(renderer).isListBox())) return true; // Textboxes should send out notifications. - if (nodeHasRole(node, "textbox")) + if (nodeHasRole(node, "textbox") || (is<Element>(*node) && contentEditableAttributeIsEnabled(downcast<Element>(node)))) return true; return false; @@ -2404,41 +2472,61 @@ AccessibilityObject* AccessibilityRenderObject::observableObject() const { // Find the object going up the parent chain that is used in accessibility to monitor certain notifications. for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) { - if (renderObjectIsObservable(renderer)) - return axObjectCache()->getOrCreate(renderer); + if (renderObjectIsObservable(*renderer)) { + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(renderer); + } } - return 0; + return nullptr; } bool AccessibilityRenderObject::isDescendantOfElementType(const QualifiedName& tagName) const { - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->node() && parent->node()->hasTagName(tagName)) + for (auto& ancestor : ancestorsOfType<RenderElement>(*m_renderer)) { + if (ancestor.element() && ancestor.element()->hasTagName(tagName)) return true; } return false; } + +String AccessibilityRenderObject::expandedTextValue() const +{ + if (AccessibilityObject* parent = parentObject()) { + if (parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag)) + return parent->getAttribute(titleAttr); + } + + return String(); +} + +bool AccessibilityRenderObject::supportsExpandedTextValue() const +{ + if (roleValue() == StaticTextRole) { + if (AccessibilityObject* parent = parentObject()) + return parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag); + } + + return false; +} AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() { if (!m_renderer) return UnknownRole; - m_ariaRole = determineAriaRoleAttribute(); + // Sometimes we need to ignore the attribute role. Like if a tree is malformed, + // we want to ignore the treeitem's attribute role. + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole && !shouldIgnoreAttributeRole()) + return m_ariaRole; Node* node = m_renderer->node(); - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; - RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (node && node->isLink()) { - if (cssBox && cssBox->isImage()) - return ImageMapRole; + if (node && node->isLink()) return WebCoreLinkRole; - } + if (node && is<HTMLImageElement>(*node) && downcast<HTMLImageElement>(*node).fastHasAttribute(usemapAttr)) + return ImageMapRole; if ((cssBox && cssBox->isListItem()) || (node && node->hasTagName(liTag))) return ListItemRole; if (m_renderer->isListMarker()) @@ -2450,7 +2538,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (m_renderer->isText()) return StaticTextRole; if (cssBox && cssBox->isImage()) { - if (node && isHTMLInputElement(node)) + if (is<HTMLInputElement>(node)) return ariaHasPopup() ? PopUpButtonRole : ButtonRole; if (isSVGImage()) return SVGRootRole; @@ -2460,35 +2548,39 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(canvasTag)) return CanvasRole; - if (cssBox && cssBox->isRenderView()) { - // If the iframe is seamless, it should not be announced as a web area to AT clients. - if (document() && document()->shouldDisplaySeamlesslyWithParent()) - return SeamlessWebAreaRole; + if (cssBox && cssBox->isRenderView()) return WebAreaRole; - } - if (cssBox && cssBox->isTextField()) - return TextFieldRole; + if (cssBox && cssBox->isTextField()) { + if (is<HTMLInputElement>(node)) + return downcast<HTMLInputElement>(*node).isSearchField() ? SearchFieldRole : TextFieldRole; + } if (cssBox && cssBox->isTextArea()) return TextAreaRole; - if (node && isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isCheckbox()) + if (is<HTMLInputElement>(node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isCheckbox()) return CheckBoxRole; - if (input->isRadioButton()) + if (input.isRadioButton()) return RadioButtonRole; - if (input->isTextButton()) + if (input.isTextButton()) return buttonRoleType(); - + // On iOS, the date field and time field are popup buttons. On other platforms they are text fields. +#if PLATFORM(IOS) + if (input.isDateField() || input.isTimeField()) + return PopUpButtonRole; +#endif #if ENABLE(INPUT_TYPE_COLOR) - const AtomicString& type = input->getAttribute(typeAttr); - if (equalIgnoringCase(type, "color")) + if (input.isColorControl()) return ColorWellRole; #endif } - + + if (hasContentEditableAttributeSet()) + return TextAreaRole; + if (isFileUploadButton()) return ButtonRole; @@ -2498,15 +2590,16 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (headingLevel()) return HeadingRole; -#if ENABLE(SVG) if (m_renderer->isSVGImage()) return ImageRole; if (m_renderer->isSVGRoot()) return SVGRootRole; if (node && node->hasTagName(SVGNames::gTag)) return GroupRole; -#endif - + + if (isStyleFormatGroup()) + return is<RenderInline>(*m_renderer) ? InlineRole : GroupRole; + #if ENABLE(MATHML) if (node && node->hasTagName(MathMLNames::mathTag)) return DocumentMathRole; @@ -2525,19 +2618,27 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(dlTag)) return DescriptionListRole; - if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) - return AnnotationRole; - -#if PLATFORM(GTK) - // Gtk ATs expect all tables, data and layout, to be exposed as tables. - if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) - return CellRole; - - if (node && node->hasTagName(trTag)) - return RowRole; - - if (node && isHTMLTableElement(node)) - return TableRole; + // Check for Ruby elements + if (m_renderer->isRubyText()) + return RubyTextRole; + if (m_renderer->isRubyBase()) + return RubyBaseRole; + if (m_renderer->isRubyRun()) + return RubyRunRole; + if (m_renderer->isRubyBlock()) + return RubyBlockRole; + if (m_renderer->isRubyInline()) + return RubyInlineRole; + + // This return value is what will be used if AccessibilityTableCell determines + // the cell should not be treated as a cell (e.g. because it is a layout table. + // In ATK, there is a distinction between generic text block elements and other + // generic containers; AX API does not make this distinction. + if (is<RenderTableCell>(m_renderer)) +#if PLATFORM(GTK) || PLATFORM(EFL) + return DivRole; +#else + return GroupRole; #endif // Table sections should be ignored. @@ -2550,7 +2651,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(pTag)) return ParagraphRole; - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return LabelRole; if (node && node->hasTagName(dfnTag)) @@ -2559,7 +2660,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(divTag)) return DivRole; - if (node && isHTMLFormElement(node)) + if (is<HTMLFormElement>(node)) return FormRole; if (node && node->hasTagName(articleTag)) @@ -2580,6 +2681,27 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(addressTag)) return LandmarkContentInfoRole; + if (node && node->hasTagName(blockquoteTag)) + return BlockquoteRole; + + if (node && node->hasTagName(captionTag)) + return CaptionRole; + + if (node && node->hasTagName(preTag)) + return PreRole; + + if (is<HTMLDetailsElement>(node)) + return DetailsRole; + if (is<HTMLSummaryElement>(node)) + return SummaryRole; + +#if ENABLE(VIDEO) + if (is<HTMLVideoElement>(node)) + return VideoRole; + if (is<HTMLAudioElement>(node)) + return AudioRole; +#endif + // The HTML element should not be exposed as an element. That's what the RenderView element does. if (node && node->hasTagName(htmlTag)) return IgnoredRole; @@ -2590,25 +2712,51 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() return LandmarkBannerRole; if (node && node->hasTagName(footerTag) && !isDescendantOfElementType(articleTag) && !isDescendantOfElementType(sectionTag)) return FooterRole; - - if (m_renderer->isBlockFlow()) - return GroupRole; - // If the element does not have role, but it has ARIA attributes, accessibility should fallback to exposing it as a group. - if (supportsARIAAttributes()) + // If the element does not have role, but it has ARIA attributes, or accepts tab focus, accessibility should fallback to exposing it as a group. + if (supportsARIAAttributes() || canSetFocusAttribute()) return GroupRole; + + if (m_renderer->isRenderBlockFlow()) { +#if PLATFORM(GTK) || PLATFORM(EFL) + // For ATK, GroupRole maps to ATK_ROLE_PANEL. Panels are most commonly found (and hence + // expected) in UI elements; not text blocks. + return m_renderer->isAnonymousBlock() ? DivRole : GroupRole; +#else + return GroupRole; +#endif + } + // InlineRole is the final fallback before assigning UnknownRole to an object. It makes it + // possible to distinguish truly unknown objects from non-focusable inline text elements + // which have an event handler or attribute suggesting possible inclusion by the platform. + if (is<RenderInline>(*m_renderer) + && (hasAttributesRequiredForInclusion() + || (node && node->hasEventListeners()) + || (supportsDatetimeAttribute() && !getAttribute(datetimeAttr).isEmpty()))) + return InlineRole; + return UnknownRole; } AccessibilityOrientation AccessibilityRenderObject::orientation() const { const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); - if (equalIgnoringCase(ariaOrientation, "horizontal")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "horizontal")) return AccessibilityOrientationHorizontal; - if (equalIgnoringCase(ariaOrientation, "vertical")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "vertical")) + return AccessibilityOrientationVertical; + if (equalLettersIgnoringASCIICase(ariaOrientation, "undefined")) + return AccessibilityOrientationUndefined; + + // ARIA 1.1 Implicit defaults are defined on some roles. + // http://www.w3.org/TR/wai-aria-1.1/#aria-orientation + if (isScrollbar() || isComboBox() || isListBox() || isMenu() || isTree()) return AccessibilityOrientationVertical; + if (isMenuBar() || isSplitter() || isTabList() || isToolbar()) + return AccessibilityOrientationHorizontal; + return AccessibilityObject::orientation(); } @@ -2621,18 +2769,25 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const // ARIA spec says that when a parent object is presentational, and it has required child elements, // those child elements are also presentational. For example, <li> becomes presentational from <ul>. // http://www.w3.org/WAI/PF/aria/complete#presentation - DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, listItemParents, ()); + static NeverDestroyed<HashSet<QualifiedName>> listItemParents; + static NeverDestroyed<HashSet<QualifiedName>> tableCellParents; - HashSet<QualifiedName>* possibleParentTagNames = 0; + HashSet<QualifiedName>* possibleParentTagNames = nullptr; switch (roleValue()) { case ListItemRole: case ListMarkerRole: - if (listItemParents.isEmpty()) { - listItemParents.add(ulTag); - listItemParents.add(olTag); - listItemParents.add(dlTag); + if (listItemParents.get().isEmpty()) { + listItemParents.get().add(ulTag); + listItemParents.get().add(olTag); + listItemParents.get().add(dlTag); } - possibleParentTagNames = &listItemParents; + possibleParentTagNames = &listItemParents.get(); + break; + case GridCellRole: + case CellRole: + if (tableCellParents.get().isEmpty()) + tableCellParents.get().add(tableTag); + possibleParentTagNames = &tableCellParents.get(); break; default: break; @@ -2643,16 +2798,16 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const return false; for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (!parent->isAccessibilityRenderObject()) + if (!is<AccessibilityRenderObject>(*parent)) continue; - Node* elementNode = static_cast<AccessibilityRenderObject*>(parent)->node(); - if (!elementNode || !elementNode->isElementNode()) + Node* node = downcast<AccessibilityRenderObject>(*parent).node(); + if (!is<Element>(node)) continue; // If native tag of the parent element matches an acceptable name, then return // based on its presentational status. - if (possibleParentTagNames->contains(toElement(elementNode)->tagQName())) + if (possibleParentTagNames->contains(downcast<Element>(node)->tagQName())) return parent->roleValue() == PresentationalRole; } @@ -2686,34 +2841,12 @@ bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const bool AccessibilityRenderObject::canSetExpandedAttribute() const { + if (roleValue() == DetailsRole) + return true; + // An object can be expanded if it aria-expanded is true or false. const AtomicString& ariaExpanded = getAttribute(aria_expandedAttr); - return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); -} - -bool AccessibilityRenderObject::canSetValueAttribute() const -{ - - // In the event of a (Boolean)@readonly and (True/False/Undefined)@aria-readonly - // value mismatch, the host language native attribute value wins. - if (isNativeTextControl()) - return !isReadOnly(); - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true")) - return false; - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "false")) - return true; - - if (isProgressIndicator() || isSlider()) - return true; - - if (isTextControl() && !isNativeTextControl()) - return true; - - // Any node could be contenteditable, so isReadOnly should be relied upon - // for this information for all elements. - return !isReadOnly(); + return equalLettersIgnoringASCIICase(ariaExpanded, "true") || equalLettersIgnoringASCIICase(ariaExpanded, "false"); } bool AccessibilityRenderObject::canSetTextRangeAttributes() const @@ -2726,16 +2859,19 @@ void AccessibilityRenderObject::textChanged() // If this element supports ARIA live regions, or is part of a region with an ARIA editable role, // then notify the AT of changes. AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) { AccessibilityObject* parent = cache->get(renderParent); if (!parent) continue; if (parent->supportsARIALiveRegion()) - cache->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true); + cache->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged); - if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->rendererIsEditable()) - cache->postNotification(renderParent, AXObjectCache::AXValueChanged, true); + if (parent->isNonNativeTextControl()) + cache->postNotification(renderParent, AXObjectCache::AXValueChanged); } } @@ -2748,25 +2884,25 @@ void AccessibilityRenderObject::clearChildren() void AccessibilityRenderObject::addImageMapChildren() { RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (!cssBox || !cssBox->isRenderImage()) + if (!is<RenderImage>(cssBox)) return; - HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); + HTMLMapElement* map = downcast<RenderImage>(*cssBox).imageMap(); if (!map) return; - for (Element* current = ElementTraversal::firstWithin(map); current; current = ElementTraversal::next(current, map)) { + for (auto& area : descendantsOfType<HTMLAreaElement>(*map)) { // add an <area> element for this child if it has a link - if (isHTMLAreaElement(current) && current->isLink()) { - AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->getOrCreate(ImageMapLinkRole)); - areaObject->setHTMLAreaElement(toHTMLAreaElement(current)); - areaObject->setHTMLMapElement(map); - areaObject->setParent(this); - if (!areaObject->accessibilityIsIgnored()) - m_children.append(areaObject); - else - axObjectCache()->remove(areaObject->axObjectID()); - } + if (!area.isLink()) + continue; + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + areaObject.setHTMLAreaElement(&area); + areaObject.setHTMLMapElement(map); + areaObject.setParent(this); + if (!areaObject.accessibilityIsIgnored()) + m_children.append(&areaObject); + else + axObjectCache()->remove(areaObject.axObjectID()); } } @@ -2781,18 +2917,18 @@ void AccessibilityRenderObject::updateChildrenIfNecessary() void AccessibilityRenderObject::addTextFieldChildren() { Node* node = this->node(); - if (!node || !isHTMLInputElement(node)) + if (!is<HTMLInputElement>(node)) return; - HTMLInputElement* input = toHTMLInputElement(node); - HTMLElement* spinButtonElement = input->innerSpinButtonElement(); - if (!spinButtonElement || !spinButtonElement->isSpinButtonElement()) + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + HTMLElement* spinButtonElement = input.innerSpinButtonElement(); + if (!is<SpinButtonElement>(spinButtonElement)) return; - AccessibilitySpinButton* axSpinButton = static_cast<AccessibilitySpinButton*>(axObjectCache()->getOrCreate(SpinButtonRole)); - axSpinButton->setSpinButtonElement(static_cast<SpinButtonElement*>(spinButtonElement)); - axSpinButton->setParent(this); - m_children.append(axSpinButton); + auto& axSpinButton = downcast<AccessibilitySpinButton>(*axObjectCache()->getOrCreate(SpinButtonRole)); + axSpinButton.setSpinButtonElement(downcast<SpinButtonElement>(spinButtonElement)); + axSpinButton.setParent(this); + m_children.append(&axSpinButton); } bool AccessibilityRenderObject::isSVGImage() const @@ -2803,54 +2939,50 @@ bool AccessibilityRenderObject::isSVGImage() const void AccessibilityRenderObject::detachRemoteSVGRoot() { if (AccessibilitySVGRoot* root = remoteSVGRootElement()) - root->setParent(0); + root->setParent(nullptr); } AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement() const { -#if ENABLE(SVG) - if (!m_renderer || !m_renderer->isRenderImage()) - return 0; + if (!is<RenderImage>(m_renderer)) + return nullptr; - CachedImage* cachedImage = toRenderImage(m_renderer)->cachedImage(); + CachedImage* cachedImage = downcast<RenderImage>(*m_renderer).cachedImage(); if (!cachedImage) - return 0; + return nullptr; Image* image = cachedImage->image(); - if (!image || !image->isSVGImage()) - return 0; + if (!is<SVGImage>(image)) + return nullptr; - SVGImage* svgImage = static_cast<SVGImage*>(image); - FrameView* frameView = svgImage->frameView(); + FrameView* frameView = downcast<SVGImage>(*image).frameView(); if (!frameView) - return 0; - Frame* frame = frameView->frame(); - if (!frame) - return 0; + return nullptr; + Frame& frame = frameView->frame(); - Document* doc = frame->document(); - if (!doc || !doc->isSVGDocument()) - return 0; + Document* document = frame.document(); + if (!is<SVGDocument>(document)) + return nullptr; - SVGSVGElement* rootElement = toSVGDocument(doc)->rootElement(); + SVGSVGElement* rootElement = downcast<SVGDocument>(*document).rootElement(); if (!rootElement) - return 0; + return nullptr; RenderObject* rendererRoot = rootElement->renderer(); if (!rendererRoot) - return 0; + return nullptr; - AccessibilityObject* rootSVGObject = frame->document()->axObjectCache()->getOrCreate(rendererRoot); + AXObjectCache* cache = frame.document()->axObjectCache(); + if (!cache) + return nullptr; + AccessibilityObject* rootSVGObject = cache->getOrCreate(rendererRoot); // In order to connect the AX hierarchy from the SVG root element from the loaded resource // the parent must be set, because there's no other way to get back to who created the image. - ASSERT(rootSVGObject && rootSVGObject->isAccessibilitySVGRoot()); - if (!rootSVGObject->isAccessibilitySVGRoot()) - return 0; + ASSERT(rootSVGObject); + if (!is<AccessibilitySVGRoot>(*rootSVGObject)) + return nullptr; - return toAccessibilitySVGRoot(rootSVGObject); -#else - return 0; -#endif + return downcast<AccessibilitySVGRoot>(rootSVGObject); } void AccessibilityRenderObject::addRemoteSVGChildren() @@ -2862,17 +2994,17 @@ void AccessibilityRenderObject::addRemoteSVGChildren() root->setParent(this); if (root->accessibilityIsIgnored()) { - AccessibilityChildrenVector children = root->children(); - unsigned length = children.size(); - for (unsigned i = 0; i < length; ++i) - m_children.append(children[i]); + for (const auto& child : root->children()) + m_children.append(child); } else m_children.append(root); } void AccessibilityRenderObject::addCanvasChildren() { - if (!node() || !node()->hasTagName(canvasTag)) + // Add the unrendered canvas children as AX nodes, unless we're not using a canvas renderer + // because JS is disabled for example. + if (!node() || !node()->hasTagName(canvasTag) || (renderer() && !renderer()->isCanvas())) return; // If it's a canvas, it won't have rendered children, but it might have accessible fallback content. @@ -2897,7 +3029,7 @@ void AccessibilityRenderObject::addAttachmentChildren() m_children.append(axWidget); } -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void AccessibilityRenderObject::updateAttachmentViewParents() { // Only the unignored parent should set the attachment parent, because that's what is reflected in the AX @@ -2905,10 +3037,9 @@ void AccessibilityRenderObject::updateAttachmentViewParents() if (accessibilityIsIgnored()) return; - size_t length = m_children.size(); - for (size_t k = 0; k < length; k++) { - if (m_children[k]->isAttachment()) - m_children[k]->overrideAttachmentParent(this); + for (const auto& child : m_children) { + if (child->isAttachment()) + child->overrideAttachmentParent(this); } } #endif @@ -2942,11 +3073,11 @@ void AccessibilityRenderObject::addHiddenChildren() // Find out where the last render sibling is located within m_children. AccessibilityObject* childObject = axObjectCache()->get(child->renderer()); if (childObject && childObject->accessibilityIsIgnored()) { - AccessibilityChildrenVector children = childObject->children(); + auto& children = childObject->children(); if (children.size()) childObject = children.last().get(); else - childObject = 0; + childObject = nullptr; } if (childObject) @@ -2966,6 +3097,24 @@ void AccessibilityRenderObject::addHiddenChildren() } } +void AccessibilityRenderObject::updateRoleAfterChildrenCreation() +{ + // If a menu does not have valid menuitem children, it should not be exposed as a menu. + if (roleValue() == MenuRole) { + // Elements marked as menus must have at least one menu item child. + size_t menuItemCount = 0; + for (const auto& child : children()) { + if (child->isMenuItem()) { + menuItemCount++; + break; + } + } + + if (!menuItemCount) + m_role = GroupRole; + } +} + void AccessibilityRenderObject::addChildren() { // If the need to add more children in addition to existing children arises, @@ -2987,9 +3136,11 @@ void AccessibilityRenderObject::addChildren() addCanvasChildren(); addRemoteSVGChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) updateAttachmentViewParents(); #endif + + updateRoleAfterChildrenCreation(); } bool AccessibilityRenderObject::canHaveChildren() const @@ -3000,36 +3151,19 @@ bool AccessibilityRenderObject::canHaveChildren() const return AccessibilityNodeObject::canHaveChildren(); } -const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const +const String AccessibilityRenderObject::ariaLiveRegionStatus() const { - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off", AtomicString::ConstructFromLiteral)); - const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); // These roles have implicit live region status. - if (liveRegionStatus.isEmpty()) { - switch (roleValue()) { - case ApplicationAlertDialogRole: - case ApplicationAlertRole: - return liveRegionStatusAssertive; - case ApplicationLogRole: - case ApplicationStatusRole: - return liveRegionStatusPolite; - case ApplicationTimerRole: - case ApplicationMarqueeRole: - return liveRegionStatusOff; - default: - break; - } - } + if (liveRegionStatus.isEmpty()) + return defaultLiveRegionStatusForRole(roleValue()); return liveRegionStatus; } const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const { - DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text", AtomicString::ConstructFromLiteral)); + static NeverDestroyed<const AtomicString> defaultLiveRegionRelevant("additions text", AtomicString::ConstructFromLiteral); const AtomicString& relevant = getAttribute(aria_relevantAttr); // Default aria-relevant = "additions text". @@ -3041,7 +3175,20 @@ const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const bool AccessibilityRenderObject::ariaLiveRegionAtomic() const { - return elementAttributeValue(aria_atomicAttr); + const AtomicString& atomic = getAttribute(aria_atomicAttr); + if (equalLettersIgnoringASCIICase(atomic, "true")) + return true; + if (equalLettersIgnoringASCIICase(atomic, "false")) + return false; + + // WAI-ARIA "alert" and "status" roles have an implicit aria-atomic value of true. + switch (roleValue()) { + case ApplicationAlertRole: + case ApplicationStatusRole: + return true; + default: + return false; + } } bool AccessibilityRenderObject::ariaLiveRegionBusy() const @@ -3051,13 +3198,6 @@ bool AccessibilityRenderObject::ariaLiveRegionBusy() const void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) { - // Get all the rows. - AccessibilityChildrenVector allRows; - if (isTree()) - ariaTreeRows(allRows); - else if (isAccessibilityTable() && toAccessibilityTable(this)->supportsSelectedRows()) - allRows = toAccessibilityTable(this)->rows(); - // Determine which rows are selected. bool isMulti = isMultiSelectable(); @@ -3069,13 +3209,24 @@ void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& re return; } - unsigned count = allRows.size(); - for (unsigned k = 0; k < count; ++k) { - if (allRows[k]->isSelected()) { - result.append(allRows[k]); - if (!isMulti) - break; + // Get all the rows. + auto rowsIteration = [&](const AccessibilityChildrenVector& rows) { + for (auto& row : rows) { + if (row->isSelected()) { + result.append(row); + if (!isMulti) + break; + } } + }; + if (isTree()) { + AccessibilityChildrenVector allRows; + ariaTreeRows(allRows); + rowsIteration(allRows); + } else if (is<AccessibilityTable>(*this)) { + auto& thisTable = downcast<AccessibilityTable>(*this); + if (thisTable.isExposableThroughAccessibility() && thisTable.supportsSelectedRows()) + rowsIteration(thisTable.rows()); } } @@ -3083,11 +3234,8 @@ void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildre { bool isMulti = isMultiSelectable(); - AccessibilityChildrenVector childObjects = children(); - unsigned childrenSize = childObjects.size(); - for (unsigned k = 0; k < childrenSize; ++k) { + for (const auto& child : children()) { // Every child should have aria-role option, and if so, check for selected attribute/state. - AccessibilityObject* child = childObjects[k].get(); if (child->isSelected() && child->ariaRoleAttribute() == ListBoxOptionRole) { result.append(child); if (!isMulti) @@ -3104,7 +3252,7 @@ void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& re AccessibilityRole role = roleValue(); if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes ariaListboxSelectedChildren(result); - else if (role == TreeRole || role == TreeGridRole || role == TableRole) + else if (role == TreeRole || role == TreeGridRole || role == TableRole || role == GridRole) ariaSelectedRows(result); } @@ -3113,11 +3261,9 @@ void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildren if (!hasChildren()) addChildren(); - AccessibilityObject::AccessibilityChildrenVector children = this->children(); - size_t size = children.size(); - for (size_t i = 0; i < size; i++) { - if (!children[i]->isOffScreen()) - result.append(children[i]); + for (const auto& child : children()) { + if (child->isOffScreen()) + result.append(child); } } @@ -3137,25 +3283,23 @@ void AccessibilityRenderObject::tabChildren(AccessibilityChildrenVector& result) { ASSERT(roleValue() == TabListRole); - AccessibilityObject::AccessibilityChildrenVector children = this->children(); - size_t size = children.size(); - for (size_t i = 0; i < size; ++i) { - if (children[i]->isTabItem()) - result.append(children[i]); + for (const auto& child : children()) { + if (child->isTabItem()) + result.append(child); } } const String& AccessibilityRenderObject::actionVerb() const { +#if !PLATFORM(IOS) // FIXME: Need to add verbs for select elements. - DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); - DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); - DEFINE_STATIC_LOCAL(const String, noAction, ()); - + static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); + static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); + static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); + static NeverDestroyed<const String> checkedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); + switch (roleValue()) { case ButtonRole: case ToggleButtonRole: @@ -3171,8 +3315,11 @@ const String& AccessibilityRenderObject::actionVerb() const case WebCoreLinkRole: return linkAction; default: - return noAction; + return nullAtom; } +#else + return nullAtom; +#endif } void AccessibilityRenderObject::setAccessibleName(const AtomicString& name) @@ -3181,15 +3328,15 @@ void AccessibilityRenderObject::setAccessibleName(const AtomicString& name) if (!m_renderer) return; - Node* domNode = 0; + Node* node = nullptr; // For web areas, set the aria-label on the HTML element. if (isWebArea()) - domNode = m_renderer->document()->documentElement(); + node = m_renderer->document().documentElement(); else - domNode = m_renderer->node(); + node = m_renderer->node(); - if (domNode && domNode->isElementNode()) - toElement(domNode)->setAttribute(aria_labelAttr, name); + if (is<Element>(node)) + downcast<Element>(*node).setAttribute(aria_labelAttr, name); } static bool isLinkable(const AccessibilityRenderObject& object) @@ -3206,8 +3353,8 @@ String AccessibilityRenderObject::stringValueForMSAA() const { if (isLinkable(*this)) { Element* anchor = anchorElement(); - if (anchor && isHTMLAnchorElement(anchor)) - return toHTMLAnchorElement(anchor)->href(); + if (is<HTMLAnchorElement>(anchor)) + return downcast<HTMLAnchorElement>(*anchor).href(); } return stringValue(); @@ -3219,10 +3366,10 @@ bool AccessibilityRenderObject::isLinked() const return false; Element* anchor = anchorElement(); - if (!anchor || !isHTMLAnchorElement(anchor)) + if (!is<HTMLAnchorElement>(anchor)) return false; - return !toHTMLAnchorElement(anchor)->href().isEmpty(); + return !downcast<HTMLAnchorElement>(*anchor).href().isEmpty(); } bool AccessibilityRenderObject::hasBoldFont() const @@ -3230,7 +3377,7 @@ bool AccessibilityRenderObject::hasBoldFont() const if (!m_renderer) return false; - return m_renderer->style()->fontDescription().weight() >= FontWeightBold; + return m_renderer->style().fontDescription().weight() >= FontWeightBold; } bool AccessibilityRenderObject::hasItalicFont() const @@ -3238,7 +3385,7 @@ bool AccessibilityRenderObject::hasItalicFont() const if (!m_renderer) return false; - return m_renderer->style()->fontDescription().italic() == FontItalicOn; + return m_renderer->style().fontDescription().italic() == FontItalicOn; } bool AccessibilityRenderObject::hasPlainText() const @@ -3246,11 +3393,11 @@ bool AccessibilityRenderObject::hasPlainText() const if (!m_renderer) return false; - RenderStyle* style = m_renderer->style(); + const RenderStyle& style = m_renderer->style(); - return style->fontDescription().weight() == FontWeightNormal - && style->fontDescription().italic() == FontItalicOff - && style->textDecorationsInEffect() == TextDecorationNone; + return style.fontDescription().weight() == FontWeightNormal + && style.fontDescription().italic() == FontItalicOff + && style.textDecorationsInEffect() == TextDecorationNone; } bool AccessibilityRenderObject::hasSameFont(RenderObject* renderer) const @@ -3258,7 +3405,7 @@ bool AccessibilityRenderObject::hasSameFont(RenderObject* renderer) const if (!m_renderer || !renderer) return false; - return m_renderer->style()->fontDescription().families() == renderer->style()->fontDescription().families(); + return m_renderer->style().fontDescription().families() == renderer->style().fontDescription().families(); } bool AccessibilityRenderObject::hasSameFontColor(RenderObject* renderer) const @@ -3266,7 +3413,7 @@ bool AccessibilityRenderObject::hasSameFontColor(RenderObject* renderer) const if (!m_renderer || !renderer) return false; - return m_renderer->style()->visitedDependentColor(CSSPropertyColor) == renderer->style()->visitedDependentColor(CSSPropertyColor); + return m_renderer->style().visitedDependentColor(CSSPropertyColor) == renderer->style().visitedDependentColor(CSSPropertyColor); } bool AccessibilityRenderObject::hasSameStyle(RenderObject* renderer) const @@ -3282,7 +3429,7 @@ bool AccessibilityRenderObject::hasUnderline() const if (!m_renderer) return false; - return m_renderer->style()->textDecorationsInEffect() & TextDecorationUnderline; + return m_renderer->style().textDecorationsInEffect() & TextDecorationUnderline; } String AccessibilityRenderObject::nameForMSAA() const @@ -3309,14 +3456,14 @@ String AccessibilityRenderObject::stringRoleForMSAA() const return String(); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return String(); - Element* element = toElement(node); - if (!shouldReturnTagNameAsRoleForMSAA(*element)) + Element& element = downcast<Element>(*node); + if (!shouldReturnTagNameAsRoleForMSAA(element)) return String(); - return element->tagName(); + return element.tagName(); } String AccessibilityRenderObject::positionalDescriptionForMSAA() const @@ -3354,10 +3501,10 @@ static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer) if (!renderer) return UnknownRole; - if (renderer->isText()) + if (is<RenderText>(*renderer)) return EditableTextRole; - if (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListItem()) + if (is<RenderListItem>(*renderer)) return ListItemRole; return UnknownRole; @@ -3378,161 +3525,125 @@ AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const String AccessibilityRenderObject::passwordFieldValue() const { -#if PLATFORM(GTK) ASSERT(isPasswordField()); // Look for the RenderText object in the RenderObject tree for this input field. RenderObject* renderer = node()->renderer(); - while (renderer && !renderer->isText()) - renderer = renderer->firstChild(); + while (renderer && !is<RenderText>(renderer)) + renderer = downcast<RenderElement>(*renderer).firstChild(); - if (!renderer || !renderer->isText()) + if (!is<RenderText>(renderer)) return String(); // Return the text that is actually being rendered in the input field. - return static_cast<RenderText*>(renderer)->textWithoutTranscoding(); -#else - // It seems only GTK is interested in this at the moment. - return String(); -#endif + return downcast<RenderText>(*renderer).textWithoutConvertingBackslashToYenSymbol(); } ScrollableArea* AccessibilityRenderObject::getScrollableAreaIfScrollable() const { // If the parent is a scroll view, then this object isn't really scrollable, the parent ScrollView should handle the scrolling. if (parentObject() && parentObject()->isAccessibilityScrollView()) - return 0; + return nullptr; - if (!m_renderer || !m_renderer->isBox()) - return 0; + if (!is<RenderBox>(m_renderer)) + return nullptr; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) - return 0; + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) + return nullptr; - return box->layer(); + return box.layer(); } void AccessibilityRenderObject::scrollTo(const IntPoint& point) const { - if (!m_renderer || !m_renderer->isBox()) + if (!is<RenderBox>(m_renderer)) return; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) return; - RenderLayer* layer = box->layer(); - layer->scrollToOffset(toIntSize(point), RenderLayer::ScrollOffsetClamped); + // FIXME: is point a ScrollOffset or ScrollPosition? Test in RTL overflow. + box.layer()->scrollToOffset(point, RenderLayer::ScrollOffsetClamped); } #if ENABLE(MATHML) bool AccessibilityRenderObject::isMathElement() const { - Node* node = this->node(); - if (!m_renderer || !node) + if (!m_renderer) return false; - return node->isElementNode() && toElement(node)->isMathMLElement(); + return is<MathMLElement>(node()); } bool AccessibilityRenderObject::isMathFraction() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLFraction(); + return m_renderer && m_renderer->isRenderMathMLFraction(); } bool AccessibilityRenderObject::isMathFenced() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLFenced(); + return m_renderer && m_renderer->isRenderMathMLFenced(); } bool AccessibilityRenderObject::isMathSubscriptSuperscript() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLSubSup(); + return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript(); } bool AccessibilityRenderObject::isMathRow() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLRow(); + return m_renderer && m_renderer->isRenderMathMLRow(); } bool AccessibilityRenderObject::isMathUnderOver() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLUnderOver(); + return m_renderer && m_renderer->isRenderMathMLUnderOver(); } bool AccessibilityRenderObject::isMathSquareRoot() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLSquareRoot(); + return m_renderer && m_renderer->isRenderMathMLSquareRoot(); } +bool AccessibilityRenderObject::isMathToken() const +{ + return m_renderer && m_renderer->isRenderMathMLToken(); +} + bool AccessibilityRenderObject::isMathRoot() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - return toRenderMathMLBlock(m_renderer)->isRenderMathMLRoot(); + return m_renderer && m_renderer->isRenderMathMLRoot(); } bool AccessibilityRenderObject::isMathOperator() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - // Ensure that this is actually a render MathML operator because - // MathML will create MathMLBlocks and use the original node as the node - // of this new block that is not tied to the DOM. - if (!toRenderMathMLBlock(m_renderer)->isRenderMathMLOperator()) + if (!m_renderer || !m_renderer->isRenderMathMLOperator()) return false; - - return isMathElement() && node()->hasTagName(MathMLNames::moTag); + + return true; } bool AccessibilityRenderObject::isMathFenceOperator() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) + if (!is<RenderMathMLOperator>(m_renderer)) return false; - - if (!toRenderMathMLBlock(m_renderer)->isRenderMathMLOperator()) - return false; - - RenderMathMLOperator* mathOperator = toRenderMathMLOperator(toRenderMathMLBlock(m_renderer)); - return mathOperator->operatorType() == RenderMathMLOperator::Fence; + + return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Fence); } bool AccessibilityRenderObject::isMathSeparatorOperator() const { - if (!m_renderer || !m_renderer->isRenderMathMLBlock()) - return false; - - if (!toRenderMathMLBlock(m_renderer)->isRenderMathMLOperator()) + if (!is<RenderMathMLOperator>(m_renderer)) return false; - - RenderMathMLOperator* mathOperator = toRenderMathMLOperator(toRenderMathMLBlock(m_renderer)); - return mathOperator->operatorType() == RenderMathMLOperator::Separator; + + return downcast<RenderMathMLOperator>(*m_renderer).hasOperatorFlag(MathMLOperatorDictionary::Separator); } bool AccessibilityRenderObject::isMathText() const { - return node() && node()->hasTagName(MathMLNames::mtextTag); + return node() && (node()->hasTagName(MathMLNames::mtextTag) || hasTagName(MathMLNames::msTag)); } bool AccessibilityRenderObject::isMathNumber() const @@ -3557,28 +3668,63 @@ bool AccessibilityRenderObject::isMathTable() const bool AccessibilityRenderObject::isMathTableRow() const { - return node() && node()->hasTagName(MathMLNames::mtrTag); + return node() && (node()->hasTagName(MathMLNames::mtrTag) || hasTagName(MathMLNames::mlabeledtrTag)); } bool AccessibilityRenderObject::isMathTableCell() const { return node() && node()->hasTagName(MathMLNames::mtdTag); } + +bool AccessibilityRenderObject::isMathScriptObject(AccessibilityMathScriptObjectType type) const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent) + return false; + + return type == Subscript ? this == parent->mathSubscriptObject() : this == parent->mathSuperscriptObject(); +} + +bool AccessibilityRenderObject::isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType type) const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!parent || !parent->isMathMultiscript()) + return false; + + // The scripts in a MathML <mmultiscripts> element consist of one or more + // subscript, superscript pairs. In order to determine if this object is + // a scripted token, we need to examine each set of pairs to see if the + // this token is present and in the position corresponding with the type. + + AccessibilityMathMultiscriptPairs pairs; + if (type == PreSubscript || type == PreSuperscript) + parent->mathPrescripts(pairs); + else + parent->mathPostscripts(pairs); + + for (const auto& pair : pairs) { + if (this == pair.first) + return (type == PreSubscript || type == PostSubscript); + if (this == pair.second) + return (type == PreSuperscript || type == PostSuperscript); + } + + return false; +} bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const { if (!m_renderer) return true; - // Ignore items that were created for layout purposes only. - if (m_renderer->isRenderMathMLBlock() && toRenderMathMLBlock(m_renderer)->ignoreInAccessibilityTree()) - return true; - - // Ignore anonymous renderers inside math blocks. + // We ignore anonymous renderers inside math blocks. + // However, we do not exclude anonymous RenderMathMLOperator nodes created by the mfenced element nor RenderText nodes created by math operators so that the text can be exposed by AccessibilityRenderObject::textUnderElement. if (m_renderer->isAnonymous()) { + if (m_renderer->isRenderMathMLOperator()) + return false; for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { if (parent->isMathElement()) - return true; + return !(m_renderer->isText() && ancestorsOfType<RenderMathMLOperator>(*m_renderer).first()); } } @@ -3599,38 +3745,37 @@ bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const AccessibilityObject* AccessibilityRenderObject::mathRadicandObject() { if (!isMathRoot()) - return 0; - - AccessibilityObject::AccessibilityChildrenVector children = this->children(); - if (children.size() < 1) - return 0; - - // The radicand is the value being rooted and must be listed first. - return children[0].get(); + return nullptr; + RenderMathMLRoot* root = &downcast<RenderMathMLRoot>(*m_renderer); + AccessibilityObject* rootRadicandWrapper = axObjectCache()->getOrCreate(root->baseWrapper()); + if (!rootRadicandWrapper) + return nullptr; + AccessibilityObject* rootRadicand = rootRadicandWrapper->children().first().get(); + ASSERT(rootRadicand && children().contains(rootRadicand)); + return rootRadicand; } AccessibilityObject* AccessibilityRenderObject::mathRootIndexObject() { if (!isMathRoot()) - return 0; - - AccessibilityObject::AccessibilityChildrenVector children = this->children(); - if (children.size() != 2) - return 0; - - // The index in a root is the value which determines if it's a square, cube, etc, root - // and must be listed second. - return children[1].get(); + return nullptr; + RenderMathMLRoot* root = &downcast<RenderMathMLRoot>(*m_renderer); + AccessibilityObject* rootIndexWrapper = axObjectCache()->getOrCreate(root->indexWrapper()); + if (!rootIndexWrapper) + return nullptr; + AccessibilityObject* rootIndex = rootIndexWrapper->children().first().get(); + ASSERT(rootIndex && children().contains(rootIndex)); + return rootIndex; } AccessibilityObject* AccessibilityRenderObject::mathNumeratorObject() { if (!isMathFraction()) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); if (children.size() != 2) - return 0; + return nullptr; return children[0].get(); } @@ -3638,11 +3783,11 @@ AccessibilityObject* AccessibilityRenderObject::mathNumeratorObject() AccessibilityObject* AccessibilityRenderObject::mathDenominatorObject() { if (!isMathFraction()) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); if (children.size() != 2) - return 0; + return nullptr; return children[1].get(); } @@ -3650,78 +3795,78 @@ AccessibilityObject* AccessibilityRenderObject::mathDenominatorObject() AccessibilityObject* AccessibilityRenderObject::mathUnderObject() { if (!isMathUnderOver() || !node()) - return 0; + return nullptr; - AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); if (children.size() < 2) - return 0; + return nullptr; if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag)) return children[1].get(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::mathOverObject() { if (!isMathUnderOver() || !node()) - return 0; + return nullptr; - AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); if (children.size() < 2) - return 0; + return nullptr; if (node()->hasTagName(MathMLNames::moverTag)) return children[1].get(); if (node()->hasTagName(MathMLNames::munderoverTag)) return children[2].get(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::mathBaseObject() { if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript()) - return 0; + return nullptr; - AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); // The base object in question is always the first child. if (children.size() > 0) return children[0].get(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::mathSubscriptObject() { if (!isMathSubscriptSuperscript() || !node()) - return 0; + return nullptr; - AccessibilityChildrenVector children = this->children(); + const auto& children = this->children(); if (children.size() < 2) - return 0; + return nullptr; if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag)) return children[1].get(); - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::mathSuperscriptObject() { if (!isMathSubscriptSuperscript() || !node()) - return 0; - - AccessibilityChildrenVector children = this->children(); - if (children.size() < 2) - return 0; + return nullptr; - if (node()->hasTagName(MathMLNames::msupTag)) + const auto& children = this->children(); + unsigned count = children.size(); + + if (count >= 2 && node()->hasTagName(MathMLNames::msupTag)) return children[1].get(); - if (node()->hasTagName(MathMLNames::msubsupTag)) + + if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag)) return children[2].get(); - return 0; + return nullptr; } String AccessibilityRenderObject::mathFencedOpenString() const @@ -3746,7 +3891,7 @@ void AccessibilityRenderObject::mathPrescripts(AccessibilityMathMultiscriptPairs return; bool foundPrescript = false; - pair<AccessibilityObject*, AccessibilityObject*> prescriptPair; + std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair; for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { if (foundPrescript) { AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); @@ -3756,8 +3901,8 @@ void AccessibilityRenderObject::mathPrescripts(AccessibilityMathMultiscriptPairs else { prescriptPair.second = axChild; prescripts.append(prescriptPair); - prescriptPair.first = 0; - prescriptPair.second = 0; + prescriptPair.first = nullptr; + prescriptPair.second = nullptr; } } } else if (child->hasTagName(MathMLNames::mprescriptsTag)) @@ -3776,7 +3921,7 @@ void AccessibilityRenderObject::mathPostscripts(AccessibilityMathMultiscriptPair // In Multiscripts, the post-script elements start after the first element (which is the base) // and continue until a <mprescripts> tag is found - pair<AccessibilityObject*, AccessibilityObject*> postscriptPair; + std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair; bool foundBaseElement = false; for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { if (child->hasTagName(MathMLNames::mprescriptsTag)) @@ -3791,8 +3936,8 @@ void AccessibilityRenderObject::mathPostscripts(AccessibilityMathMultiscriptPair else { postscriptPair.second = axChild; postscripts.append(postscriptPair); - postscriptPair.first = 0; - postscriptPair.second = 0; + postscriptPair.first = nullptr; + postscriptPair.second = nullptr; } } } @@ -3804,10 +3949,10 @@ void AccessibilityRenderObject::mathPostscripts(AccessibilityMathMultiscriptPair int AccessibilityRenderObject::mathLineThickness() const { - if (!isMathFraction()) + if (!is<RenderMathMLFraction>(m_renderer)) return -1; - return toRenderMathMLFraction(m_renderer)->lineThickness(); + return downcast<RenderMathMLFraction>(*m_renderer).lineThickness(); } #endif diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.h b/Source/WebCore/accessibility/AccessibilityRenderObject.h index c4512f492..709245d48 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.h +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -57,185 +57,183 @@ class VisibleSelection; class Widget; class AccessibilityRenderObject : public AccessibilityNodeObject { -protected: - explicit AccessibilityRenderObject(RenderObject*); public: - static PassRefPtr<AccessibilityRenderObject> create(RenderObject*); + static Ref<AccessibilityRenderObject> create(RenderObject*); virtual ~AccessibilityRenderObject(); - - virtual bool isAccessibilityRenderObject() const { return true; } - virtual void init(); + virtual void init() override; - virtual bool isAttachment() const; - virtual bool isFileUploadButton() const; + virtual bool isAttachment() const override; + virtual bool isFileUploadButton() const override; - virtual bool isSelected() const; - virtual bool isFocused() const; - virtual bool isLoaded() const; - virtual bool isOffScreen() const; - virtual bool isReadOnly() const; - virtual bool isUnvisited() const; - virtual bool isVisited() const; - virtual bool isLinked() const; - virtual bool hasBoldFont() const; - virtual bool hasItalicFont() const; - virtual bool hasPlainText() const; - virtual bool hasSameFont(RenderObject*) const; - virtual bool hasSameFontColor(RenderObject*) const; - virtual bool hasSameStyle(RenderObject*) const; - virtual bool hasUnderline() const; + virtual bool isSelected() const override; + virtual bool isFocused() const override; + virtual bool isLoaded() const override; + virtual bool isOffScreen() const override; + virtual bool isUnvisited() const override; + virtual bool isVisited() const override; + virtual bool isLinked() const override; + virtual bool hasBoldFont() const override; + virtual bool hasItalicFont() const override; + virtual bool hasPlainText() const override; + virtual bool hasSameFont(RenderObject*) const override; + virtual bool hasSameFontColor(RenderObject*) const override; + virtual bool hasSameStyle(RenderObject*) const override; + virtual bool hasUnderline() const override; - virtual bool canSetTextRangeAttributes() const; - virtual bool canSetValueAttribute() const; - virtual bool canSetExpandedAttribute() const; + virtual bool canSetTextRangeAttributes() const override; + virtual bool canSetExpandedAttribute() const override; - virtual void setAccessibleName(const AtomicString&); + virtual void setAccessibleName(const AtomicString&) override; // Provides common logic used by all elements when determining isIgnored. - virtual AccessibilityObjectInclusion defaultObjectInclusion() const; + virtual AccessibilityObjectInclusion defaultObjectInclusion() const override; - virtual int layoutCount() const; - virtual double estimatedLoadingProgress() const; + virtual int layoutCount() const override; + virtual double estimatedLoadingProgress() const override; - virtual AccessibilityObject* firstChild() const; - virtual AccessibilityObject* lastChild() const; - virtual AccessibilityObject* previousSibling() const; - virtual AccessibilityObject* nextSibling() const; - virtual AccessibilityObject* parentObject() const; - virtual AccessibilityObject* parentObjectIfExists() const; - virtual AccessibilityObject* observableObject() const; - virtual void linkedUIElements(AccessibilityChildrenVector&) const; - virtual bool exposesTitleUIElement() const; - virtual AccessibilityObject* titleUIElement() const; - virtual AccessibilityObject* correspondingControlForLabelElement() const; - virtual AccessibilityObject* correspondingLabelForControlElement() const; + virtual AccessibilityObject* firstChild() const override; + virtual AccessibilityObject* lastChild() const override; + virtual AccessibilityObject* previousSibling() const override; + virtual AccessibilityObject* nextSibling() const override; + virtual AccessibilityObject* parentObject() const override; + virtual AccessibilityObject* parentObjectIfExists() const override; + virtual AccessibilityObject* observableObject() const override; + virtual void linkedUIElements(AccessibilityChildrenVector&) const override; + virtual bool exposesTitleUIElement() const override; + virtual AccessibilityObject* titleUIElement() const override; + virtual AccessibilityObject* correspondingControlForLabelElement() const override; + virtual AccessibilityObject* correspondingLabelForControlElement() const override; - virtual void ariaOwnsElements(AccessibilityChildrenVector&) const; - virtual bool supportsARIAOwns() const; - virtual bool isPresentationalChildOfAriaRole() const; - virtual bool ariaRoleHasPresentationalChildren() const; + virtual void ariaOwnsElements(AccessibilityChildrenVector&) const override; + virtual bool supportsARIAOwns() const override; + virtual bool isPresentationalChildOfAriaRole() const override; + virtual bool ariaRoleHasPresentationalChildren() const override; // Should be called on the root accessibility object to kick off a hit test. - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const; + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; - FrameView* frameViewIfRenderView() const; - virtual Element* anchorElement() const; + virtual Element* anchorElement() const override; - virtual LayoutRect boundingBoxRect() const; - virtual LayoutRect elementRect() const; - virtual IntPoint clickPoint(); + virtual LayoutRect boundingBoxRect() const override; + virtual LayoutRect elementRect() const override; + virtual IntPoint clickPoint() override; void setRenderer(RenderObject*); - virtual RenderObject* renderer() const { return m_renderer; } + virtual RenderObject* renderer() const override { return m_renderer; } RenderBoxModelObject* renderBoxModelObject() const; - virtual Node* node() const; + virtual Node* node() const override; - virtual Document* document() const; + virtual Document* document() const override; RenderView* topRenderer() const; RenderTextControl* textControl() const; - FrameView* topDocumentFrameView() const; - Document* topDocument() const; HTMLLabelElement* labelElementContainer() const; - virtual KURL url() const; - virtual PlainTextRange selectedTextRange() const; - virtual VisibleSelection selection() const; - virtual String stringValue() const; - virtual String helpText() const; - virtual String textUnderElement(AccessibilityTextUnderElementMode = TextUnderElementModeSkipIgnoredChildren) const; - virtual String text() const; - virtual int textLength() const; - virtual String selectedText() const; - virtual const AtomicString& accessKey() const; + virtual URL url() const override; + virtual PlainTextRange selectedTextRange() const override; + virtual VisibleSelection selection() const override; + virtual String stringValue() const override; + virtual String helpText() const override; + virtual String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; + virtual String text() const override; + virtual int textLength() const override; + virtual String selectedText() const override; + virtual const AtomicString& accessKey() const override; virtual const String& actionVerb() const; - virtual Widget* widget() const; - virtual Widget* widgetForAttachmentView() const; + virtual Widget* widget() const override; + virtual Widget* widgetForAttachmentView() const override; virtual void getDocumentLinks(AccessibilityChildrenVector&); - virtual FrameView* documentFrameView() const; + virtual FrameView* documentFrameView() const override; - virtual void clearChildren(); - virtual void updateChildrenIfNecessary(); + virtual void clearChildren() override; + virtual void updateChildrenIfNecessary() override; - virtual void setFocused(bool); - virtual void setSelectedTextRange(const PlainTextRange&); - virtual void setValue(const String&); - virtual void setSelectedRows(AccessibilityChildrenVector&); - virtual AccessibilityOrientation orientation() const; + virtual void setFocused(bool) override; + virtual void setSelectedTextRange(const PlainTextRange&) override; + virtual void setValue(const String&) override; + virtual void setSelectedRows(AccessibilityChildrenVector&) override; + virtual AccessibilityOrientation orientation() const override; - virtual void detach(); - virtual void textChanged(); - virtual void addChildren(); - virtual bool canHaveChildren() const; - virtual void selectedChildren(AccessibilityChildrenVector&); - virtual void visibleChildren(AccessibilityChildrenVector&); - virtual void tabChildren(AccessibilityChildrenVector&); - virtual bool shouldFocusActiveDescendant() const; + virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; + virtual void textChanged() override; + virtual void addChildren() override; + virtual bool canHaveChildren() const override; + virtual void selectedChildren(AccessibilityChildrenVector&) override; + virtual void visibleChildren(AccessibilityChildrenVector&) override; + virtual void tabChildren(AccessibilityChildrenVector&) override; + virtual bool shouldFocusActiveDescendant() const override; bool shouldNotifyActiveDescendant() const; - virtual AccessibilityObject* activeDescendant() const; - virtual void handleActiveDescendantChanged(); - virtual void handleAriaExpandedChanged(); + virtual AccessibilityObject* activeDescendant() const override; + virtual void handleActiveDescendantChanged() override; + virtual void handleAriaExpandedChanged() override; - virtual VisiblePositionRange visiblePositionRange() const; - virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const; - virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const; - virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const; - virtual bool supportsARIAFlowTo() const; - virtual void ariaFlowToElements(AccessibilityChildrenVector&) const; - virtual bool ariaHasPopup() const; + virtual VisiblePositionRange visiblePositionRange() const override; + virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const override; + virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const override; + virtual void setSelectedVisiblePositionRange(const VisiblePositionRange&) const override; + virtual bool supportsARIAFlowTo() const override; + virtual void ariaFlowToElements(AccessibilityChildrenVector&) const override; + virtual bool supportsARIADescribedBy() const override; + virtual void ariaDescribedByElements(AccessibilityChildrenVector&) const override; + virtual bool supportsARIAControls() const override; + virtual void ariaControlsElements(AccessibilityChildrenVector&) const override; + virtual bool ariaHasPopup() const override; - virtual bool supportsARIADropping() const; - virtual bool supportsARIADragging() const; - virtual bool isARIAGrabbed(); - virtual void determineARIADropEffects(Vector<String>&); + virtual bool supportsARIADropping() const override; + virtual bool supportsARIADragging() const override; + virtual bool isARIAGrabbed() override; + virtual void determineARIADropEffects(Vector<String>&) override; - virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const; - virtual VisiblePosition visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const; - virtual int index(const VisiblePosition&) const; + virtual VisiblePosition visiblePositionForPoint(const IntPoint&) const override; + virtual VisiblePosition visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const override; + virtual int index(const VisiblePosition&) const override; - virtual VisiblePosition visiblePositionForIndex(int) const; - virtual int indexForVisiblePosition(const VisiblePosition&) const; + virtual VisiblePosition visiblePositionForIndex(int) const override; + virtual int indexForVisiblePosition(const VisiblePosition&) const override; - virtual void lineBreaks(Vector<int>&) const; - virtual PlainTextRange doAXRangeForLine(unsigned) const; - virtual PlainTextRange doAXRangeForIndex(unsigned) const; + virtual void lineBreaks(Vector<int>&) const override; + virtual PlainTextRange doAXRangeForLine(unsigned) const override; + virtual PlainTextRange doAXRangeForIndex(unsigned) const override; - virtual String doAXStringForRange(const PlainTextRange&) const; - virtual IntRect doAXBoundsForRange(const PlainTextRange&) const; + virtual String doAXStringForRange(const PlainTextRange&) const override; + virtual IntRect doAXBoundsForRange(const PlainTextRange&) const override; - virtual String stringValueForMSAA() const; - virtual String stringRoleForMSAA() const; - virtual String nameForMSAA() const; - virtual String descriptionForMSAA() const; - virtual AccessibilityRole roleValueForMSAA() const; + virtual String stringValueForMSAA() const override; + virtual String stringRoleForMSAA() const override; + virtual String nameForMSAA() const override; + virtual String descriptionForMSAA() const override; + virtual AccessibilityRole roleValueForMSAA() const override; - virtual String passwordFieldValue() const; + virtual String passwordFieldValue() const override; protected: - RenderObject* m_renderer; - + explicit AccessibilityRenderObject(RenderObject*); void setRenderObject(RenderObject* renderer) { m_renderer = renderer; } + void ariaElementsFromAttribute(AccessibilityChildrenVector&, const QualifiedName&) const; bool needsToUpdateChildren() const { return m_childrenDirty; } - ScrollableArea* getScrollableAreaIfScrollable() const; - void scrollTo(const IntPoint&) const; + virtual ScrollableArea* getScrollableAreaIfScrollable() const override; + virtual void scrollTo(const IntPoint&) const override; - virtual bool isDetached() const { return !m_renderer; } + virtual bool isDetached() const override { return !m_renderer; } - virtual AccessibilityRole determineAccessibilityRole(); - virtual bool computeAccessibilityIsIgnored() const; + virtual AccessibilityRole determineAccessibilityRole() override; + virtual bool computeAccessibilityIsIgnored() const override; + + RenderObject* m_renderer; private: + virtual bool isAccessibilityRenderObject() const override final { return true; } void ariaListboxSelectedChildren(AccessibilityChildrenVector&); void ariaListboxVisibleChildren(AccessibilityChildrenVector&); bool isAllowedChildOfTree() const; bool hasTextAlternative() const; String positionalDescriptionForMSAA() const; - PlainTextRange ariaSelectedTextRange() const; + PlainTextRange documentBasedSelectedTextRange() const; Element* rootEditableElementForPosition(const Position&) const; bool nodeIsTextControl(const Node*) const; - virtual void setNeedsToUpdateChildren() { m_childrenDirty = true; } - virtual Path elementPath() const; + virtual void setNeedsToUpdateChildren() override { m_childrenDirty = true; } + virtual Path elementPath() const override; bool isTabItemSelected() const; LayoutRect checkboxOrRadioRect() const; @@ -243,9 +241,9 @@ private: AccessibilityObject* internalLinkElement() const; AccessibilityObject* accessibilityImageMapHitTest(HTMLAreaElement*, const IntPoint&) const; AccessibilityObject* accessibilityParentForImageMap(HTMLMapElement*) const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; - bool renderObjectIsObservable(RenderObject*) const; + bool renderObjectIsObservable(RenderObject&) const; RenderObject* renderParentObject() const; bool isDescendantOfElementType(const QualifiedName& tagName) const; @@ -254,7 +252,7 @@ private: AccessibilitySVGRoot* remoteSVGRootElement() const; AccessibilityObject* remoteSVGElementHitTest(const IntPoint&) const; void offsetBoundingBoxForRemoteSVGElement(LayoutRect&) const; - virtual bool supportsPath() const; + virtual bool supportsPath() const override; void addHiddenChildren(); void addTextFieldChildren(); @@ -262,92 +260,87 @@ private: void addCanvasChildren(); void addAttachmentChildren(); void addRemoteSVGChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void updateAttachmentViewParents(); #endif - + virtual String expandedTextValue() const override; + virtual bool supportsExpandedTextValue() const override; + void updateRoleAfterChildrenCreation(); + void ariaSelectedRows(AccessibilityChildrenVector&); bool elementAttributeValue(const QualifiedName&) const; void setElementAttributeValue(const QualifiedName&, bool); - virtual ESpeak speakProperty() const; + virtual ESpeak speakProperty() const override; - virtual const AtomicString& ariaLiveRegionStatus() const; - virtual const AtomicString& ariaLiveRegionRelevant() const; - virtual bool ariaLiveRegionAtomic() const; - virtual bool ariaLiveRegionBusy() const; + virtual const String ariaLiveRegionStatus() const override; + virtual const AtomicString& ariaLiveRegionRelevant() const override; + virtual bool ariaLiveRegionAtomic() const override; + virtual bool ariaLiveRegionBusy() const override; - bool inheritsPresentationalRole() const; + virtual bool inheritsPresentationalRole() const override; + + bool shouldGetTextFromNode(AccessibilityTextUnderElementMode) const; #if ENABLE(MATHML) // All math elements return true for isMathElement(). - virtual bool isMathElement() const; - virtual bool isMathFraction() const; - virtual bool isMathFenced() const; - virtual bool isMathSubscriptSuperscript() const; - virtual bool isMathRow() const; - virtual bool isMathUnderOver() const; - virtual bool isMathRoot() const; - virtual bool isMathSquareRoot() const; - virtual bool isMathText() const; - virtual bool isMathNumber() const; - virtual bool isMathOperator() const; - virtual bool isMathFenceOperator() const; - virtual bool isMathSeparatorOperator() const; - virtual bool isMathIdentifier() const; - virtual bool isMathTable() const; - virtual bool isMathTableRow() const; - virtual bool isMathTableCell() const; - virtual bool isMathMultiscript() const; + virtual bool isMathElement() const override; + virtual bool isMathFraction() const override; + virtual bool isMathFenced() const override; + virtual bool isMathSubscriptSuperscript() const override; + virtual bool isMathRow() const override; + virtual bool isMathUnderOver() const override; + virtual bool isMathRoot() const override; + virtual bool isMathSquareRoot() const override; + virtual bool isMathText() const override; + virtual bool isMathNumber() const override; + virtual bool isMathOperator() const override; + virtual bool isMathFenceOperator() const override; + virtual bool isMathSeparatorOperator() const override; + virtual bool isMathIdentifier() const override; + virtual bool isMathTable() const override; + virtual bool isMathTableRow() const override; + virtual bool isMathTableCell() const override; + virtual bool isMathMultiscript() const override; + virtual bool isMathToken() const override; + virtual bool isMathScriptObject(AccessibilityMathScriptObjectType) const override; + virtual bool isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType) const override; // Generic components. - virtual AccessibilityObject* mathBaseObject(); + virtual AccessibilityObject* mathBaseObject() override; // Root components. - virtual AccessibilityObject* mathRadicandObject(); - virtual AccessibilityObject* mathRootIndexObject(); + virtual AccessibilityObject* mathRadicandObject() override; + virtual AccessibilityObject* mathRootIndexObject() override; // Fraction components. - virtual AccessibilityObject* mathNumeratorObject(); - virtual AccessibilityObject* mathDenominatorObject(); + virtual AccessibilityObject* mathNumeratorObject() override; + virtual AccessibilityObject* mathDenominatorObject() override; // Under over components. - virtual AccessibilityObject* mathUnderObject(); - virtual AccessibilityObject* mathOverObject(); + virtual AccessibilityObject* mathUnderObject() override; + virtual AccessibilityObject* mathOverObject() override; // Subscript/superscript components. - virtual AccessibilityObject* mathSubscriptObject(); - virtual AccessibilityObject* mathSuperscriptObject(); + virtual AccessibilityObject* mathSubscriptObject() override; + virtual AccessibilityObject* mathSuperscriptObject() override; // Fenced components. - virtual String mathFencedOpenString() const; - virtual String mathFencedCloseString() const; - virtual int mathLineThickness() const; + virtual String mathFencedOpenString() const override; + virtual String mathFencedCloseString() const override; + virtual int mathLineThickness() const override; // Multiscripts components. - virtual void mathPrescripts(AccessibilityMathMultiscriptPairs&); - virtual void mathPostscripts(AccessibilityMathMultiscriptPairs&); + virtual void mathPrescripts(AccessibilityMathMultiscriptPairs&) override; + virtual void mathPostscripts(AccessibilityMathMultiscriptPairs&) override; bool isIgnoredElementWithinMathTree() const; #endif }; -inline AccessibilityRenderObject* toAccessibilityRenderObject(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isAccessibilityRenderObject()); - return static_cast<AccessibilityRenderObject*>(object); -} - -inline const AccessibilityRenderObject* toAccessibilityRenderObject(const AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isAccessibilityRenderObject()); - return static_cast<const AccessibilityRenderObject*>(object); -} - -// This will catch anyone doing an unnecessary cast. -void toAccessibilityRenderObject(const AccessibilityRenderObject*); - } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityRenderObject, isAccessibilityRenderObject()) + #endif // AccessibilityRenderObject_h diff --git a/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp b/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp index 08a2d589d..2b62d92f2 100644 --- a/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp +++ b/Source/WebCore/accessibility/AccessibilitySVGRoot.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -35,7 +35,7 @@ namespace WebCore { AccessibilitySVGRoot::AccessibilitySVGRoot(RenderObject* renderer) : AccessibilityRenderObject(renderer) - , m_parent(0) + , m_parent(nullptr) { } @@ -43,9 +43,9 @@ AccessibilitySVGRoot::~AccessibilitySVGRoot() { } -PassRefPtr<AccessibilitySVGRoot> AccessibilitySVGRoot::create(RenderObject* renderer) +Ref<AccessibilitySVGRoot> AccessibilitySVGRoot::create(RenderObject* renderer) { - return adoptRef(new AccessibilitySVGRoot(renderer)); + return adoptRef(*new AccessibilitySVGRoot(renderer)); } AccessibilityObject* AccessibilitySVGRoot::parentObject() const diff --git a/Source/WebCore/accessibility/AccessibilitySVGRoot.h b/Source/WebCore/accessibility/AccessibilitySVGRoot.h index 519229db3..44eedc591 100644 --- a/Source/WebCore/accessibility/AccessibilitySVGRoot.h +++ b/Source/WebCore/accessibility/AccessibilitySVGRoot.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -33,29 +33,24 @@ namespace WebCore { -class AccessibilitySVGRoot : public AccessibilityRenderObject { - -protected: - explicit AccessibilitySVGRoot(RenderObject*); +class AccessibilitySVGRoot final : public AccessibilityRenderObject { public: - static PassRefPtr<AccessibilitySVGRoot> create(RenderObject*); + static Ref<AccessibilitySVGRoot> create(RenderObject*); virtual ~AccessibilitySVGRoot(); void setParent(AccessibilityObject* parent) { m_parent = parent; } - + private: - AccessibilityObject* m_parent; + explicit AccessibilitySVGRoot(RenderObject*); - virtual AccessibilityObject* parentObject() const; - virtual bool isAccessibilitySVGRoot() const { return true; } -}; + virtual AccessibilityObject* parentObject() const override; + virtual bool isAccessibilitySVGRoot() const override { return true; } -inline AccessibilitySVGRoot* toAccessibilitySVGRoot(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isAccessibilitySVGRoot()); - return static_cast<AccessibilitySVGRoot*>(object); -} + AccessibilityObject* m_parent; +}; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySVGRoot, isAccessibilitySVGRoot()) + #endif // AccessibilitySVGRoot_h diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.cpp b/Source/WebCore/accessibility/AccessibilityScrollView.cpp index 26ff62fc0..ed5aa5844 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollView.cpp +++ b/Source/WebCore/accessibility/AccessibilityScrollView.cpp @@ -31,7 +31,7 @@ #include "Frame.h" #include "FrameView.h" #include "HTMLFrameOwnerElement.h" -#include "RenderPart.h" +#include "RenderElement.h" #include "ScrollView.h" #include "Widget.h" @@ -48,15 +48,15 @@ AccessibilityScrollView::~AccessibilityScrollView() ASSERT(isDetached()); } -void AccessibilityScrollView::detach() +void AccessibilityScrollView::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { - AccessibilityObject::detach(); - m_scrollView = 0; + AccessibilityObject::detach(detachmentType, cache); + m_scrollView = nullptr; } -PassRefPtr<AccessibilityScrollView> AccessibilityScrollView::create(ScrollView* view) +Ref<AccessibilityScrollView> AccessibilityScrollView::create(ScrollView* view) { - return adoptRef(new AccessibilityScrollView(view)); + return adoptRef(*new AccessibilityScrollView(view)); } AccessibilityObject* AccessibilityScrollView::scrollBar(AccessibilityOrientation orientation) @@ -64,13 +64,15 @@ AccessibilityObject* AccessibilityScrollView::scrollBar(AccessibilityOrientation updateScrollbars(); switch (orientation) { + // ARIA 1.1 Elements with the role scrollbar have an implicit aria-orientation value of vertical. + case AccessibilityOrientationUndefined: case AccessibilityOrientationVertical: - return m_verticalScrollbar ? m_verticalScrollbar.get() : 0; + return m_verticalScrollbar ? m_verticalScrollbar.get() : nullptr; case AccessibilityOrientationHorizontal: - return m_horizontalScrollbar ? m_horizontalScrollbar.get() : 0; + return m_horizontalScrollbar ? m_horizontalScrollbar.get() : nullptr; } - return 0; + return nullptr; } // If this is WebKit1 then the native scroll view needs to return the @@ -86,15 +88,31 @@ Widget* AccessibilityScrollView::widgetForAttachmentView() const return m_scrollView; } -void AccessibilityScrollView::updateChildrenIfNecessary() +bool AccessibilityScrollView::canSetFocusAttribute() const { - if (m_childrenDirty) - clearChildren(); - - if (!m_haveChildren) - addChildren(); + AccessibilityObject* webArea = webAreaObject(); + return webArea && webArea->canSetFocusAttribute(); +} - updateScrollbars(); +bool AccessibilityScrollView::isFocused() const +{ + AccessibilityObject* webArea = webAreaObject(); + return webArea && webArea->isFocused(); +} + +void AccessibilityScrollView::setFocused(bool focused) +{ + if (AccessibilityObject* webArea = webAreaObject()) + webArea->setFocused(focused); +} + +void AccessibilityScrollView::updateChildrenIfNecessary() +{ + // Always update our children when asked for them so that we don't inadvertently cache them after + // a new web area has been created for this scroll view (like when moving back and forth through history). + // Since a ScrollViews children will always be relatively small and limited this should not be a performance problem. + clearChildren(); + addChildren(); } void AccessibilityScrollView::updateScrollbars() @@ -106,14 +124,14 @@ void AccessibilityScrollView::updateScrollbars() m_horizontalScrollbar = addChildScrollbar(m_scrollView->horizontalScrollbar()); else if (!m_scrollView->horizontalScrollbar() && m_horizontalScrollbar) { removeChildScrollbar(m_horizontalScrollbar.get()); - m_horizontalScrollbar = 0; + m_horizontalScrollbar = nullptr; } if (m_scrollView->verticalScrollbar() && !m_verticalScrollbar) m_verticalScrollbar = addChildScrollbar(m_scrollView->verticalScrollbar()); else if (!m_scrollView->verticalScrollbar() && m_verticalScrollbar) { removeChildScrollbar(m_verticalScrollbar.get()); - m_verticalScrollbar = 0; + m_verticalScrollbar = nullptr; } } @@ -129,19 +147,23 @@ void AccessibilityScrollView::removeChildScrollbar(AccessibilityObject* scrollba AccessibilityScrollbar* AccessibilityScrollView::addChildScrollbar(Scrollbar* scrollbar) { if (!scrollbar) - return 0; + return nullptr; - AccessibilityScrollbar* scrollBarObject = static_cast<AccessibilityScrollbar*>(axObjectCache()->getOrCreate(scrollbar)); - scrollBarObject->setParent(this); - m_children.append(scrollBarObject); - return scrollBarObject; + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + auto& scrollBarObject = downcast<AccessibilityScrollbar>(*cache->getOrCreate(scrollbar)); + scrollBarObject.setParent(this); + m_children.append(&scrollBarObject); + return &scrollBarObject; } void AccessibilityScrollView::clearChildren() { AccessibilityObject::clearChildren(); - m_verticalScrollbar = 0; - m_horizontalScrollbar = 0; + m_verticalScrollbar = nullptr; + m_horizontalScrollbar = nullptr; } bool AccessibilityScrollView::computeAccessibilityIsIgnored() const @@ -167,21 +189,24 @@ void AccessibilityScrollView::addChildren() AccessibilityObject* AccessibilityScrollView::webAreaObject() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - Document* doc = toFrameView(m_scrollView)->frame()->document(); - if (!doc || !doc->renderer()) - return 0; + Document* document = downcast<FrameView>(*m_scrollView).frame().document(); + if (!document || !document->hasLivingRenderTree()) + return nullptr; - return axObjectCache()->getOrCreate(doc); + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(document); + + return nullptr; } AccessibilityObject* AccessibilityScrollView::accessibilityHitTest(const IntPoint& point) const { AccessibilityObject* webArea = webAreaObject(); if (!webArea) - return 0; + return nullptr; if (m_horizontalScrollbar && m_horizontalScrollbar->elementRect().contains(point)) return m_horizontalScrollbar.get(); @@ -196,39 +221,52 @@ LayoutRect AccessibilityScrollView::elementRect() const if (!m_scrollView) return LayoutRect(); - return m_scrollView->frameRect(); + LayoutRect rect = m_scrollView->frameRect(); + float topContentInset = m_scrollView->topContentInset(); + // Top content inset pushes the frame down and shrinks it. + rect.move(0, topContentInset); + rect.contract(0, topContentInset); + return rect; } FrameView* AccessibilityScrollView::documentFrameView() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - return toFrameView(m_scrollView); + return downcast<FrameView>(m_scrollView); } AccessibilityObject* AccessibilityScrollView::parentObject() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; - - HTMLFrameOwnerElement* owner = toFrameView(m_scrollView)->frame()->ownerElement(); + if (!is<FrameView>(m_scrollView)) + return nullptr; + + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + HTMLFrameOwnerElement* owner = downcast<FrameView>(*m_scrollView).frame().ownerElement(); if (owner && owner->renderer()) - return axObjectCache()->getOrCreate(owner); + return cache->getOrCreate(owner); - return 0; + return nullptr; } AccessibilityObject* AccessibilityScrollView::parentObjectIfExists() const { - if (!m_scrollView || !m_scrollView->isFrameView()) - return 0; + if (!is<FrameView>(m_scrollView)) + return nullptr; - HTMLFrameOwnerElement* owner = toFrameView(m_scrollView)->frame()->ownerElement(); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + + HTMLFrameOwnerElement* owner = downcast<FrameView>(m_scrollView)->frame().ownerElement(); if (owner && owner->renderer()) - return axObjectCache()->get(owner); + return cache->get(owner); - return 0; + return nullptr; } ScrollableArea* AccessibilityScrollView::getScrollableAreaIfScrollable() const diff --git a/Source/WebCore/accessibility/AccessibilityScrollView.h b/Source/WebCore/accessibility/AccessibilityScrollView.h index 20ab64ea6..0c464bb09 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollView.h +++ b/Source/WebCore/accessibility/AccessibilityScrollView.h @@ -34,44 +34,46 @@ class AccessibilityScrollbar; class Scrollbar; class ScrollView; -class AccessibilityScrollView : public AccessibilityObject { +class AccessibilityScrollView final : public AccessibilityObject { public: - static PassRefPtr<AccessibilityScrollView> create(ScrollView*); - virtual AccessibilityRole roleValue() const { return ScrollAreaRole; } + static Ref<AccessibilityScrollView> create(ScrollView*); + virtual AccessibilityRole roleValue() const override { return ScrollAreaRole; } ScrollView* scrollView() const { return m_scrollView; } virtual ~AccessibilityScrollView(); - virtual void detach(); + virtual void detach(AccessibilityDetachmentType, AXObjectCache*) override; + + AccessibilityObject* webAreaObject() const; -protected: - virtual ScrollableArea* getScrollableAreaIfScrollable() const; - virtual void scrollTo(const IntPoint&) const; - private: explicit AccessibilityScrollView(ScrollView*); - virtual bool computeAccessibilityIsIgnored() const; - virtual bool isAccessibilityScrollView() const { return true; } - virtual bool isEnabled() const { return true; } + virtual ScrollableArea* getScrollableAreaIfScrollable() const override; + virtual void scrollTo(const IntPoint&) const override; + virtual bool computeAccessibilityIsIgnored() const override; + virtual bool isAccessibilityScrollView() const override { return true; } + virtual bool isEnabled() const override { return true; } - virtual bool isAttachment() const; - virtual Widget* widgetForAttachmentView() const; + virtual bool isAttachment() const override; + virtual Widget* widgetForAttachmentView() const override; - virtual AccessibilityObject* scrollBar(AccessibilityOrientation); - virtual void addChildren(); - virtual void clearChildren(); - virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const; - virtual void updateChildrenIfNecessary(); - virtual void setNeedsToUpdateChildren() { m_childrenDirty = true; } + virtual AccessibilityObject* scrollBar(AccessibilityOrientation) override; + virtual void addChildren() override; + virtual void clearChildren() override; + virtual AccessibilityObject* accessibilityHitTest(const IntPoint&) const override; + virtual void updateChildrenIfNecessary() override; + virtual void setNeedsToUpdateChildren() override { m_childrenDirty = true; } void updateScrollbars(); + virtual void setFocused(bool) override; + virtual bool canSetFocusAttribute() const override; + virtual bool isFocused() const override; - virtual FrameView* documentFrameView() const; - virtual LayoutRect elementRect() const; - virtual AccessibilityObject* parentObject() const; - virtual AccessibilityObject* parentObjectIfExists() const; + virtual FrameView* documentFrameView() const override; + virtual LayoutRect elementRect() const override; + virtual AccessibilityObject* parentObject() const override; + virtual AccessibilityObject* parentObjectIfExists() const override; - AccessibilityObject* webAreaObject() const; - virtual AccessibilityObject* firstChild() const { return webAreaObject(); } + virtual AccessibilityObject* firstChild() const override { return webAreaObject(); } AccessibilityScrollbar* addChildScrollbar(Scrollbar*); void removeChildScrollbar(AccessibilityObject*); @@ -80,16 +82,9 @@ private: RefPtr<AccessibilityObject> m_verticalScrollbar; bool m_childrenDirty; }; - -inline AccessibilityScrollView* toAccessibilityScrollView(AccessibilityObject* object) -{ - ASSERT(!object || object->isAccessibilityScrollView()); - if (!object->isAccessibilityScrollView()) - return 0; - - return static_cast<AccessibilityScrollView*>(object); -} } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityScrollView, isAccessibilityScrollView()) + #endif // AccessibilityScrollView_h diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.cpp b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp index bf6d042d7..a46d1b21f 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollbar.cpp +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010, 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -42,9 +42,9 @@ AccessibilityScrollbar::AccessibilityScrollbar(Scrollbar* scrollbar) ASSERT(scrollbar); } -PassRefPtr<AccessibilityScrollbar> AccessibilityScrollbar::create(Scrollbar* scrollbar) +Ref<AccessibilityScrollbar> AccessibilityScrollbar::create(Scrollbar* scrollbar) { - return adoptRef(new AccessibilityScrollbar(scrollbar)); + return adoptRef(*new AccessibilityScrollbar(scrollbar)); } LayoutRect AccessibilityScrollbar::elementRect() const @@ -59,21 +59,22 @@ Document* AccessibilityScrollbar::document() const { AccessibilityObject* parent = parentObject(); if (!parent) - return 0; + return nullptr; return parent->document(); } AccessibilityOrientation AccessibilityScrollbar::orientation() const { + // ARIA 1.1 Elements with the role scrollbar have an implicit aria-orientation value of vertical. if (!m_scrollbar) - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationVertical; if (m_scrollbar->orientation() == HorizontalScrollbar) return AccessibilityOrientationHorizontal; if (m_scrollbar->orientation() == VerticalScrollbar) return AccessibilityOrientationVertical; - return AccessibilityOrientationHorizontal; + return AccessibilityOrientationVertical; } bool AccessibilityScrollbar::isEnabled() const @@ -96,11 +97,8 @@ void AccessibilityScrollbar::setValue(float value) if (!m_scrollbar) return; - if (!m_scrollbar->scrollableArea()) - return; - float newValue = value * m_scrollbar->maximum(); - m_scrollbar->scrollableArea()->scrollToOffsetWithoutAnimation(m_scrollbar->orientation(), newValue); + m_scrollbar->scrollableArea().scrollToOffsetWithoutAnimation(m_scrollbar->orientation(), newValue); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityScrollbar.h b/Source/WebCore/accessibility/AccessibilityScrollbar.h index 6caeaea92..2af3c9537 100644 --- a/Source/WebCore/accessibility/AccessibilityScrollbar.h +++ b/Source/WebCore/accessibility/AccessibilityScrollbar.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -35,33 +35,35 @@ namespace WebCore { class Scrollbar; -class AccessibilityScrollbar : public AccessibilityMockObject { +class AccessibilityScrollbar final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityScrollbar> create(Scrollbar*); + static Ref<AccessibilityScrollbar> create(Scrollbar*); Scrollbar* scrollbar() const { return m_scrollbar.get(); } private: explicit AccessibilityScrollbar(Scrollbar*); - virtual bool canSetValueAttribute() const { return true; } - virtual bool canSetNumericValue() const { return true; } + virtual bool canSetValueAttribute() const override { return true; } + virtual bool canSetNumericValue() const override { return true; } - virtual bool isAccessibilityScrollbar() const { return true; } - virtual LayoutRect elementRect() const; + virtual bool isAccessibilityScrollbar() const override { return true; } + virtual LayoutRect elementRect() const override; - virtual AccessibilityRole roleValue() const { return ScrollBarRole; } - virtual AccessibilityOrientation orientation() const; - virtual Document* document() const; - virtual bool isEnabled() const; + virtual AccessibilityRole roleValue() const override { return ScrollBarRole; } + virtual AccessibilityOrientation orientation() const override; + virtual Document* document() const override; + virtual bool isEnabled() const override; // Assumes float [0..1] - virtual void setValue(float); - virtual float valueForRange() const; + virtual void setValue(float) override; + virtual float valueForRange() const override; RefPtr<Scrollbar> m_scrollbar; }; } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityScrollbar, isAccessibilityScrollbar()) + #endif // AccessibilityScrollbar_h diff --git a/Source/WebCore/accessibility/AccessibilitySlider.cpp b/Source/WebCore/accessibility/AccessibilitySlider.cpp index 51cc898ef..d7ba9e43d 100644 --- a/Source/WebCore/accessibility/AccessibilitySlider.cpp +++ b/Source/WebCore/accessibility/AccessibilitySlider.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -45,9 +45,9 @@ AccessibilitySlider::AccessibilitySlider(RenderObject* renderer) { } -PassRefPtr<AccessibilitySlider> AccessibilitySlider::create(RenderObject* renderer) +Ref<AccessibilitySlider> AccessibilitySlider::create(RenderObject* renderer) { - return adoptRef(new AccessibilitySlider(renderer)); + return adoptRef(*new AccessibilitySlider(renderer)); } AccessibilityOrientation AccessibilitySlider::orientation() const @@ -56,11 +56,9 @@ AccessibilityOrientation AccessibilitySlider::orientation() const if (!m_renderer) return AccessibilityOrientationHorizontal; - RenderStyle* style = m_renderer->style(); - if (!style) - return AccessibilityOrientationHorizontal; - - ControlPart styleAppearance = style->appearance(); + const RenderStyle& style = m_renderer->style(); + + ControlPart styleAppearance = style.appearance(); switch (styleAppearance) { case SliderThumbHorizontalPart: case SliderHorizontalPart: @@ -84,22 +82,22 @@ void AccessibilitySlider::addChildren() m_haveChildren = true; - AXObjectCache* cache = m_renderer->document()->axObjectCache(); + AXObjectCache* cache = m_renderer->document().axObjectCache(); - AccessibilitySliderThumb* thumb = static_cast<AccessibilitySliderThumb*>(cache->getOrCreate(SliderThumbRole)); - thumb->setParent(this); + auto& thumb = downcast<AccessibilitySliderThumb>(*cache->getOrCreate(SliderThumbRole)); + thumb.setParent(this); // Before actually adding the value indicator to the hierarchy, // allow the platform to make a final decision about it. - if (thumb->accessibilityIsIgnored()) - cache->remove(thumb->axObjectID()); + if (thumb.accessibilityIsIgnored()) + cache->remove(thumb.axObjectID()); else - m_children.append(thumb); + m_children.append(&thumb); } const AtomicString& AccessibilitySlider::getAttribute(const QualifiedName& attribute) const { - return element()->getAttribute(attribute); + return inputElement()->getAttribute(attribute); } AccessibilityObject* AccessibilitySlider::elementAccessibilityHitTest(const IntPoint& point) const @@ -115,35 +113,32 @@ AccessibilityObject* AccessibilitySlider::elementAccessibilityHitTest(const IntP float AccessibilitySlider::valueForRange() const { - return element()->value().toFloat(); + return inputElement()->value().toFloat(); } float AccessibilitySlider::maxValueForRange() const { - return static_cast<float>(element()->maximum()); + return static_cast<float>(inputElement()->maximum()); } float AccessibilitySlider::minValueForRange() const { - return static_cast<float>(element()->minimum()); + return static_cast<float>(inputElement()->minimum()); } void AccessibilitySlider::setValue(const String& value) { - HTMLInputElement* input = element(); + HTMLInputElement* input = inputElement(); if (input->value() == value) return; - input->setValue(value); - - // Fire change event manually, as RenderSlider::setValueForPosition does. - input->dispatchFormControlChangeEvent(); + input->setValue(value, DispatchChangeEvent); } -HTMLInputElement* AccessibilitySlider::element() const +HTMLInputElement* AccessibilitySlider::inputElement() const { - return toHTMLInputElement(m_renderer->node()); + return downcast<HTMLInputElement>(m_renderer->node()); } @@ -151,9 +146,9 @@ AccessibilitySliderThumb::AccessibilitySliderThumb() { } -PassRefPtr<AccessibilitySliderThumb> AccessibilitySliderThumb::create() +Ref<AccessibilitySliderThumb> AccessibilitySliderThumb::create() { - return adoptRef(new AccessibilitySliderThumb()); + return adoptRef(*new AccessibilitySliderThumb()); } LayoutRect AccessibilitySliderThumb::elementRect() const @@ -164,7 +159,9 @@ LayoutRect AccessibilitySliderThumb::elementRect() const RenderObject* sliderRenderer = m_parent->renderer(); if (!sliderRenderer || !sliderRenderer->isSlider()) return LayoutRect(); - return sliderThumbElementOf(sliderRenderer->node())->boundingBox(); + if (auto* thumbRenderer = downcast<RenderSlider>(*sliderRenderer).element().sliderThumbElement()->renderer()) + return thumbRenderer->absoluteBoundingBoxRect(); + return LayoutRect(); } bool AccessibilitySliderThumb::computeAccessibilityIsIgnored() const diff --git a/Source/WebCore/accessibility/AccessibilitySlider.h b/Source/WebCore/accessibility/AccessibilitySlider.h index 2c4dbbf2d..1f2e573c9 100644 --- a/Source/WebCore/accessibility/AccessibilitySlider.h +++ b/Source/WebCore/accessibility/AccessibilitySlider.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -37,52 +37,51 @@ namespace WebCore { class HTMLInputElement; class AccessibilitySlider : public AccessibilityRenderObject { - public: - static PassRefPtr<AccessibilitySlider> create(RenderObject*); + static Ref<AccessibilitySlider> create(RenderObject*); virtual ~AccessibilitySlider() { } protected: explicit AccessibilitySlider(RenderObject*); private: - HTMLInputElement* element() const; - virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const; + HTMLInputElement* inputElement() const; + virtual AccessibilityObject* elementAccessibilityHitTest(const IntPoint&) const override; - virtual AccessibilityRole roleValue() const { return SliderRole; } - virtual bool isSlider() const { return true; } - virtual bool isInputSlider() const { return true; } - virtual bool isControl() const { return true; } + virtual AccessibilityRole roleValue() const override { return SliderRole; } + virtual bool isSlider() const override final { return true; } + virtual bool isInputSlider() const override { return true; } + virtual bool isControl() const override { return true; } - virtual void addChildren(); + virtual void addChildren() override; - virtual bool canSetValueAttribute() const { return true; } + virtual bool canSetValueAttribute() const override { return true; } const AtomicString& getAttribute(const QualifiedName& attribute) const; - virtual void setValue(const String&); - virtual float valueForRange() const; - virtual float maxValueForRange() const; - virtual float minValueForRange() const; - virtual AccessibilityOrientation orientation() const; + virtual void setValue(const String&) override; + virtual float valueForRange() const override; + virtual float maxValueForRange() const override; + virtual float minValueForRange() const override; + virtual AccessibilityOrientation orientation() const override; }; -class AccessibilitySliderThumb : public AccessibilityMockObject { - +class AccessibilitySliderThumb final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySliderThumb> create(); + static Ref<AccessibilitySliderThumb> create(); virtual ~AccessibilitySliderThumb() { } - virtual AccessibilityRole roleValue() const { return SliderThumbRole; } - - virtual LayoutRect elementRect() const; + virtual AccessibilityRole roleValue() const override { return SliderThumbRole; } + virtual LayoutRect elementRect() const override; private: AccessibilitySliderThumb(); - virtual bool computeAccessibilityIsIgnored() const; + virtual bool isSliderThumb() const override { return true; } + virtual bool computeAccessibilityIsIgnored() const override; }; - } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySliderThumb, isSliderThumb()) + #endif // AccessibilitySlider_h diff --git a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp index 46a182805..0c53370c0 100644 --- a/Source/WebCore/accessibility/AccessibilitySpinButton.cpp +++ b/Source/WebCore/accessibility/AccessibilitySpinButton.cpp @@ -27,17 +27,17 @@ #include "AccessibilitySpinButton.h" #include "AXObjectCache.h" -#include "RenderObject.h" +#include "RenderElement.h" namespace WebCore { -PassRefPtr<AccessibilitySpinButton> AccessibilitySpinButton::create() +Ref<AccessibilitySpinButton> AccessibilitySpinButton::create() { - return adoptRef(new AccessibilitySpinButton); + return adoptRef(*new AccessibilitySpinButton); } AccessibilitySpinButton::AccessibilitySpinButton() - : m_spinButtonElement(0) + : m_spinButtonElement(nullptr) { } @@ -82,15 +82,15 @@ void AccessibilitySpinButton::addChildren() { m_haveChildren = true; - AccessibilitySpinButtonPart* incrementor = static_cast<AccessibilitySpinButtonPart*>(axObjectCache()->getOrCreate(SpinButtonPartRole)); - incrementor->setIsIncrementor(true); - incrementor->setParent(this); - m_children.append(incrementor); + auto& incrementor = downcast<AccessibilitySpinButtonPart>(*axObjectCache()->getOrCreate(SpinButtonPartRole)); + incrementor.setIsIncrementor(true); + incrementor.setParent(this); + m_children.append(&incrementor); - AccessibilitySpinButtonPart* decrementor = static_cast<AccessibilitySpinButtonPart*>(axObjectCache()->getOrCreate(SpinButtonPartRole)); - decrementor->setIsIncrementor(false); - decrementor->setParent(this); - m_children.append(decrementor); + auto& decrementor = downcast<AccessibilitySpinButtonPart>(*axObjectCache()->getOrCreate(SpinButtonPartRole)); + decrementor.setIsIncrementor(false); + decrementor.setParent(this); + m_children.append(&decrementor); } void AccessibilitySpinButton::step(int amount) @@ -109,9 +109,9 @@ AccessibilitySpinButtonPart::AccessibilitySpinButtonPart() { } -PassRefPtr<AccessibilitySpinButtonPart> AccessibilitySpinButtonPart::create() +Ref<AccessibilitySpinButtonPart> AccessibilitySpinButtonPart::create() { - return adoptRef(new AccessibilitySpinButtonPart); + return adoptRef(*new AccessibilitySpinButtonPart); } LayoutRect AccessibilitySpinButtonPart::elementRect() const @@ -130,16 +130,16 @@ LayoutRect AccessibilitySpinButtonPart::elementRect() const return parentRect; } -bool AccessibilitySpinButtonPart::press() const +bool AccessibilitySpinButtonPart::press() { - if (!m_parent || !m_parent->isSpinButton()) + if (!is<AccessibilitySpinButton>(m_parent)) return false; - AccessibilitySpinButton* spinButton = toAccessibilitySpinButton(parentObject()); + auto& spinButton = downcast<AccessibilitySpinButton>(*m_parent); if (m_isIncrementor) - spinButton->step(1); + spinButton.step(1); else - spinButton->step(-1); + spinButton.step(-1); return true; } diff --git a/Source/WebCore/accessibility/AccessibilitySpinButton.h b/Source/WebCore/accessibility/AccessibilitySpinButton.h index d3d6e6ad7..e0cb6c39a 100644 --- a/Source/WebCore/accessibility/AccessibilitySpinButton.h +++ b/Source/WebCore/accessibility/AccessibilitySpinButton.h @@ -32,9 +32,9 @@ namespace WebCore { -class AccessibilitySpinButton : public AccessibilityMockObject { +class AccessibilitySpinButton final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySpinButton> create(); + static Ref<AccessibilitySpinButton> create(); virtual ~AccessibilitySpinButton(); void setSpinButtonElement(SpinButtonElement* spinButton) { m_spinButtonElement = spinButton; } @@ -47,18 +47,18 @@ public: private: AccessibilitySpinButton(); - virtual AccessibilityRole roleValue() const { return SpinButtonRole; } - virtual bool isSpinButton() const { return true; } - virtual bool isNativeSpinButton() const { return true; } - virtual void addChildren(); - virtual LayoutRect elementRect() const; + virtual AccessibilityRole roleValue() const override { return SpinButtonRole; } + virtual bool isSpinButton() const override { return true; } + virtual bool isNativeSpinButton() const override { return true; } + virtual void addChildren() override; + virtual LayoutRect elementRect() const override; SpinButtonElement* m_spinButtonElement; }; -class AccessibilitySpinButtonPart : public AccessibilityMockObject { +class AccessibilitySpinButtonPart final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilitySpinButtonPart> create(); + static Ref<AccessibilitySpinButtonPart> create(); virtual ~AccessibilitySpinButtonPart() { } bool isIncrementor() const { return m_isIncrementor; } @@ -66,26 +66,18 @@ public: private: AccessibilitySpinButtonPart(); - bool m_isIncrementor : 1; - virtual bool press() const; - virtual AccessibilityRole roleValue() const { return ButtonRole; } - virtual bool isSpinButtonPart() const { return true; } - virtual LayoutRect elementRect() const; + virtual bool press() override; + virtual AccessibilityRole roleValue() const override { return ButtonRole; } + virtual bool isSpinButtonPart() const override { return true; } + virtual LayoutRect elementRect() const override; + + unsigned m_isIncrementor : 1; }; -inline AccessibilitySpinButton* toAccessibilitySpinButton(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isNativeSpinButton()); - return static_cast<AccessibilitySpinButton*>(object); -} - -inline AccessibilitySpinButtonPart* toAccessibilitySpinButtonPart(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isSpinButtonPart()); - return static_cast<AccessibilitySpinButtonPart*>(object); -} - -} // namespace WebCore +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySpinButton, isNativeSpinButton()) +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilitySpinButtonPart, isSpinButtonPart()) #endif // AccessibilitySpinButton_h diff --git a/Source/WebCore/accessibility/AccessibilityTable.cpp b/Source/WebCore/accessibility/AccessibilityTable.cpp index 9ac955d06..e5bb16978 100644 --- a/Source/WebCore/accessibility/AccessibilityTable.cpp +++ b/Source/WebCore/accessibility/AccessibilityTable.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -34,6 +34,7 @@ #include "AccessibilityTableColumn.h" #include "AccessibilityTableHeaderContainer.h" #include "AccessibilityTableRow.h" +#include "ElementIterator.h" #include "HTMLNames.h" #include "HTMLTableCaptionElement.h" #include "HTMLTableCellElement.h" @@ -43,14 +44,16 @@ #include "RenderTableCell.h" #include "RenderTableSection.h" +#include <wtf/Deque.h> + namespace WebCore { using namespace HTMLNames; AccessibilityTable::AccessibilityTable(RenderObject* renderer) : AccessibilityRenderObject(renderer) - , m_headerContainer(0) - , m_isAccessibilityTable(true) + , m_headerContainer(nullptr) + , m_isExposableThroughAccessibility(true) { } @@ -61,12 +64,12 @@ AccessibilityTable::~AccessibilityTable() void AccessibilityTable::init() { AccessibilityRenderObject::init(); - m_isAccessibilityTable = isTableExposableThroughAccessibility(); + m_isExposableThroughAccessibility = computeIsTableExposableThroughAccessibility(); } -PassRefPtr<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) +Ref<AccessibilityTable> AccessibilityTable::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTable(renderer)); + return adoptRef(*new AccessibilityTable(renderer)); } bool AccessibilityTable::hasARIARole() const @@ -81,14 +84,34 @@ bool AccessibilityTable::hasARIARole() const return false; } -bool AccessibilityTable::isAccessibilityTable() const +bool AccessibilityTable::isExposableThroughAccessibility() const { if (!m_renderer) return false; - return m_isAccessibilityTable; + return m_isExposableThroughAccessibility; } +HTMLTableElement* AccessibilityTable::tableElement() const +{ + if (!is<RenderTable>(*m_renderer)) + return nullptr; + + RenderTable& table = downcast<RenderTable>(*m_renderer); + if (is<HTMLTableElement>(table.element())) + return downcast<HTMLTableElement>(table.element()); + + table.forceSectionsRecalc(); + + // If the table has a display:table-row-group, then the RenderTable does not have a pointer to it's HTMLTableElement. + // We can instead find it by asking the firstSection for its parent. + RenderTableSection* firstBody = table.firstBody(); + if (!firstBody || !firstBody->element()) + return nullptr; + + return ancestorsOfType<HTMLTableElement>(*(firstBody->element())).first(); +} + bool AccessibilityTable::isDataTable() const { if (!m_renderer) @@ -101,38 +124,41 @@ bool AccessibilityTable::isDataTable() const // When a section of the document is contentEditable, all tables should be // treated as data tables, otherwise users may not be able to work with rich // text editors that allow creating and editing tables. - if (node() && node()->rendererIsEditable()) + if (node() && node()->hasEditableStyle()) return true; + if (!is<RenderTable>(*m_renderer)) + return false; + // This employs a heuristic to determine if this table should appear. // Only "data" tables should be exposed as tables. // Unfortunately, there is no good way to determine the difference // between a "layout" table and a "data" table. - - RenderTable* table = toRenderTable(m_renderer); - Node* tableNode = table->node(); - if (!tableNode || !isHTMLTableElement(tableNode)) - return false; - - // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table - HTMLTableElement* tableElement = toHTMLTableElement(tableNode); - if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) - return true; - - // if someone used "rules" attribute than the table should appear - if (!tableElement->rules().isEmpty()) - return true; - - // if there's a colgroup or col element, it's probably a data table. - for (Node* child = tableElement->firstChild(); child; child = child->nextSibling()) { - if (child->hasTagName(colTag) || child->hasTagName(colgroupTag)) + if (HTMLTableElement* tableElement = this->tableElement()) { + // If there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table. + if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) + return true; + + // If someone used "rules" attribute than the table should appear. + if (!tableElement->rules().isEmpty()) return true; + + // If there's a colgroup or col element, it's probably a data table. + for (const auto& child : childrenOfType<HTMLElement>(*tableElement)) { + if (child.hasTagName(colTag) || child.hasTagName(colgroupTag)) + return true; + } } + // The following checks should only apply if this is a real <table> element. + if (!hasTagName(tableTag)) + return false; + + RenderTable& table = downcast<RenderTable>(*m_renderer); // go through the cell's and check for tell-tale signs of "data" table status // cells have borders, or use attributes like headers, abbr, scope or axis - table->recalcSectionsIfNeeded(); - RenderTableSection* firstBody = table->firstBody(); + table.recalcSectionsIfNeeded(); + RenderTableSection* firstBody = table.firstBody(); if (!firstBody) return false; @@ -148,10 +174,8 @@ bool AccessibilityTable::isDataTable() const return true; // Store the background color of the table to check against cell's background colors. - RenderStyle* tableStyle = table->style(); - if (!tableStyle) - return false; - Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor); + const RenderStyle& tableStyle = table.style(); + Color tableBGColor = tableStyle.visitedDependentColor(CSSPropertyBackgroundColor); // check enough of the cells to find if the table matches our criteria // Criteria: @@ -177,61 +201,60 @@ bool AccessibilityTable::isDataTable() const RenderTableCell* cell = firstBody->primaryCellAt(row, col); if (!cell) continue; - Node* cellNode = cell->node(); - if (!cellNode) + + Element* cellElement = cell->element(); + if (!cellElement) continue; if (cell->width() < 1 || cell->height() < 1) continue; - validCellCount++; - - HTMLTableCellElement* cellElement = static_cast<HTMLTableCellElement*>(cellNode); + ++validCellCount; bool isTHCell = cellElement->hasTagName(thTag); // If the first row is comprised of all <th> tags, assume it is a data table. if (!row && isTHCell) - headersInFirstRowCount++; + ++headersInFirstRowCount; // If the first column is comprised of all <th> tags, assume it is a data table. if (!col && isTHCell) - headersInFirstColumnCount++; - - // in this case, the developer explicitly assigned a "data" table attribute - if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() - || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) - return true; + ++headersInFirstColumnCount; - RenderStyle* renderStyle = cell->style(); - if (!renderStyle) - continue; + // In this case, the developer explicitly assigned a "data" table attribute. + if (is<HTMLTableCellElement>(*cellElement)) { + HTMLTableCellElement& tableCellElement = downcast<HTMLTableCellElement>(*cellElement); + if (!tableCellElement.headers().isEmpty() || !tableCellElement.abbr().isEmpty() + || !tableCellElement.axis().isEmpty() || !tableCellElement.scope().isEmpty()) + return true; + } + const RenderStyle& renderStyle = cell->style(); // If the empty-cells style is set, we'll call it a data table. - if (renderStyle->emptyCells() == HIDE) + if (renderStyle.emptyCells() == HIDE) return true; // If a cell has matching bordered sides, call it a (fully) bordered cell. if ((cell->borderTop() > 0 && cell->borderBottom() > 0) || (cell->borderLeft() > 0 && cell->borderRight() > 0)) - borderedCellCount++; + ++borderedCellCount; // Also keep track of each individual border, so we can catch tables where most // cells have a bottom border, for example. if (cell->borderTop() > 0) - cellsWithTopBorder++; + ++cellsWithTopBorder; if (cell->borderBottom() > 0) - cellsWithBottomBorder++; + ++cellsWithBottomBorder; if (cell->borderLeft() > 0) - cellsWithLeftBorder++; + ++cellsWithLeftBorder; if (cell->borderRight() > 0) - cellsWithRightBorder++; + ++cellsWithRightBorder; // If the cell has a different color from the table and there is cell spacing, // then it is probably a data table cell (spacing and colors take the place of borders). - Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor); - if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 + Color cellColor = renderStyle.visitedDependentColor(CSSPropertyBackgroundColor); + if (table.hBorderSpacing() > 0 && table.vBorderSpacing() > 0 && tableBGColor != cellColor && cellColor.alpha() != 1) - backgroundDifferenceCellCount++; + ++backgroundDifferenceCellCount; // If we've found 10 "good" cells, we don't need to keep searching. if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) @@ -239,15 +262,13 @@ bool AccessibilityTable::isDataTable() const // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows. if (row < 5 && row == alternatingRowColorCount) { - RenderObject* renderRow = cell->parent(); - if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow()) + RenderElement* renderRow = cell->parent(); + if (!is<RenderTableRow>(renderRow)) continue; - RenderStyle* rowRenderStyle = renderRow->style(); - if (!rowRenderStyle) - continue; - Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor); + const RenderStyle& rowRenderStyle = renderRow->style(); + Color rowColor = rowRenderStyle.visitedDependentColor(CSSPropertyBackgroundColor); alternatingRowColors[alternatingRowColorCount] = rowColor; - alternatingRowColorCount++; + ++alternatingRowColorCount; } } @@ -292,7 +313,7 @@ bool AccessibilityTable::isDataTable() const return false; } -bool AccessibilityTable::isTableExposableThroughAccessibility() const +bool AccessibilityTable::computeIsTableExposableThroughAccessibility() const { // The following is a heuristic used to determine if a // <table> should be exposed as an AXTable. The goal @@ -307,12 +328,6 @@ bool AccessibilityTable::isTableExposableThroughAccessibility() const if (hasARIARole()) return false; - // Gtk+ ATs expect all tables to be exposed as tables. -#if PLATFORM(GTK) - Node* tableNode = toRenderTable(m_renderer)->node(); - return tableNode && isHTMLTableElement(tableNode); -#endif - return isDataTable(); } @@ -324,13 +339,13 @@ void AccessibilityTable::clearChildren() if (m_headerContainer) { m_headerContainer->detachFromParent(); - m_headerContainer = 0; + m_headerContainer = nullptr; } } void AccessibilityTable::addChildren() { - if (!isAccessibilityTable()) { + if (!isExposableThroughAccessibility()) { AccessibilityRenderObject::addChildren(); return; } @@ -338,68 +353,120 @@ void AccessibilityTable::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_renderer || !m_renderer->isTable()) + if (!is<RenderTable>(m_renderer)) return; - RenderTable* table = toRenderTable(m_renderer); - AXObjectCache* axCache = m_renderer->document()->axObjectCache(); - + RenderTable& table = downcast<RenderTable>(*m_renderer); // Go through all the available sections to pull out the rows and add them as children. - table->recalcSectionsIfNeeded(); - RenderTableSection* tableSection = table->topSection(); - if (!tableSection) - return; + table.recalcSectionsIfNeeded(); - unsigned maxColumnCount = 0; - while (tableSection) { - - HashSet<AccessibilityObject*> appendedRows; - unsigned numRows = tableSection->numRows(); - for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { - - RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex); - if (!renderRow) - continue; - - AccessibilityObject* rowObject = axCache->getOrCreate(renderRow); - if (!rowObject->isTableRow()) - continue; - - AccessibilityTableRow* row = static_cast<AccessibilityTableRow*>(rowObject); - // We need to check every cell for a new row, because cell spans - // can cause us to miss rows if we just check the first column. - if (appendedRows.contains(row)) - continue; - - row->setRowIndex(static_cast<int>(m_rows.size())); - m_rows.append(row); - if (!row->accessibilityIsIgnored()) - m_children.append(row); -#if PLATFORM(GTK) - else - m_children.appendVector(row->children()); -#endif - appendedRows.add(row); + if (HTMLTableElement* tableElement = this->tableElement()) { + if (HTMLTableCaptionElement* caption = tableElement->caption()) { + AccessibilityObject* axCaption = axObjectCache()->getOrCreate(caption); + if (axCaption && !axCaption->accessibilityIsIgnored()) + m_children.append(axCaption); } + } + + unsigned maxColumnCount = 0; + RenderTableSection* footer = table.footer(); - maxColumnCount = max(tableSection->numColumns(), maxColumnCount); - tableSection = table->sectionBelow(tableSection, SkipEmptySections); + for (RenderTableSection* tableSection = table.topSection(); tableSection; tableSection = table.sectionBelow(tableSection, SkipEmptySections)) { + if (tableSection == footer) + continue; + addChildrenFromSection(tableSection, maxColumnCount); } + // Process the footer last, in case it was ordered earlier in the DOM. + if (footer) + addChildrenFromSection(footer, maxColumnCount); + + AXObjectCache* axCache = m_renderer->document().axObjectCache(); // make the columns based on the number of columns in the first body unsigned length = maxColumnCount; for (unsigned i = 0; i < length; ++i) { - AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole)); - column->setColumnIndex((int)i); - column->setParent(this); - m_columns.append(column); - if (!column->accessibilityIsIgnored()) - m_children.append(column); + auto& column = downcast<AccessibilityTableColumn>(*axCache->getOrCreate(ColumnRole)); + column.setColumnIndex((int)i); + column.setParent(this); + m_columns.append(&column); + if (!column.accessibilityIsIgnored()) + m_children.append(&column); } AccessibilityObject* headerContainerObject = headerContainer(); if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored()) m_children.append(headerContainerObject); + + // Sometimes the cell gets the wrong role initially because it is created before the parent + // determines whether it is an accessibility table. Iterate all the cells and allow them to + // update their roles now that the table knows its status. + // see bug: https://bugs.webkit.org/show_bug.cgi?id=147001 + for (const auto& row : m_rows) { + for (const auto& cell : row->children()) + cell->updateAccessibilityRole(); + } + +} + +void AccessibilityTable::addTableCellChild(AccessibilityObject* rowObject, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount) +{ + if (!rowObject || !is<AccessibilityTableRow>(*rowObject)) + return; + + auto& row = downcast<AccessibilityTableRow>(*rowObject); + // We need to check every cell for a new row, because cell spans + // can cause us to miss rows if we just check the first column. + if (appendedRows.contains(&row)) + return; + + row.setRowIndex(static_cast<int>(m_rows.size())); + m_rows.append(&row); + if (!row.accessibilityIsIgnored()) + m_children.append(&row); + appendedRows.add(&row); + + // store the maximum number of columns + unsigned rowCellCount = row.children().size(); + if (rowCellCount > columnCount) + columnCount = rowCellCount; +} + +void AccessibilityTable::addChildrenFromSection(RenderTableSection* tableSection, unsigned& maxColumnCount) +{ + ASSERT(tableSection); + if (!tableSection) + return; + + AXObjectCache* axCache = m_renderer->document().axObjectCache(); + HashSet<AccessibilityObject*> appendedRows; + unsigned numRows = tableSection->numRows(); + for (unsigned rowIndex = 0; rowIndex < numRows; ++rowIndex) { + + RenderTableRow* renderRow = tableSection->rowRendererAt(rowIndex); + if (!renderRow) + continue; + + AccessibilityObject& rowObject = *axCache->getOrCreate(renderRow); + + // If the row is anonymous, we should dive deeper into the descendants to try to find a valid row. + if (renderRow->isAnonymous()) { + Deque<AccessibilityObject*> queue; + queue.append(&rowObject); + + while (!queue.isEmpty()) { + AccessibilityObject* obj = queue.takeFirst(); + if (obj->node() && is<AccessibilityTableRow>(*obj)) { + addTableCellChild(obj, appendedRows, maxColumnCount); + continue; + } + for (auto* child = obj->firstChild(); child; child = child->nextSibling()) + queue.append(child); + } + } else + addTableCellChild(&rowObject, appendedRows, maxColumnCount); + } + + maxColumnCount = std::max(tableSection->numColumns(), maxColumnCount); } AccessibilityObject* AccessibilityTable::headerContainer() @@ -407,27 +474,43 @@ AccessibilityObject* AccessibilityTable::headerContainer() if (m_headerContainer) return m_headerContainer.get(); - AccessibilityMockObject* tableHeader = toAccessibilityMockObject(axObjectCache()->getOrCreate(TableHeaderContainerRole)); - tableHeader->setParent(this); + auto& tableHeader = downcast<AccessibilityMockObject>(*axObjectCache()->getOrCreate(TableHeaderContainerRole)); + tableHeader.setParent(this); - m_headerContainer = tableHeader; + m_headerContainer = &tableHeader; return m_headerContainer.get(); } -AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns() +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::columns() { updateChildrenIfNecessary(); return m_columns; } -AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows() +const AccessibilityObject::AccessibilityChildrenVector& AccessibilityTable::rows() { updateChildrenIfNecessary(); return m_rows; } + +void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers) +{ + if (!m_renderer) + return; + + updateChildrenIfNecessary(); + // Sometimes m_columns can be reset during the iteration, we cache it here to be safe. + AccessibilityChildrenVector columnsCopy = m_columns; + + for (const auto& column : columnsCopy) { + if (AccessibilityObject* header = downcast<AccessibilityTableColumn>(*column).headerObject()) + headers.append(header); + } +} + void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers) { if (!m_renderer) @@ -435,31 +518,28 @@ void AccessibilityTable::rowHeaders(AccessibilityChildrenVector& headers) updateChildrenIfNecessary(); - unsigned rowCount = m_rows.size(); - for (unsigned k = 0; k < rowCount; ++k) { - AccessibilityObject* header = static_cast<AccessibilityTableRow*>(m_rows[k].get())->headerObject(); - if (!header) - continue; - headers.append(header); + // Sometimes m_rows can be reset during the iteration, we cache it here to be safe. + AccessibilityChildrenVector rowsCopy = m_rows; + + for (const auto& row : rowsCopy) { + if (AccessibilityObject* header = downcast<AccessibilityTableRow>(*row).headerObject()) + headers.append(header); } } -void AccessibilityTable::columnHeaders(AccessibilityChildrenVector& headers) +void AccessibilityTable::visibleRows(AccessibilityChildrenVector& rows) { if (!m_renderer) return; updateChildrenIfNecessary(); - unsigned colCount = m_columns.size(); - for (unsigned k = 0; k < colCount; ++k) { - AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_columns[k].get())->headerObject(); - if (!header) - continue; - headers.append(header); + for (const auto& row : m_rows) { + if (row && !row->isOffScreen()) + rows.append(row); } } - + void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& cells) { if (!m_renderer) @@ -467,8 +547,8 @@ void AccessibilityTable::cells(AccessibilityObject::AccessibilityChildrenVector& updateChildrenIfNecessary(); - for (size_t row = 0; row < m_rows.size(); ++row) - cells.appendVector(m_rows[row]->children()); + for (const auto& row : m_rows) + cells.appendVector(row->children()); } unsigned AccessibilityTable::columnCount() @@ -489,7 +569,7 @@ int AccessibilityTable::tableLevel() const { int level = 0; for (AccessibilityObject* obj = static_cast<AccessibilityObject*>(const_cast<AccessibilityTable*>(this)); obj; obj = obj->parentObject()) { - if (obj->isAccessibilityTable()) + if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility()) ++level; } @@ -500,40 +580,44 @@ AccessibilityTableCell* AccessibilityTable::cellForColumnAndRow(unsigned column, { updateChildrenIfNecessary(); if (column >= columnCount() || row >= rowCount()) - return 0; + return nullptr; // Iterate backwards through the rows in case the desired cell has a rowspan and exists in a previous row. for (unsigned rowIndexCounter = row + 1; rowIndexCounter > 0; --rowIndexCounter) { unsigned rowIndex = rowIndexCounter - 1; - AccessibilityChildrenVector children = m_rows[rowIndex]->children(); + const auto& children = m_rows[rowIndex]->children(); // Since some cells may have colspans, we have to check the actual range of each // cell to determine which is the right one. for (unsigned colIndexCounter = std::min(static_cast<unsigned>(children.size()), column + 1); colIndexCounter > 0; --colIndexCounter) { unsigned colIndex = colIndexCounter - 1; AccessibilityObject* child = children[colIndex].get(); - ASSERT(child->isTableCell()); - if (!child->isTableCell()) + ASSERT(is<AccessibilityTableCell>(*child)); + if (!is<AccessibilityTableCell>(*child)) continue; - pair<unsigned, unsigned> columnRange; - pair<unsigned, unsigned> rowRange; - AccessibilityTableCell* tableCellChild = static_cast<AccessibilityTableCell*>(child); - tableCellChild->columnIndexRange(columnRange); - tableCellChild->rowIndexRange(rowRange); + std::pair<unsigned, unsigned> columnRange; + std::pair<unsigned, unsigned> rowRange; + auto& tableCellChild = downcast<AccessibilityTableCell>(*child); + tableCellChild.columnIndexRange(columnRange); + tableCellChild.rowIndexRange(rowRange); if ((column >= columnRange.first && column < (columnRange.first + columnRange.second)) && (row >= rowRange.first && row < (rowRange.first + rowRange.second))) - return tableCellChild; + return &tableCellChild; } } - return 0; + return nullptr; } AccessibilityRole AccessibilityTable::roleValue() const { - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::roleValue(); + + AccessibilityRole ariaRole = ariaRoleAttribute(); + if (ariaRole == GridRole || ariaRole == TreeGridRole) + return GridRole; return TableRole; } @@ -546,15 +630,22 @@ bool AccessibilityTable::computeAccessibilityIsIgnored() const if (decision == IgnoreObject) return true; - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::computeAccessibilityIsIgnored(); return false; } - + +void AccessibilityTable::titleElementText(Vector<AccessibilityText>& textOrder) const +{ + String title = this->title(); + if (!title.isEmpty()) + textOrder.append(AccessibilityText(title, LabelByElementText)); +} + String AccessibilityTable::title() const { - if (!isAccessibilityTable()) + if (!isExposableThroughAccessibility()) return AccessibilityRenderObject::title(); String title; @@ -563,9 +654,8 @@ String AccessibilityTable::title() const // see if there is a caption Node* tableElement = m_renderer->node(); - if (tableElement && isHTMLTableElement(tableElement)) { - HTMLTableCaptionElement* caption = toHTMLTableElement(tableElement)->caption(); - if (caption) + if (is<HTMLTableElement>(tableElement)) { + if (HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*tableElement).caption()) title = caption->innerText(); } @@ -576,4 +666,30 @@ String AccessibilityTable::title() const return title; } +int AccessibilityTable::ariaColumnCount() const +{ + const AtomicString& colCountValue = getAttribute(aria_colcountAttr); + + int colCountInt = colCountValue.toInt(); + // If only a portion of the columns is present in the DOM at a given moment, this attribute is needed to + // provide an explicit indication of the number of columns in the full table. + if (colCountInt > (int)m_columns.size()) + return colCountInt; + + return -1; +} + +int AccessibilityTable::ariaRowCount() const +{ + const AtomicString& rowCountValue = getAttribute(aria_rowcountAttr); + + int rowCountInt = rowCountValue.toInt(); + // If only a portion of the rows is present in the DOM at a given moment, this attribute is needed to + // provide an explicit indication of the number of rows in the full table. + if (rowCountInt > (int)m_rows.size()) + return rowCountInt; + + return -1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTable.h b/Source/WebCore/accessibility/AccessibilityTable.h index f1945e8ee..255729dc5 100644 --- a/Source/WebCore/accessibility/AccessibilityTable.h +++ b/Source/WebCore/accessibility/AccessibilityTable.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -35,33 +35,31 @@ namespace WebCore { class AccessibilityTableCell; +class HTMLTableElement; class RenderTableSection; class AccessibilityTable : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTable(RenderObject*); public: - static PassRefPtr<AccessibilityTable> create(RenderObject*); + static Ref<AccessibilityTable> create(RenderObject*); virtual ~AccessibilityTable(); - virtual void init(); + virtual void init() override final; - virtual AccessibilityRole roleValue() const; + virtual AccessibilityRole roleValue() const override final; virtual bool isAriaTable() const { return false; } - virtual void addChildren(); - virtual void clearChildren(); + virtual void addChildren() override; + virtual void clearChildren() override final; - AccessibilityChildrenVector& columns(); - AccessibilityChildrenVector& rows(); + const AccessibilityChildrenVector& columns(); + const AccessibilityChildrenVector& rows(); virtual bool supportsSelectedRows() { return false; } unsigned columnCount(); unsigned rowCount(); - virtual int tableLevel() const; + virtual int tableLevel() const override final; - virtual String title() const; + virtual String title() const override final; // all the cells in the table void cells(AccessibilityChildrenVector&); @@ -69,37 +67,45 @@ public: void columnHeaders(AccessibilityChildrenVector&); void rowHeaders(AccessibilityChildrenVector&); - + void visibleRows(AccessibilityChildrenVector&); + // an object that contains, as children, all the objects that act as headers AccessibilityObject* headerContainer(); + // isExposableThroughAccessibility() is whether it is exposed as an AccessibilityTable to the platform. + bool isExposableThroughAccessibility() const; + + int ariaColumnCount() const; + int ariaRowCount() const; + protected: + explicit AccessibilityTable(RenderObject*); + AccessibilityChildrenVector m_rows; AccessibilityChildrenVector m_columns; RefPtr<AccessibilityObject> m_headerContainer; - bool m_isAccessibilityTable; + bool m_isExposableThroughAccessibility; bool hasARIARole() const; // isTable is whether it's an AccessibilityTable object. - virtual bool isTable() const OVERRIDE { return true; } - // isAccessibilityTable is whether it is exposed as an AccessibilityTable to the platform. - virtual bool isAccessibilityTable() const OVERRIDE; + virtual bool isTable() const override final { return true; } // isDataTable is whether it is exposed as an AccessibilityTable because the heuristic // think this "looks" like a data-based table (instead of a table used for layout). - virtual bool isDataTable() const OVERRIDE; + virtual bool isDataTable() const override final; + virtual bool computeAccessibilityIsIgnored() const override final; - virtual bool isTableExposableThroughAccessibility() const; - virtual bool computeAccessibilityIsIgnored() const OVERRIDE; +private: + virtual bool computeIsTableExposableThroughAccessibility() const; + virtual void titleElementText(Vector<AccessibilityText>&) const override final; + HTMLTableElement* tableElement() const; + void addChildrenFromSection(RenderTableSection*, unsigned& maxColumnCount); + void addTableCellChild(AccessibilityObject*, HashSet<AccessibilityObject*>& appendedRows, unsigned& columnCount); }; - -inline AccessibilityTable* toAccessibilityTable(AccessibilityObject* object) -{ - ASSERT_WITH_SECURITY_IMPLICATION(!object || object->isTable()); - return static_cast<AccessibilityTable*>(object); -} - + } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTable, isTable()) + #endif // AccessibilityTable_h diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.cpp b/Source/WebCore/accessibility/AccessibilityTableCell.cpp index 32227d21f..287997b08 100644 --- a/Source/WebCore/accessibility/AccessibilityTableCell.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableCell.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -30,18 +30,21 @@ #include "AccessibilityTableCell.h" #include "AXObjectCache.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableRow.h" +#include "ElementIterator.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "RenderObject.h" #include "RenderTableCell.h" -using namespace std; - namespace WebCore { using namespace HTMLNames; AccessibilityTableCell::AccessibilityTableCell(RenderObject* renderer) : AccessibilityRenderObject(renderer) + , m_ariaColIndexFromRow(-1) { } @@ -49,9 +52,9 @@ AccessibilityTableCell::~AccessibilityTableCell() { } -PassRefPtr<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer) +Ref<AccessibilityTableCell> AccessibilityTableCell::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTableCell(renderer)); + return adoptRef(*new AccessibilityTableCell(renderer)); } bool AccessibilityTableCell::computeAccessibilityIsIgnored() const @@ -62,85 +65,273 @@ bool AccessibilityTableCell::computeAccessibilityIsIgnored() const if (decision == IgnoreObject) return true; + // Ignore anonymous table cells as long as they're not in a table (ie. when display:table is used). + RenderObject* renderTable = is<RenderTableCell>(m_renderer) ? downcast<RenderTableCell>(*m_renderer).table() : nullptr; + bool inTable = renderTable && renderTable->node() && (renderTable->node()->hasTagName(tableTag) || nodeHasRole(renderTable->node(), "grid")); + if (!node() && !inTable) + return true; + if (!isTableCell()) return AccessibilityRenderObject::computeAccessibilityIsIgnored(); return false; } - -AccessibilityObject* AccessibilityTableCell::parentTable() const + +AccessibilityTable* AccessibilityTableCell::parentTable() const { - if (!m_renderer || !m_renderer->isTableCell()) - return 0; + if (!is<RenderTableCell>(m_renderer)) + return nullptr; // If the document no longer exists, we might not have an axObjectCache. if (!axObjectCache()) - return 0; + return nullptr; // Do not use getOrCreate. parentTable() can be called while the render tree is being modified // by javascript, and creating a table element may try to access the render tree while in a bad state. // By using only get() implies that the AXTable must be created before AXTableCells. This should // always be the case when AT clients access a table. - // https://bugs.webkit.org/show_bug.cgi?id=42652 - return axObjectCache()->get(toRenderTableCell(m_renderer)->table()); + // https://bugs.webkit.org/show_bug.cgi?id=42652 + AccessibilityObject* parentTable = axObjectCache()->get(downcast<RenderTableCell>(*m_renderer).table()); + if (!is<AccessibilityTable>(parentTable)) + return nullptr; + + // The RenderTableCell's table() object might be anonymous sometimes. We should handle it gracefully + // by finding the right table. + if (!parentTable->node()) { + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + // If this is a non-anonymous table object, but not an accessibility table, we should stop because + // we don't want to choose another ancestor table as this cell's table. + if (is<AccessibilityTable>(*parent)) { + auto& parentTable = downcast<AccessibilityTable>(*parent); + if (parentTable.isExposableThroughAccessibility()) + return &parentTable; + if (parentTable.node()) + break; + } + } + return nullptr; + } + + return downcast<AccessibilityTable>(parentTable); } bool AccessibilityTableCell::isTableCell() const { - AccessibilityObject* parent = parentObjectUnignored(); - if (!parent || !parent->isTableRow()) - return false; - - return true; + // If the parent table is an accessibility table, then we are a table cell. + // This used to check if the unignoredParent was a row, but that exploded performance if + // this was in nested tables. This check should be just as good. + AccessibilityObject* parentTable = this->parentTable(); + return is<AccessibilityTable>(parentTable) && downcast<AccessibilityTable>(*parentTable).isExposableThroughAccessibility(); } AccessibilityRole AccessibilityTableCell::determineAccessibilityRole() { - // Always call determineAccessibleRole so that the ARIA role is set. - // Even though this object reports a Cell role, the ARIA role will be used - // to determine if it's a column header. + // AccessibilityRenderObject::determineAccessibleRole provides any ARIA-supplied + // role, falling back on the role to be used if we determine here that the element + // should not be exposed as a cell. Thus if we already know it's a cell, return that. AccessibilityRole defaultRole = AccessibilityRenderObject::determineAccessibilityRole(); + if (defaultRole == ColumnHeaderRole || defaultRole == RowHeaderRole || defaultRole == CellRole || defaultRole == GridCellRole) + return defaultRole; + if (!isTableCell()) return defaultRole; - + if (isColumnHeaderCell()) + return ColumnHeaderRole; + if (isRowHeaderCell()) + return RowHeaderRole; + return CellRole; } -void AccessibilityTableCell::rowIndexRange(pair<unsigned, unsigned>& rowRange) +bool AccessibilityTableCell::isTableHeaderCell() const +{ + return node() && node()->hasTagName(thTag); +} + +bool AccessibilityTableCell::isColumnHeaderCell() const +{ + const AtomicString& scope = getAttribute(scopeAttr); + if (scope == "col" || scope == "colgroup") + return true; + if (scope == "row" || scope == "rowgroup") + return false; + if (!isTableHeaderCell()) + return false; + + // We are in a situation after checking the scope attribute. + // It is an attempt to resolve the type of th element without support in the specification. + // Checking tableTag and tbodyTag allows to check the case of direct row placement in the table and lets stop the loop at the table level. + for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(theadTag)) + return true; + if (parentNode->hasTagName(tfootTag)) + return false; + if (parentNode->hasTagName(tableTag) || parentNode->hasTagName(tbodyTag)) { + std::pair<unsigned, unsigned> rowRange; + rowIndexRange(rowRange); + if (!rowRange.first) + return true; + return false; + } + } + return false; +} + +bool AccessibilityTableCell::isRowHeaderCell() const +{ + const AtomicString& scope = getAttribute(scopeAttr); + if (scope == "row" || scope == "rowgroup") + return true; + if (scope == "col" || scope == "colgroup") + return false; + if (!isTableHeaderCell()) + return false; + + // We are in a situation after checking the scope attribute. + // It is an attempt to resolve the type of th element without support in the specification. + // Checking tableTag allows to check the case of direct row placement in the table and lets stop the loop at the table level. + for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(tfootTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tableTag)) { + std::pair<unsigned, unsigned> colRange; + columnIndexRange(colRange); + if (!colRange.first) + return true; + return false; + } + if (parentNode->hasTagName(theadTag)) + return false; + } + return false; +} + +bool AccessibilityTableCell::isTableCellInSameRowGroup(AccessibilityTableCell* otherTableCell) +{ + Node* parentNode = node(); + for ( ; parentNode; parentNode = parentNode->parentNode()) { + if (parentNode->hasTagName(theadTag) || parentNode->hasTagName(tbodyTag) || parentNode->hasTagName(tfootTag)) + break; + } + + Node* otherParentNode = otherTableCell->node(); + for ( ; otherParentNode; otherParentNode = otherParentNode->parentNode()) { + if (otherParentNode->hasTagName(theadTag) || otherParentNode->hasTagName(tbodyTag) || otherParentNode->hasTagName(tfootTag)) + break; + } + + return otherParentNode == parentNode; +} + + +bool AccessibilityTableCell::isTableCellInSameColGroup(AccessibilityTableCell* tableCell) +{ + std::pair<unsigned, unsigned> colRange; + columnIndexRange(colRange); + + std::pair<unsigned, unsigned> otherColRange; + tableCell->columnIndexRange(otherColRange); + + if (colRange.first <= (otherColRange.first + otherColRange.second)) + return true; + return false; +} + +String AccessibilityTableCell::expandedTextValue() const { - if (!m_renderer || !m_renderer->isTableCell()) + return getAttribute(abbrAttr); +} + +bool AccessibilityTableCell::supportsExpandedTextValue() const +{ + return isTableHeaderCell() && hasAttribute(abbrAttr); +} + +void AccessibilityTableCell::columnHeaders(AccessibilityChildrenVector& headers) +{ + AccessibilityTable* parent = parentTable(); + if (!parent) return; + + // Choose columnHeaders as the place where the "headers" attribute is reported. + ariaElementsFromAttribute(headers, headersAttr); + // If the headers attribute returned valid values, then do not further search for column headers. + if (!headers.isEmpty()) + return; + + std::pair<unsigned, unsigned> rowRange; + rowIndexRange(rowRange); + + std::pair<unsigned, unsigned> colRange; + columnIndexRange(colRange); - RenderTableCell* renderCell = toRenderTableCell(m_renderer); - rowRange.first = renderCell->rowIndex(); - rowRange.second = renderCell->rowSpan(); + for (unsigned row = 0; row < rowRange.first; row++) { + AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(colRange.first, row); + if (!tableCell || tableCell == this || headers.contains(tableCell)) + continue; + + std::pair<unsigned, unsigned> childRowRange; + tableCell->rowIndexRange(childRowRange); + + const AtomicString& scope = tableCell->getAttribute(scopeAttr); + if (scope == "colgroup" && isTableCellInSameColGroup(tableCell)) + headers.append(tableCell); + else if (tableCell->isColumnHeaderCell()) + headers.append(tableCell); + } +} - // since our table might have multiple sections, we have to offset our row appropriately - RenderTableSection* section = renderCell->section(); - RenderTable* table = renderCell->table(); - if (!table || !section) +void AccessibilityTableCell::rowHeaders(AccessibilityChildrenVector& headers) +{ + AccessibilityTable* parent = parentTable(); + if (!parent) return; - RenderTableSection* tableSection = table->topSection(); - unsigned rowOffset = 0; - while (tableSection) { - if (tableSection == section) - break; - rowOffset += tableSection->numRows(); - tableSection = table->sectionBelow(tableSection, SkipEmptySections); + std::pair<unsigned, unsigned> rowRange; + rowIndexRange(rowRange); + + std::pair<unsigned, unsigned> colRange; + columnIndexRange(colRange); + + for (unsigned column = 0; column < colRange.first; column++) { + AccessibilityTableCell* tableCell = parent->cellForColumnAndRow(column, rowRange.first); + if (!tableCell || tableCell == this || headers.contains(tableCell)) + continue; + + const AtomicString& scope = tableCell->getAttribute(scopeAttr); + if (scope == "rowgroup" && isTableCellInSameRowGroup(tableCell)) + headers.append(tableCell); + else if (tableCell->isRowHeaderCell()) + headers.append(tableCell); } +} - rowRange.first += rowOffset; +AccessibilityTableRow* AccessibilityTableCell::parentRow() const +{ + AccessibilityObject* parent = parentObjectUnignored(); + if (!is<AccessibilityTableRow>(*parent)) + return nullptr; + return downcast<AccessibilityTableRow>(parent); +} + +void AccessibilityTableCell::rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const +{ + if (!is<RenderTableCell>(m_renderer)) + return; + + RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer); + rowRange.second = renderCell.rowSpan(); + + if (AccessibilityTableRow* parentRow = this->parentRow()) + rowRange.first = parentRow->rowIndex(); } -void AccessibilityTableCell::columnIndexRange(pair<unsigned, unsigned>& columnRange) +void AccessibilityTableCell::columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const { - if (!m_renderer || !m_renderer->isTableCell()) + if (!is<RenderTableCell>(m_renderer)) return; - RenderTableCell* renderCell = toRenderTableCell(m_renderer); - columnRange.first = renderCell->col(); - columnRange.second = renderCell->colSpan(); + const RenderTableCell& cell = downcast<RenderTableCell>(*m_renderer); + columnRange.first = cell.table()->colToEffCol(cell.col()); + columnRange.second = cell.table()->colToEffCol(cell.col() + cell.colSpan()) - columnRange.first; } AccessibilityObject* AccessibilityTableCell::titleUIElement() const @@ -148,37 +339,90 @@ AccessibilityObject* AccessibilityTableCell::titleUIElement() const // Try to find if the first cell in this row is a <th>. If it is, // then it can act as the title ui element. (This is only in the // case when the table is not appearing as an AXTable.) - if (isTableCell() || !m_renderer || !m_renderer->isTableCell()) - return 0; + if (isTableCell() || !is<RenderTableCell>(m_renderer)) + return nullptr; // Table cells that are th cannot have title ui elements, since by definition // they are title ui elements Node* node = m_renderer->node(); if (node && node->hasTagName(thTag)) - return 0; + return nullptr; - RenderTableCell* renderCell = toRenderTableCell(m_renderer); + RenderTableCell& renderCell = downcast<RenderTableCell>(*m_renderer); // If this cell is in the first column, there is no need to continue. - int col = renderCell->col(); + int col = renderCell.col(); if (!col) - return 0; + return nullptr; - int row = renderCell->rowIndex(); + int row = renderCell.rowIndex(); - RenderTableSection* section = renderCell->section(); + RenderTableSection* section = renderCell.section(); if (!section) - return 0; + return nullptr; RenderTableCell* headerCell = section->primaryCellAt(row, 0); - if (!headerCell || headerCell == renderCell) - return 0; + if (!headerCell || headerCell == &renderCell) + return nullptr; - Node* cellElement = headerCell->node(); - if (!cellElement || !cellElement->hasTagName(thTag)) - return 0; + if (!headerCell->element() || !headerCell->element()->hasTagName(thTag)) + return nullptr; return axObjectCache()->getOrCreate(headerCell); } +int AccessibilityTableCell::ariaColumnIndex() const +{ + const AtomicString& colIndexValue = getAttribute(aria_colindexAttr); + if (colIndexValue.toInt() >= 1) + return colIndexValue.toInt(); + + // "ARIA 1.1: If the set of columns which is present in the DOM is contiguous, and if there are no cells which span more than one row + // or column in that set, then authors may place aria-colindex on each row, setting the value to the index of the first column of the set." + // Here, we let its parent row to set its index beforehand, so we don't have to go through the siblings to calculate the index. + AccessibilityTableRow* parentRow = this->parentRow(); + if (parentRow && m_ariaColIndexFromRow != -1) + return m_ariaColIndexFromRow; + + return -1; +} + +int AccessibilityTableCell::ariaRowIndex() const +{ + // ARIA 1.1: Authors should place aria-rowindex on each row. Authors may also place + // aria-rowindex on all of the children or owned elements of each row. + const AtomicString& rowIndexValue = getAttribute(aria_rowindexAttr); + if (rowIndexValue.toInt() >= 1) + return rowIndexValue.toInt(); + + if (AccessibilityTableRow* parentRow = this->parentRow()) + return parentRow->ariaRowIndex(); + + return -1; +} + +unsigned AccessibilityTableCell::ariaColumnSpan() const +{ + const AtomicString& colSpanValue = getAttribute(aria_colspanAttr); + // ARIA 1.1: Authors must set the value of aria-colspan to an integer greater than or equal to 1. + if (colSpanValue.toInt() >= 1) + return colSpanValue.toInt(); + + return 1; +} + +unsigned AccessibilityTableCell::ariaRowSpan() const +{ + const AtomicString& rowSpanValue = getAttribute(aria_rowspanAttr); + + // ARIA 1.1: Authors must set the value of aria-rowspan to an integer greater than or equal to 0. + // Setting the value to 0 indicates that the cell or gridcell is to span all the remaining rows in the row group. + if (rowSpanValue == "0") + return 0; + if (rowSpanValue.toInt() >= 1) + return rowSpanValue.toInt(); + + return 1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableCell.h b/Source/WebCore/accessibility/AccessibilityTableCell.h index 78f0b906b..85c418252 100644 --- a/Source/WebCore/accessibility/AccessibilityTableCell.h +++ b/Source/WebCore/accessibility/AccessibilityTableCell.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -33,33 +33,57 @@ namespace WebCore { +class AccessibilityTable; +class AccessibilityTableRow; + class AccessibilityTableCell : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTableCell(RenderObject*); public: - static PassRefPtr<AccessibilityTableCell> create(RenderObject*); + static Ref<AccessibilityTableCell> create(RenderObject*); virtual ~AccessibilityTableCell(); - virtual bool isTableCell() const; + virtual bool isTableCell() const override final; + bool isTableHeaderCell() const; + bool isColumnHeaderCell() const; + bool isRowHeaderCell() const; // fills in the start location and row span of cell - virtual void rowIndexRange(pair<unsigned, unsigned>& rowRange); + virtual void rowIndexRange(std::pair<unsigned, unsigned>& rowRange) const; // fills in the start location and column span of cell - virtual void columnIndexRange(pair<unsigned, unsigned>& columnRange); + virtual void columnIndexRange(std::pair<unsigned, unsigned>& columnRange) const; + void columnHeaders(AccessibilityChildrenVector&); + void rowHeaders(AccessibilityChildrenVector&); + + int ariaColumnIndex() const; + int ariaRowIndex() const; + unsigned ariaColumnSpan() const; + unsigned ariaRowSpan() const; + void setARIAColIndexFromRow(int index) { m_ariaColIndexFromRow = index; } + protected: - virtual AccessibilityObject* parentTable() const; + explicit AccessibilityTableCell(RenderObject*); + + AccessibilityTableRow* parentRow() const; + virtual AccessibilityTable* parentTable() const; + virtual AccessibilityRole determineAccessibilityRole() override final; + int m_rowIndex; - virtual AccessibilityRole determineAccessibilityRole(); + int m_ariaColIndexFromRow; private: // If a table cell is not exposed as a table cell, a TH element can serve as its title UI element. - virtual AccessibilityObject* titleUIElement() const; - virtual bool exposesTitleUIElement() const { return true; } - virtual bool computeAccessibilityIsIgnored() const; -}; - + virtual AccessibilityObject* titleUIElement() const override final; + virtual bool exposesTitleUIElement() const override final { return true; } + virtual bool computeAccessibilityIsIgnored() const override final; + virtual String expandedTextValue() const override final; + virtual bool supportsExpandedTextValue() const override final; + + bool isTableCellInSameRowGroup(AccessibilityTableCell*); + bool isTableCellInSameColGroup(AccessibilityTableCell*); +}; + } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableCell, isTableCell()) + #endif // AccessibilityTableCell_h diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.cpp b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp index 4d494822d..3fa78b558 100644 --- a/Source/WebCore/accessibility/AccessibilityTableColumn.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -31,13 +31,12 @@ #include "AXObjectCache.h" #include "AccessibilityTableCell.h" +#include "HTMLElement.h" #include "HTMLNames.h" #include "RenderTable.h" #include "RenderTableCell.h" #include "RenderTableSection.h" -using namespace std; - namespace WebCore { using namespace HTMLNames; @@ -50,9 +49,9 @@ AccessibilityTableColumn::~AccessibilityTableColumn() { } -PassRefPtr<AccessibilityTableColumn> AccessibilityTableColumn::create() +Ref<AccessibilityTableColumn> AccessibilityTableColumn::create() { - return adoptRef(new AccessibilityTableColumn()); + return adoptRef(*new AccessibilityTableColumn()); } void AccessibilityTableColumn::setParent(AccessibilityObject* parent) @@ -64,90 +63,100 @@ void AccessibilityTableColumn::setParent(AccessibilityObject* parent) LayoutRect AccessibilityTableColumn::elementRect() const { - // this will be filled in when addChildren is called - return m_columnRect; + // This used to be cached during the call to addChildren(), but calling elementRect() + // can invalidate elements, so its better to ask for this on demand. + LayoutRect columnRect; + AccessibilityChildrenVector childrenCopy = m_children; + for (const auto& cell : childrenCopy) + columnRect.unite(cell->elementRect()); + + return columnRect; } AccessibilityObject* AccessibilityTableColumn::headerObject() { if (!m_parent) - return 0; + return nullptr; RenderObject* renderer = m_parent->renderer(); if (!renderer) - return 0; - - if (!m_parent->isAccessibilityTable()) - return 0; + return nullptr; + if (!is<AccessibilityTable>(*m_parent)) + return nullptr; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) + return nullptr; - AccessibilityTable* parentTable = toAccessibilityTable(m_parent); - if (parentTable->isAriaTable()) { - AccessibilityChildrenVector rowChildren = children(); - unsigned childrenCount = rowChildren.size(); - for (unsigned i = 0; i < childrenCount; ++i) { - AccessibilityObject* cell = rowChildren[i].get(); + if (parentTable.isAriaTable()) { + for (const auto& cell : children()) { if (cell->ariaRoleAttribute() == ColumnHeaderRole) - return cell; + return cell.get(); } - return 0; + return nullptr; } - if (!renderer->isTable()) - return 0; - - RenderTable* table = toRenderTable(renderer); - - AccessibilityObject* headerObject = 0; + if (!is<RenderTable>(*renderer)) + return nullptr; - // try the <thead> section first. this doesn't require th tags - headerObject = headerObjectForSection(table->header(), false); + RenderTable& table = downcast<RenderTable>(*renderer); - if (headerObject) + // try the <thead> section first. this doesn't require th tags + if (auto* headerObject = headerObjectForSection(table.header(), false)) return headerObject; - // now try for <th> tags in the first body - headerObject = headerObjectForSection(table->firstBody(), true); - - return headerObject; + RenderTableSection* bodySection = table.firstBody(); + while (bodySection && bodySection->isAnonymous()) + bodySection = table.sectionBelow(bodySection, SkipEmptySections); + + // now try for <th> tags in the first body. If the first body is + return headerObjectForSection(bodySection, true); } AccessibilityObject* AccessibilityTableColumn::headerObjectForSection(RenderTableSection* section, bool thTagRequired) { if (!section) - return 0; + return nullptr; unsigned numCols = section->numColumns(); if (m_columnIndex >= numCols) - return 0; + return nullptr; if (!section->numRows()) - return 0; + return nullptr; - RenderTableCell* cell = 0; + RenderTableCell* cell = nullptr; // also account for cells that have a span for (int testCol = m_columnIndex; testCol >= 0; --testCol) { - RenderTableCell* testCell = section->primaryCellAt(0, testCol); - if (!testCell) - continue; - // we've reached a cell that doesn't even overlap our column - // it can't be our header - if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex) + // Run down the rows in case initial rows are invalid (like when a <caption> is used). + unsigned rowCount = section->numRows(); + for (unsigned testRow = 0; testRow < rowCount; testRow++) { + RenderTableCell* testCell = section->primaryCellAt(testRow, testCol); + // No cell at this index, keep checking more rows and columns. + if (!testCell) + continue; + + // If we've reached a cell that doesn't even overlap our column it can't be the header. + if ((testCell->col() + (testCell->colSpan()-1)) < m_columnIndex) + break; + + // If this does not have an element (like a <caption>) then check the next row + if (!testCell->element()) + continue; + + // If th is required, but we found an element that doesn't have a th tag, we can stop looking. + if (thTagRequired && !testCell->element()->hasTagName(thTag)) + break; + + cell = testCell; break; - - Node* node = testCell->node(); - if (!node) - continue; - - if (thTagRequired && !node->hasTagName(thTag)) - continue; - - cell = testCell; + } } if (!cell) - return 0; + return nullptr; return axObjectCache()->getOrCreate(cell); } @@ -157,7 +166,7 @@ bool AccessibilityTableColumn::computeAccessibilityIsIgnored() const if (!m_parent) return true; -#if PLATFORM(GTK) +#if PLATFORM(IOS) || PLATFORM(GTK) || PLATFORM(EFL) return true; #endif @@ -169,14 +178,17 @@ void AccessibilityTableColumn::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_parent || !m_parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(m_parent)) + return; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) return; - AccessibilityTable* parentTable = toAccessibilityTable(m_parent); - int numRows = parentTable->rowCount(); + int numRows = parentTable.rowCount(); - for (int i = 0; i < numRows; i++) { - AccessibilityTableCell* cell = parentTable->cellForColumnAndRow(m_columnIndex, i); + for (int i = 0; i < numRows; ++i) { + AccessibilityTableCell* cell = parentTable.cellForColumnAndRow(m_columnIndex, i); if (!cell) continue; @@ -185,7 +197,6 @@ void AccessibilityTableColumn::addChildren() continue; m_children.append(cell); - m_columnRect.unite(cell->elementRect()); } } diff --git a/Source/WebCore/accessibility/AccessibilityTableColumn.h b/Source/WebCore/accessibility/AccessibilityTableColumn.h index 6e737c121..5b6eb7b1c 100644 --- a/Source/WebCore/accessibility/AccessibilityTableColumn.h +++ b/Source/WebCore/accessibility/AccessibilityTableColumn.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -37,35 +37,35 @@ namespace WebCore { class RenderTableSection; -class AccessibilityTableColumn : public AccessibilityMockObject { - -private: - AccessibilityTableColumn(); +class AccessibilityTableColumn final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityTableColumn> create(); + static Ref<AccessibilityTableColumn> create(); virtual ~AccessibilityTableColumn(); AccessibilityObject* headerObject(); - virtual AccessibilityRole roleValue() const { return ColumnRole; } - virtual bool isTableColumn() const { return true; } + virtual AccessibilityRole roleValue() const override { return ColumnRole; } void setColumnIndex(int columnIndex) { m_columnIndex = columnIndex; } int columnIndex() const { return m_columnIndex; } - virtual void addChildren(); - virtual void setParent(AccessibilityObject*); + virtual void addChildren() override; + virtual void setParent(AccessibilityObject*) override; - virtual LayoutRect elementRect() const; + virtual LayoutRect elementRect() const override; -private: - unsigned m_columnIndex; - LayoutRect m_columnRect; +private: + AccessibilityTableColumn(); AccessibilityObject* headerObjectForSection(RenderTableSection*, bool thTagRequired); - virtual bool computeAccessibilityIsIgnored() const; + virtual bool computeAccessibilityIsIgnored() const override; + virtual bool isTableColumn() const override { return true; } + + unsigned m_columnIndex; }; - + } // namespace WebCore +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableColumn, isTableColumn()) + #endif // AccessibilityTableColumn_h diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp index 3e789def2..e30114829 100644 --- a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -32,8 +32,6 @@ #include "AXObjectCache.h" #include "AccessibilityTable.h" -using namespace std; - namespace WebCore { AccessibilityTableHeaderContainer::AccessibilityTableHeaderContainer() @@ -44,9 +42,9 @@ AccessibilityTableHeaderContainer::~AccessibilityTableHeaderContainer() { } -PassRefPtr<AccessibilityTableHeaderContainer> AccessibilityTableHeaderContainer::create() +Ref<AccessibilityTableHeaderContainer> AccessibilityTableHeaderContainer::create() { - return adoptRef(new AccessibilityTableHeaderContainer()); + return adoptRef(*new AccessibilityTableHeaderContainer()); } LayoutRect AccessibilityTableHeaderContainer::elementRect() const @@ -60,7 +58,7 @@ bool AccessibilityTableHeaderContainer::computeAccessibilityIsIgnored() const if (!m_parent) return true; -#if PLATFORM(GTK) +#if PLATFORM(IOS) || PLATFORM(GTK) || PLATFORM(EFL) return true; #endif @@ -72,14 +70,17 @@ void AccessibilityTableHeaderContainer::addChildren() ASSERT(!m_haveChildren); m_haveChildren = true; - if (!m_parent || !m_parent->isAccessibilityTable()) + if (!is<AccessibilityTable>(m_parent)) + return; + + auto& parentTable = downcast<AccessibilityTable>(*m_parent); + if (!parentTable.isExposableThroughAccessibility()) return; - static_cast<AccessibilityTable*>(m_parent)->columnHeaders(m_children); + parentTable.columnHeaders(m_children); - unsigned length = m_children.size(); - for (unsigned k = 0; k < length; ++k) - m_headerRect.unite(m_children[k]->elementRect()); + for (const auto& child : m_children) + m_headerRect.unite(child->elementRect()); } } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h index 6a9b2c95b..e4ce74068 100644 --- a/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h +++ b/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -35,24 +35,23 @@ namespace WebCore { -class AccessibilityTableHeaderContainer : public AccessibilityMockObject { - -private: - AccessibilityTableHeaderContainer(); +class AccessibilityTableHeaderContainer final : public AccessibilityMockObject { public: - static PassRefPtr<AccessibilityTableHeaderContainer> create(); + static Ref<AccessibilityTableHeaderContainer> create(); virtual ~AccessibilityTableHeaderContainer(); - virtual AccessibilityRole roleValue() const { return TableHeaderContainerRole; } + virtual AccessibilityRole roleValue() const override { return TableHeaderContainerRole; } - virtual void addChildren(); + virtual void addChildren() override; - virtual LayoutRect elementRect() const; + virtual LayoutRect elementRect() const override; private: - LayoutRect m_headerRect; + AccessibilityTableHeaderContainer(); - virtual bool computeAccessibilityIsIgnored() const; + virtual bool computeAccessibilityIsIgnored() const override; + + LayoutRect m_headerRect; }; } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.cpp b/Source/WebCore/accessibility/AccessibilityTableRow.cpp index 44edada28..b51b18c41 100644 --- a/Source/WebCore/accessibility/AccessibilityTableRow.cpp +++ b/Source/WebCore/accessibility/AccessibilityTableRow.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -30,6 +30,7 @@ #include "AccessibilityTableRow.h" #include "AXObjectCache.h" +#include "AccessibilityTable.h" #include "AccessibilityTableCell.h" #include "HTMLNames.h" #include "HTMLTableRowElement.h" @@ -37,8 +38,6 @@ #include "RenderTableCell.h" #include "RenderTableRow.h" -using namespace std; - namespace WebCore { using namespace HTMLNames; @@ -52,9 +51,9 @@ AccessibilityTableRow::~AccessibilityTableRow() { } -PassRefPtr<AccessibilityTableRow> AccessibilityTableRow::create(RenderObject* renderer) +Ref<AccessibilityTableRow> AccessibilityTableRow::create(RenderObject* renderer) { - return adoptRef(new AccessibilityTableRow(renderer)); + return adoptRef(*new AccessibilityTableRow(renderer)); } AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() @@ -62,11 +61,8 @@ AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() if (!isTableRow()) return AccessibilityRenderObject::determineAccessibilityRole(); - m_ariaRole = determineAriaRoleAttribute(); - - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) + return m_ariaRole; return RowRole; } @@ -74,10 +70,7 @@ AccessibilityRole AccessibilityTableRow::determineAccessibilityRole() bool AccessibilityTableRow::isTableRow() const { AccessibilityObject* table = parentTable(); - if (!table || !table->isAccessibilityTable()) - return false; - - return true; + return is<AccessibilityTable>(table) && downcast<AccessibilityTable>(*table).isExposableThroughAccessibility(); } AccessibilityObject* AccessibilityTableRow::observableObject() const @@ -100,43 +93,86 @@ bool AccessibilityTableRow::computeAccessibilityIsIgnored() const return false; } -AccessibilityObject* AccessibilityTableRow::parentTable() const +AccessibilityTable* AccessibilityTableRow::parentTable() const { // The parent table might not be the direct ancestor of the row unfortunately. ARIA states that role="grid" should // only have "row" elements, but if not, we still should handle it gracefully by finding the right table. for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - // If this is a table object, but not an accessibility table, we should stop because we don't want to + // If this is a non-anonymous table object, but not an accessibility table, we should stop because we don't want to // choose another ancestor table as this row's table. - if (parent->isTable()) - return parent->isAccessibilityTable() ? parent : 0; + if (is<AccessibilityTable>(*parent)) { + auto& parentTable = downcast<AccessibilityTable>(*parent); + if (parentTable.isExposableThroughAccessibility()) + return &parentTable; + if (parentTable.node()) + break; + } } - return 0; + return nullptr; } AccessibilityObject* AccessibilityTableRow::headerObject() { if (!m_renderer || !m_renderer->isTableRow()) - return 0; + return nullptr; - AccessibilityChildrenVector rowChildren = children(); + const auto& rowChildren = children(); if (!rowChildren.size()) - return 0; + return nullptr; // check the first element in the row to see if it is a TH element AccessibilityObject* cell = rowChildren[0].get(); - if (!cell->isTableCell()) - return 0; + if (!is<AccessibilityTableCell>(*cell)) + return nullptr; - RenderObject* cellRenderer = static_cast<AccessibilityTableCell*>(cell)->renderer(); + RenderObject* cellRenderer = downcast<AccessibilityTableCell>(*cell).renderer(); if (!cellRenderer) - return 0; + return nullptr; Node* cellNode = cellRenderer->node(); if (!cellNode || !cellNode->hasTagName(thTag)) - return 0; + return nullptr; return cell; } +void AccessibilityTableRow::addChildren() +{ + AccessibilityRenderObject::addChildren(); + + // "ARIA 1.1, If the set of columns which is present in the DOM is contiguous, and if there are no cells which span more than one row or + // column in that set, then authors may place aria-colindex on each row, setting the value to the index of the first column of the set." + // Update child cells' ariaColIndex if there's an aria-colindex value set for the row. So the cell doesn't have to go through the siblings + // to calculate the index. + int colIndex = ariaColumnIndex(); + if (colIndex == -1) + return; + + unsigned index = 0; + for (const auto& cell : children()) { + if (is<AccessibilityTableCell>(*cell)) + downcast<AccessibilityTableCell>(*cell).setARIAColIndexFromRow(colIndex + index); + index++; + } +} + +int AccessibilityTableRow::ariaColumnIndex() const +{ + const AtomicString& colIndexValue = getAttribute(aria_colindexAttr); + if (colIndexValue.toInt() >= 1) + return colIndexValue.toInt(); + + return -1; +} + +int AccessibilityTableRow::ariaRowIndex() const +{ + const AtomicString& rowIndexValue = getAttribute(aria_rowindexAttr); + if (rowIndexValue.toInt() >= 1) + return rowIndexValue.toInt(); + + return -1; +} + } // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTableRow.h b/Source/WebCore/accessibility/AccessibilityTableRow.h index d242ee449..887c213c7 100644 --- a/Source/WebCore/accessibility/AccessibilityTableRow.h +++ b/Source/WebCore/accessibility/AccessibilityTableRow.h @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -33,19 +33,16 @@ namespace WebCore { +class AccessibilityTable; + class AccessibilityTableRow : public AccessibilityRenderObject { - -protected: - explicit AccessibilityTableRow(RenderObject*); public: - static PassRefPtr<AccessibilityTableRow> create(RenderObject*); + static Ref<AccessibilityTableRow> create(RenderObject*); virtual ~AccessibilityTableRow(); - - virtual bool isTableRow() const; // retrieves the "row" header (a th tag in the rightmost column) virtual AccessibilityObject* headerObject(); - virtual AccessibilityObject* parentTable() const; + virtual AccessibilityTable* parentTable() const; void setRowIndex(int rowIndex) { m_rowIndex = rowIndex; } int rowIndex() const { return m_rowIndex; } @@ -54,16 +51,26 @@ public: // in the row, but their col/row spans overlap into it void appendChild(AccessibilityObject*); + virtual void addChildren() override; + + int ariaColumnIndex() const; + int ariaRowIndex() const; + protected: - virtual AccessibilityRole determineAccessibilityRole(); + explicit AccessibilityTableRow(RenderObject*); + + virtual AccessibilityRole determineAccessibilityRole() override final; private: + virtual bool isTableRow() const override final; + virtual AccessibilityObject* observableObject() const override final; + virtual bool computeAccessibilityIsIgnored() const override final; + int m_rowIndex; - - virtual AccessibilityObject* observableObject() const; - virtual bool computeAccessibilityIsIgnored() const; -}; - -} // namespace WebCore +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTableRow, isTableRow()) #endif // AccessibilityTableRow_h diff --git a/Source/WebCore/accessibility/AccessibilityTree.cpp b/Source/WebCore/accessibility/AccessibilityTree.cpp new file mode 100644 index 000000000..f4613cdce --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTree.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "AccessibilityTree.h" + +#include "AXObjectCache.h" +#include "AccessibilityTreeItem.h" +#include "Element.h" +#include "HTMLNames.h" + +#include <wtf/Deque.h> + +namespace WebCore { + +using namespace HTMLNames; + +AccessibilityTree::AccessibilityTree(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ +} + +AccessibilityTree::~AccessibilityTree() +{ +} + +Ref<AccessibilityTree> AccessibilityTree::create(RenderObject* renderer) +{ + return adoptRef(*new AccessibilityTree(renderer)); +} + +bool AccessibilityTree::computeAccessibilityIsIgnored() const +{ + return accessibilityIsIgnoredByDefault(); +} + +AccessibilityRole AccessibilityTree::determineAccessibilityRole() +{ + if ((m_ariaRole = determineAriaRoleAttribute()) != TreeRole) + return AccessibilityRenderObject::determineAccessibilityRole(); + + return isTreeValid() ? TreeRole : GroupRole; +} + +bool AccessibilityTree::isTreeValid() const +{ + // A valid tree can only have treeitem or group of treeitems as a child + // http://www.w3.org/TR/wai-aria/roles#tree + + Node* node = this->node(); + if (!node) + return false; + + Deque<Node*> queue; + for (auto* child = node->firstChild(); child; child = child->nextSibling()) + queue.append(child); + + while (!queue.isEmpty()) { + auto child = queue.takeFirst(); + + if (!is<Element>(*child)) + continue; + if (nodeHasRole(child, "treeitem")) + continue; + if (!nodeHasRole(child, "group")) + return false; + + for (auto* groupChild = child->firstChild(); groupChild; groupChild = groupChild->nextSibling()) + queue.append(groupChild); + } + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.h b/Source/WebCore/accessibility/AccessibilityTree.h index 86fdc361a..2e69fe9cd 100644 --- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.h +++ b/Source/WebCore/accessibility/AccessibilityTree.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,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,13 +26,28 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef WebAccessibilityObjectWrapper_h -#define WebAccessibilityObjectWrapper_h -#import "WebAccessibilityObjectWrapperBase.h" +#ifndef AccessibilityTree_h +#define AccessibilityTree_h -@interface WebAccessibilityObjectWrapper : WebAccessibilityObjectWrapperBase +#include "AccessibilityRenderObject.h" -@end +namespace WebCore { + +class AccessibilityTree final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityTree> create(RenderObject*); + virtual ~AccessibilityTree(); -#endif // WebAccessibilityObjectWrapper_h +private: + explicit AccessibilityTree(RenderObject*); + virtual bool computeAccessibilityIsIgnored() const override; + virtual AccessibilityRole determineAccessibilityRole() override; + bool isTreeValid() const; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTree, isTree()) + +#endif // AccessibilityTree_h diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.h b/Source/WebCore/accessibility/AccessibilityTreeItem.cpp index 873dff7ac..cd36c38bd 100644 --- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.h +++ b/Source/WebCore/accessibility/AccessibilityTreeItem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Apple Inc. All rights reserved. + * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,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,45 +26,40 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef WebAccessibilityObjectWrapperBase_h -#define WebAccessibilityObjectWrapperBase_h +#include "config.h" +#include "AccessibilityTreeItem.h" -#include <CoreGraphics/CoreGraphics.h> +#include "AXObjectCache.h" +#include "HTMLNames.h" namespace WebCore { -class AccessibilityObject; -class IntRect; -class FloatPoint; -class Path; -class VisiblePosition; + +using namespace HTMLNames; + +AccessibilityTreeItem::AccessibilityTreeItem(RenderObject* renderer) + : AccessibilityRenderObject(renderer) +{ } - -@interface WebAccessibilityObjectWrapperBase : NSObject { - WebCore::AccessibilityObject* m_object; + +AccessibilityTreeItem::~AccessibilityTreeItem() +{ +} + +Ref<AccessibilityTreeItem> AccessibilityTreeItem::create(RenderObject* renderer) +{ + return adoptRef(*new AccessibilityTreeItem(renderer)); } - -- (id)initWithAccessibilityObject:(WebCore::AccessibilityObject*)axObject; -- (void)detach; -- (WebCore::AccessibilityObject*)accessibilityObject; -- (BOOL)updateObjectBackingStore; - -- (NSString *)accessibilityTitle; -- (NSString *)accessibilityDescription; -- (NSString *)accessibilityHelpText; - -- (NSString *)ariaLandmarkRoleDescription; - -- (id)attachmentView; -// Used to inform an element when a notification is posted for it. Used by DRT. -- (void)accessibilityPostedNotification:(NSString *)notificationName; - -- (CGPathRef)convertPathToScreenSpace:(WebCore::Path &)path; -- (CGPoint)convertPointToScreenSpace:(WebCore::FloatPoint &)point; - -// Math related functions -- (NSArray *)accessibilityMathPostscriptPairs; -- (NSArray *)accessibilityMathPrescriptPairs; -@end +AccessibilityRole AccessibilityTreeItem::determineAccessibilityRole() +{ + + // Walk the parent chain looking for a parent that is a tree. A treeitem is + // only considered valid if it is in a tree. + AccessibilityObject* parent = nullptr; + for (parent = parentObject(); parent && !parent->isTree(); parent = parent->parentObject()) { } + m_isTreeItemValid = parent; -#endif // WebAccessibilityObjectWrapperBase_h + return AccessibilityRenderObject::determineAccessibilityRole(); +} + +} // namespace WebCore diff --git a/Source/WebCore/accessibility/AccessibilityTreeItem.h b/Source/WebCore/accessibility/AccessibilityTreeItem.h new file mode 100644 index 000000000..f6e332ad8 --- /dev/null +++ b/Source/WebCore/accessibility/AccessibilityTreeItem.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef AccessibilityTreeItem_h +#define AccessibilityTreeItem_h + +#include "AccessibilityRenderObject.h" + +namespace WebCore { + +class AccessibilityTreeItem final : public AccessibilityRenderObject { +public: + static Ref<AccessibilityTreeItem> create(RenderObject*); + virtual ~AccessibilityTreeItem(); + + virtual bool shouldIgnoreAttributeRole() const override { return !m_isTreeItemValid; } + +private: + explicit AccessibilityTreeItem(RenderObject*); + virtual AccessibilityRole determineAccessibilityRole() override; + bool m_isTreeItemValid; +}; + +} // namespace WebCore + +SPECIALIZE_TYPE_TRAITS_ACCESSIBILITY(AccessibilityTreeItem, isTreeItem()) + +#endif // AccessibilityTreeItem_h diff --git a/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp b/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp index 826d6c20e..85e0c64d5 100644 --- a/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp +++ b/Source/WebCore/accessibility/atk/AXObjectCacheAtk.cpp @@ -30,14 +30,38 @@ #include "Range.h" #include "TextIterator.h" #include "WebKitAccessibleWrapperAtk.h" -#include <wtf/gobject/GOwnPtr.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/glib/GRefPtr.h> #include <wtf/text/CString.h> namespace WebCore { -void AXObjectCache::detachWrapper(AccessibilityObject* obj) +void AXObjectCache::detachWrapper(AccessibilityObject* obj, AccessibilityDetachmentType detachmentType) { - webkitAccessibleDetach(WEBKIT_ACCESSIBLE(obj->wrapper())); + AtkObject* wrapper = obj->wrapper(); + ASSERT(wrapper); + + // If an object is being detached NOT because of the AXObjectCache being destroyed, + // then it's being removed from the accessibility tree and we should emit a signal. + if (detachmentType != CacheDestroyed) { + if (obj->document()) { + // Look for the right object to emit the signal from, but using the implementation + // of atk_object_get_parent from AtkObject class (which uses a cached pointer if set) + // since the accessibility hierarchy in WebCore will no longer be navigable. + gpointer webkitAccessibleClass = g_type_class_peek_parent(WEBKIT_ACCESSIBLE_GET_CLASS(wrapper)); + gpointer atkObjectClass = g_type_class_peek_parent(webkitAccessibleClass); + AtkObject* atkParent = ATK_OBJECT_CLASS(atkObjectClass)->get_parent(ATK_OBJECT(wrapper)); + + // We don't want to emit any signal from an object outside WebKit's world. + if (WEBKIT_IS_ACCESSIBLE(atkParent)) { + // The accessibility hierarchy is already invalid, so the parent-children relationships + // in the AccessibilityObject tree are not there anymore, so we can't know the offset. + g_signal_emit_by_name(atkParent, "children-changed::remove", -1, wrapper); + } + } + } + + webkitAccessibleDetach(WEBKIT_ACCESSIBLE(wrapper)); } void AXObjectCache::attachWrapper(AccessibilityObject* obj) @@ -45,6 +69,37 @@ void AXObjectCache::attachWrapper(AccessibilityObject* obj) AtkObject* atkObj = ATK_OBJECT(webkitAccessibleNew(obj)); obj->setWrapper(atkObj); g_object_unref(atkObj); + + // If an object is being attached and we are not in the middle of a layout update, then + // we should report ATs by emitting the children-changed::add signal from the parent. + Document* document = obj->document(); + if (!document || document->childNeedsStyleRecalc()) + return; + + // Don't emit the signal when the actual object being added is not going to be exposed. + if (obj->accessibilityIsIgnoredByDefault()) + return; + + // Don't emit the signal if the object being added is not -- or not yet -- rendered, + // which can occur in nested iframes. In these instances we don't want to ignore the + // child. But if an assistive technology is listening, AT-SPI2 will attempt to create + // and cache the state set for the child upon emission of the signal. If the object + // has not yet been rendered, this will result in a crash. + if (!obj->renderer()) + return; + + // Don't emit the signal for objects whose parents won't be exposed directly. + AccessibilityObject* coreParent = obj->parentObjectUnignored(); + if (!coreParent || coreParent->accessibilityIsIgnoredByDefault()) + return; + + // Look for the right object to emit the signal from. + AtkObject* atkParent = coreParent->wrapper(); + if (!atkParent) + return; + + size_t index = coreParent->children(false).find(obj); + g_signal_emit_by_name(atkParent, "children-changed::add", index, atkObj); } static AccessibilityObject* getListObject(AccessibilityObject* object) @@ -60,7 +115,7 @@ static AccessibilityObject* getListObject(AccessibilityObject* object) // For menu lists we need to return the first accessible child, // with role MenuListPopupRole, since that's the one holding the list // of items with role MenuListOptionRole. - AccessibilityObject::AccessibilityChildrenVector children = object->children(); + const AccessibilityObject::AccessibilityChildrenVector& children = object->children(); if (!children.size()) return 0; @@ -77,8 +132,8 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // focused object and its associated list object, as per previous // calls to this function, in order to properly decide whether to // emit some signals or not. - DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldListObject, ()); - DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldFocusedObject, ()); + static NeverDestroyed<RefPtr<AccessibilityObject>> oldListObject; + static NeverDestroyed<RefPtr<AccessibilityObject>> oldFocusedObject; // Only list boxes and menu lists supported so far. if (!object || !(object->isListBox() || object->isMenuList())) @@ -86,25 +141,23 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // Only support HTML select elements so far (ARIA selectors not supported). Node* node = object->node(); - if (!node || !node->hasTagName(HTMLNames::selectTag)) + if (!is<HTMLSelectElement>(node)) return; // Emit signal from the listbox's point of view first. g_signal_emit_by_name(object->wrapper(), "selection-changed"); // Find the item where the selection change was triggered from. - HTMLSelectElement* select = toHTMLSelectElement(node); - if (!select) - return; - int changedItemIndex = select->activeSelectionStartListIndex(); + HTMLSelectElement& select = downcast<HTMLSelectElement>(*node); + int changedItemIndex = select.activeSelectionStartListIndex(); AccessibilityObject* listObject = getListObject(object); if (!listObject) { - oldListObject = 0; + oldListObject.get() = nullptr; return; } - AccessibilityObject::AccessibilityChildrenVector items = listObject->children(); + const AccessibilityObject::AccessibilityChildrenVector& items = listObject->children(); if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size())) return; AccessibilityObject* item = items.at(changedItemIndex).get(); @@ -112,29 +165,33 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object) // Ensure the current list object is the same than the old one so // further comparisons make sense. Otherwise, just reset // oldFocusedObject so it won't be taken into account. - if (oldListObject != listObject) - oldFocusedObject = 0; + if (oldListObject.get() != listObject) + oldFocusedObject.get() = nullptr; - AtkObject* axItem = item ? item->wrapper() : 0; - AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0; + AtkObject* axItem = item ? item->wrapper() : nullptr; + AtkObject* axOldFocusedObject = oldFocusedObject.get() ? oldFocusedObject.get()->wrapper() : nullptr; // Old focused object just lost focus, so emit the events. if (axOldFocusedObject && axItem != axOldFocusedObject) { g_signal_emit_by_name(axOldFocusedObject, "focus-event", false); - g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", false); + atk_object_notify_state_change(axOldFocusedObject, ATK_STATE_FOCUSED, false); } // Emit needed events for the currently (un)selected item. if (axItem) { bool isSelected = item->isSelected(); - g_signal_emit_by_name(axItem, "state-change", "selected", isSelected); - g_signal_emit_by_name(axItem, "focus-event", isSelected); - g_signal_emit_by_name(axItem, "state-change", "focused", isSelected); + atk_object_notify_state_change(axItem, ATK_STATE_SELECTED, isSelected); + // When the selection changes in a collapsed widget such as a combo box + // whose child menu is not showing, that collapsed widget retains focus. + if (!object->isCollapsed()) { + g_signal_emit_by_name(axItem, "focus-event", isSelected); + atk_object_notify_state_change(axItem, ATK_STATE_FOCUSED, isSelected); + } } // Update pointers to the previously involved objects. - oldListObject = listObject; - oldFocusedObject = item; + oldListObject.get() = listObject; + oldFocusedObject.get() = item; } void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification) @@ -143,27 +200,47 @@ void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX if (!axObject) return; - if (notification == AXCheckedStateChanged) { - if (!coreObject->isCheckboxOrRadio()) + switch (notification) { + case AXCheckedStateChanged: + if (!coreObject->isCheckboxOrRadio() && !coreObject->isSwitch()) return; - g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked()); - } else if (notification == AXSelectedChildrenChanged || notification == AXMenuListValueChanged) { - if (notification == AXMenuListValueChanged && coreObject->isMenuList()) { + atk_object_notify_state_change(axObject, ATK_STATE_CHECKED, coreObject->isChecked()); + break; + + case AXSelectedChildrenChanged: + case AXMenuListValueChanged: + // Accessible focus claims should not be made if the associated widget is not focused. + if (notification == AXMenuListValueChanged && coreObject->isMenuList() && coreObject->isFocused()) { g_signal_emit_by_name(axObject, "focus-event", true); - g_signal_emit_by_name(axObject, "state-change", "focused", true); + atk_object_notify_state_change(axObject, ATK_STATE_FOCUSED, true); } notifyChildrenSelectionChange(coreObject); - } else if (notification == AXValueChanged) { - if (!ATK_IS_VALUE(axObject)) - return; + break; - AtkPropertyValues propertyValues; - propertyValues.property_name = "accessible-value"; + case AXValueChanged: + if (ATK_IS_VALUE(axObject)) { + AtkPropertyValues propertyValues; + propertyValues.property_name = "accessible-value"; + + memset(&propertyValues.new_value, 0, sizeof(GValue)); +#if ATK_CHECK_VERSION(2,11,92) + double value; + atk_value_get_value_and_text(ATK_VALUE(axObject), &value, nullptr); + g_value_set_double(g_value_init(&propertyValues.new_value, G_TYPE_DOUBLE), value); +#else + atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value); +#endif - memset(&propertyValues.new_value, 0, sizeof(GValue)); - atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value); + g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL); + } + break; + + case AXInvalidStatusChanged: + atk_object_notify_state_change(axObject, ATK_STATE_INVALID_ENTRY, coreObject->invalidStatus() != "false"); + break; - g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL); + default: + break; } } @@ -172,7 +249,7 @@ void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* obje if (!object || text.isEmpty()) return; - AccessibilityObject* parentObject = object->parentObjectUnignored(); + AccessibilityObject* parentObject = object->isNonNativeTextControl() ? object : object->parentObjectUnignored(); if (!parentObject) return; @@ -185,18 +262,21 @@ void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* obje return; // Ensure document's layout is up-to-date before using TextIterator. - Document* document = node->document(); - document->updateLayout(); + Document& document = node->document(); + document.updateLayout(); // Select the right signal to be emitted CString detail; switch (textChange) { - case AXObjectCache::AXTextInserted: + case AXTextInserted: detail = "text-insert"; break; - case AXObjectCache::AXTextDeleted: + case AXTextDeleted: detail = "text-remove"; break; + case AXTextAttributesChanged: + detail = "text-attributes-changed"; + break; } String textToEmit = text; @@ -229,34 +309,34 @@ void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* o switch (loadingEvent) { case AXObjectCache::AXLoadingStarted: - g_signal_emit_by_name(axObject, "state-change", "busy", true); + atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true); break; case AXObjectCache::AXLoadingReloaded: - g_signal_emit_by_name(axObject, "state-change", "busy", true); + atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true); g_signal_emit_by_name(axObject, "reload"); break; case AXObjectCache::AXLoadingFailed: g_signal_emit_by_name(axObject, "load-stopped"); - g_signal_emit_by_name(axObject, "state-change", "busy", false); + atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false); break; case AXObjectCache::AXLoadingFinished: g_signal_emit_by_name(axObject, "load-complete"); - g_signal_emit_by_name(axObject, "state-change", "busy", false); + atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false); break; } } -void AXObjectCache::handleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode) +void AXObjectCache::platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode) { RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedNode); if (oldObject) { g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false); - g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", false); + atk_object_notify_state_change(oldObject->wrapper(), ATK_STATE_FOCUSED, false); } RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedNode); if (newObject) { g_signal_emit_by_name(newObject->wrapper(), "focus-event", true); - g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", true); + atk_object_notify_state_change(newObject->wrapper(), ATK_STATE_FOCUSED, true); } } diff --git a/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp b/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp index 8bac09c11..84668e2e2 100644 --- a/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp +++ b/Source/WebCore/accessibility/atk/AccessibilityObjectAtk.cpp @@ -21,8 +21,13 @@ #include "config.h" #include "AccessibilityObject.h" -#include "RenderObject.h" +#include "HTMLSpanElement.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderIterator.h" +#include "RenderTableCell.h" #include "RenderText.h" +#include "TextControlInnerElements.h" #include <glib-object.h> #if HAVE(ACCESSIBILITY) @@ -41,9 +46,6 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO return DefaultBehavior; AccessibilityRole role = roleValue(); - if (role == HorizontalRuleRole) - return IncludeObject; - // We expose the slider as a whole but not its value indicator. if (role == SliderThumbRole) return IgnoreObject; @@ -58,7 +60,7 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO return IgnoreObject; // Include all tables, even layout tables. The AT can decide what to do with each. - if (role == CellRole || role == TableRole) + if (role == CellRole || role == TableRole || role == ColumnHeaderRole || role == RowHeaderRole) return IncludeObject; // The object containing the text should implement AtkText itself. @@ -78,39 +80,72 @@ AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesO if (role == UnknownRole) return IgnoreObject; - // Given a paragraph or div containing a non-nested anonymous block, WebCore - // ignores the paragraph or div and includes the block. We want the opposite: - // ATs are expecting accessible objects associated with textual elements. They - // usually have no need for the anonymous block. And when the wrong objects - // get included or ignored, needed accessibility signals do not get emitted. - if (role == ParagraphRole || role == DivRole) { - // Don't call textUnderElement() here, because it's slow and it can - // crash when called while we're in the middle of a subtree being deleted. - if (!renderer()->firstChild()) - return DefaultBehavior; - - if (!parent->renderer() || parent->renderer()->isAnonymousBlock()) - return DefaultBehavior; - - for (RenderObject* r = renderer()->firstChild(); r; r = r->nextSibling()) { - if (r->isAnonymousBlock()) + if (role == InlineRole) + return IncludeObject; + + // Lines past this point only make sense for AccessibilityRenderObjects. + RenderObject* renderObject = renderer(); + if (!renderObject) + return DefaultBehavior; + + // We always want to include paragraphs that have rendered content. + // WebCore Accessibility does so unless there is a RenderBlock child. + if (role == ParagraphRole) { + auto child = childrenOfType<RenderBlock>(downcast<RenderElement>(*renderObject)).first(); + return child ? IncludeObject : DefaultBehavior; + } + + // We always want to include table cells (layout and CSS) that have rendered text content. + if (is<RenderTableCell>(renderObject)) { + for (const auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(*renderObject))) { + if (is<RenderInline>(child) || is<RenderText>(child) || is<HTMLSpanElement>(child.node())) return IncludeObject; } + return DefaultBehavior; + } + + if (renderObject->isAnonymousBlock()) { + // The text displayed by an ARIA menu item is exposed through the accessible name. + if (parent->isMenuItem()) + return IgnoreObject; + + // The text displayed in headings is typically exposed in the heading itself. + if (parent->isHeading()) + return IgnoreObject; + + // The text displayed in list items is typically exposed in the list item itself. + if (parent->isListItem()) + return IgnoreObject; + + // The text displayed in links is typically exposed in the link itself. + if (parent->isLink()) + return IgnoreObject; + + // FIXME: This next one needs some further consideration. But paragraphs are not + // typically huge (like divs). And ignoring anonymous block children of paragraphs + // will preserve existing behavior. + if (parent->roleValue() == ParagraphRole) + return IgnoreObject; + + return DefaultBehavior; } - // Block spans result in objects of ATK_ROLE_PANEL which are almost always unwanted. - // However, if we ignore block spans whose parent is the body, the child controls - // will become immediate children of the ATK_ROLE_DOCUMENT_FRAME and any text will - // become text within the document frame itself. This ultimately may be what we want - // and would largely be consistent with what we see from Gecko. However, ignoring - // spans whose parent is the body changes the current behavior we see from WebCore. - // Until we have sufficient time to properly analyze these cases, we will defer to - // WebCore. We only check that the parent is not aria because we do not expect - // anonymous blocks which are aria-related to themselves have an aria role, nor - // have we encountered instances where the parent of an anonymous block also lacked - // an aria role but the grandparent had one. - if (renderer() && renderer()->isAnonymousBlock() && !parent->renderer()->isBody() - && parent->ariaRoleAttribute() == UnknownRole) + Node* node = renderObject->node(); + if (!node) + return DefaultBehavior; + + // We don't want <span> elements to show up in the accessibility hierarchy unless + // we have good reasons for that (e.g. focusable or visible because of containing + // a meaningful accessible name, maybe set through ARIA), so we can use + // atk_component_grab_focus() to set the focus to it. + if (is<HTMLSpanElement>(node) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion()) + return IgnoreObject; + + // If we include TextControlInnerTextElement children, changes to those children + // will result in focus and text notifications that suggest the user is no longer + // in the control. This can be especially problematic for screen reader users with + // key echo enabled when typing in a password input. + if (is<TextControlInnerTextElement>(node)) return IgnoreObject; return DefaultBehavior; @@ -143,7 +178,7 @@ bool AccessibilityObject::allowsTextRanges() const // Check roles as the last fallback mechanism. AccessibilityRole role = roleValue(); - return role == ParagraphRole || role == LabelRole || role == DivRole || role == FormRole; + return role == ParagraphRole || role == LabelRole || role == DivRole || role == FormRole || role == PreRole; } unsigned AccessibilityObject::getLengthForTextRange() const @@ -155,16 +190,14 @@ unsigned AccessibilityObject::getLengthForTextRange() const // Gtk ATs need this for all text objects; not just text controls. Node* node = this->node(); - RenderObject* renderer = node ? node->renderer() : 0; - if (renderer && renderer->isText()) { - RenderText* renderText = toRenderText(renderer); - textLength = renderText ? renderText->textLength() : 0; - } + RenderObject* renderer = node ? node->renderer() : nullptr; + if (is<RenderText>(renderer)) + textLength = downcast<RenderText>(*renderer).textLength(); // Get the text length from the elements under the // accessibility object if the value is still zero. if (!textLength && allowsTextRanges()) - textLength = textUnderElement(TextUnderElementModeIncludeAllChildren).length(); + textLength = textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).length(); return textLength; } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp index 8a4ff4227..9df03efa9 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleHyperlink.cpp @@ -94,6 +94,7 @@ static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), FALSE); g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE); g_return_val_if_fail(!index, FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE); if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) return FALSE; @@ -109,6 +110,7 @@ static gint webkitAccessibleHyperlinkActionGetNActions(AtkAction* action) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl)) return 0; @@ -121,6 +123,7 @@ static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* act g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); g_return_val_if_fail(!index, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); // TODO: Need a way to provide/localize action descriptions. notImplemented(); @@ -130,10 +133,11 @@ static const gchar* webkitAccessibleHyperlinkActionGetDescription(AtkAction* act static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* action, gint index) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); g_return_val_if_fail(!index, 0); WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv; - g_return_val_if_fail(priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(priv->hyperlinkImpl, 0); if (!ATK_IS_ACTION(priv->hyperlinkImpl)) return 0; @@ -149,10 +153,11 @@ static const gchar* webkitAccessibleHyperlinkActionGetKeybinding(AtkAction* acti static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint index) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0); g_return_val_if_fail(!index, 0); WebKitAccessibleHyperlinkPrivate* priv = WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv; - g_return_val_if_fail(priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(priv->hyperlinkImpl, 0); if (!ATK_IS_ACTION(priv->hyperlinkImpl)) return 0; @@ -177,10 +182,14 @@ static void atkActionInterfaceInit(AtkActionIface* iface) static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + // FIXME: Do NOT support more than one instance of an AtkObject // implementing AtkHyperlinkImpl in every instance of AtkHyperLink g_return_val_if_fail(!index, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + AccessibilityObject* coreObject = core(link); if (!coreObject || coreObject->url().isNull()) return 0; @@ -197,6 +206,8 @@ static AtkObject* webkitAccessibleHyperlinkGetObject(AtkHyperlink* link, gint in // implementing AtkHyperlinkImpl in every instance of AtkHyperLink g_return_val_if_fail(!index, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + return ATK_OBJECT(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl); } @@ -220,16 +231,18 @@ static gint getRangeLengthForObject(AccessibilityObject* obj, Range* range) return baseLength; RenderObject* renderer = markerObj->renderer(); - if (!renderer || !renderer->isListMarker()) + if (!is<RenderListMarker>(renderer)) return baseLength; - RenderListMarker* marker = toRenderListMarker(renderer); - return baseLength + marker->text().length() + marker->suffix().length(); + auto& marker = downcast<RenderListMarker>(*renderer); + return baseLength + marker.text().length() + marker.suffix().length(); } static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); AccessibilityObject* coreObject = core(link); if (!coreObject) @@ -254,6 +267,8 @@ static gint webkitAccessibleHyperlinkGetStartIndex(AtkHyperlink* link) static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link) { g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); AccessibilityObject* coreObject = core(link); if (!coreObject) @@ -277,8 +292,9 @@ static gint webkitAccessibleHyperlinkGetEndIndex(AtkHyperlink* link) static gboolean webkitAccessibleHyperlinkIsValid(AtkHyperlink* link) { - g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), FALSE); g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); // Link is valid for the whole object's lifetime return TRUE; @@ -290,11 +306,17 @@ static gint webkitAccessibleHyperlinkGetNAnchors(AtkHyperlink* link) // implementing AtkHyperlinkImpl in every instance of AtkHyperLink g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0); g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0); + return 1; } -static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink*) +static gboolean webkitAccessibleHyperlinkIsSelectedLink(AtkHyperlink* link) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), FALSE); + g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, FALSE); + // Not implemented: this function is deprecated in ATK now notImplemented(); return FALSE; @@ -363,7 +385,7 @@ static void webkitAccessibleHyperlinkClassInit(AtkHyperlinkClass* klass) static void webkitAccessibleHyperlinkInit(AtkHyperlink* link) { WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv = WEBKIT_ACCESSIBLE_HYPERLINK_GET_PRIVATE(link); - WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = 0; + WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl = nullptr; } GType webkitAccessibleHyperlinkGetType() diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.cpp index eb18beb6d..c79bdc4d6 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceAction.cpp @@ -51,17 +51,27 @@ static AccessibilityObject* core(AtkAction* action) static gboolean webkitAccessibleActionDoAction(AtkAction* action, gint index) { + g_return_val_if_fail(ATK_IS_ACTION(action), FALSE); g_return_val_if_fail(!index, FALSE); + + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(action), FALSE); + return core(action)->performDefaultAction(); } -static gint webkitAccessibleActionGetNActions(AtkAction*) +static gint webkitAccessibleActionGetNActions(AtkAction* action) { + g_return_val_if_fail(ATK_IS_ACTION(action), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(action), 0); + return 1; } -static const gchar* webkitAccessibleActionGetDescription(AtkAction*, gint) +static const gchar* webkitAccessibleActionGetDescription(AtkAction* action, gint) { + g_return_val_if_fail(ATK_IS_ACTION(action), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(action), 0); + // TODO: Need a way to provide/localize action descriptions. notImplemented(); return ""; @@ -69,14 +79,20 @@ static const gchar* webkitAccessibleActionGetDescription(AtkAction*, gint) static const gchar* webkitAccessibleActionGetKeybinding(AtkAction* action, gint index) { + g_return_val_if_fail(ATK_IS_ACTION(action), 0); g_return_val_if_fail(!index, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(action), 0); + // FIXME: Construct a proper keybinding string. return cacheAndReturnAtkProperty(ATK_OBJECT(action), AtkCachedActionKeyBinding, core(action)->accessKey().string()); } static const gchar* webkitAccessibleActionGetName(AtkAction* action, gint index) { + g_return_val_if_fail(ATK_IS_ACTION(action), 0); g_return_val_if_fail(!index, 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(action), 0); + return cacheAndReturnAtkProperty(ATK_OBJECT(action), AtkCachedActionName, core(action)->actionVerb()); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp index 7103ea686..e86f23098 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceComponent.cpp @@ -68,6 +68,9 @@ static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coor static AtkObject* webkitAccessibleComponentRefAccessibleAtPoint(AtkComponent* component, gint x, gint y, AtkCoordType coordType) { + g_return_val_if_fail(ATK_IS_COMPONENT(component), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(component), 0); + IntPoint pos = atkToContents(core(component), coordType, x, y); AccessibilityObject* target = core(component)->accessibilityHitTest(pos); @@ -79,12 +82,18 @@ static AtkObject* webkitAccessibleComponentRefAccessibleAtPoint(AtkComponent* co static void webkitAccessibleComponentGetExtents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType) { - IntRect rect = pixelSnappedIntRect(core(component)->elementRect()); + g_return_if_fail(ATK_IS_COMPONENT(component)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(component)); + + IntRect rect = snappedIntRect(core(component)->elementRect()); contentsRelativeToAtkCoordinateType(core(component), coordType, rect, x, y, width, height); } static gboolean webkitAccessibleComponentGrabFocus(AtkComponent* component) { + g_return_val_if_fail(ATK_IS_COMPONENT(component), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(component), FALSE); + core(component)->setFocused(true); return core(component)->isFocused(); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp index f07484318..6752d12d3 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceDocument.cpp @@ -78,12 +78,18 @@ static const gchar* documentAttributeValue(AtkDocument* document, const gchar* a static const gchar* webkitAccessibleDocumentGetAttributeValue(AtkDocument* document, const gchar* attribute) { + g_return_val_if_fail(ATK_IS_DOCUMENT(document), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(document), 0); + return documentAttributeValue(document, attribute); } static AtkAttributeSet* webkitAccessibleDocumentGetAttributes(AtkDocument* document) { - AtkAttributeSet* attributeSet = 0; + g_return_val_if_fail(ATK_IS_DOCUMENT(document), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(document), 0); + + AtkAttributeSet* attributeSet = nullptr; const gchar* attributes[] = { "DocType", "Encoding", "URI" }; for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) { @@ -97,6 +103,9 @@ static AtkAttributeSet* webkitAccessibleDocumentGetAttributes(AtkDocument* docum static const gchar* webkitAccessibleDocumentGetLocale(AtkDocument* document) { + g_return_val_if_fail(ATK_IS_DOCUMENT(document), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(document), 0); + // The logic to resolve locale has been moved to // AtkObject::get_object_locale() virtual method. However, to avoid breaking // clients expecting the deprecated AtkDocumentIface::get_document_locale() diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.cpp index a33c59360..a12e96a8f 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceEditableText.cpp @@ -38,6 +38,7 @@ #include "Editor.h" #include "Frame.h" #include "NotImplemented.h" +#include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" using namespace WebCore; @@ -50,20 +51,29 @@ static AccessibilityObject* core(AtkEditableText* text) return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text)); } -static gboolean webkitAccessibleEditableTextSetRunAttributes(AtkEditableText*, AtkAttributeSet*, gint, gint) +static gboolean webkitAccessibleEditableTextSetRunAttributes(AtkEditableText* text, AtkAttributeSet*, gint, gint) { + g_return_val_if_fail(ATK_IS_EDITABLE_TEXT(text), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE); + notImplemented(); return FALSE; } static void webkitAccessibleEditableTextSetTextContents(AtkEditableText* text, const gchar* string) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + // FIXME: string nullcheck? core(text)->setValue(String::fromUTF8(string)); } static void webkitAccessibleEditableTextInsertText(AtkEditableText* text, const gchar* string, gint length, gint* position) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + if (!string) return; @@ -83,18 +93,27 @@ static void webkitAccessibleEditableTextInsertText(AtkEditableText* text, const *position += length; } -static void webkitAccessibleEditableTextCopyText(AtkEditableText*, gint, gint) +static void webkitAccessibleEditableTextCopyText(AtkEditableText* text, gint, gint) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + notImplemented(); } -static void webkitAccessibleEditableTextCutText(AtkEditableText*, gint, gint) +static void webkitAccessibleEditableTextCutText(AtkEditableText* text, gint, gint) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + notImplemented(); } static void webkitAccessibleEditableTextDeleteText(AtkEditableText* text, gint startPos, gint endPos) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + AccessibilityObject* coreObject = core(text); // FIXME: Not implemented in WebCore // coreObject->setSelectedTextRange(PlainTextRange(startPos, endPos - startPos)); @@ -109,8 +128,11 @@ static void webkitAccessibleEditableTextDeleteText(AtkEditableText* text, gint s document->frame()->editor().performDelete(); } -static void webkitAccessibleEditableTextPasteText(AtkEditableText*, gint) +static void webkitAccessibleEditableTextPasteText(AtkEditableText* text, gint) { + g_return_if_fail(ATK_IS_EDITABLE_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + notImplemented(); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp index 0e338d8c8..db60218df 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceHypertext.cpp @@ -23,6 +23,7 @@ #if HAVE(ACCESSIBILITY) #include "AccessibilityObject.h" +#include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" using namespace WebCore; @@ -37,13 +38,16 @@ static AccessibilityObject* core(AtkHypertext* hypertext) static AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, gint index) { - AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); + g_return_val_if_fail(ATK_HYPERTEXT(hypertext), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(hypertext), 0); + + const AccessibilityObject::AccessibilityChildrenVector& children = core(hypertext)->children(); if (index < 0 || static_cast<unsigned>(index) >= children.size()) return 0; gint currentLink = -1; - for (unsigned i = 0; i < children.size(); i++) { - AccessibilityObject* coreChild = children.at(i).get(); + for (const auto& child : children) { + AccessibilityObject* coreChild = child.get(); if (!coreChild->accessibilityIsIgnored()) { AtkObject* axObject = coreChild->wrapper(); if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject)) @@ -62,13 +66,13 @@ static AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, g static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext) { - AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children(); - if (!children.size()) - return 0; + g_return_val_if_fail(ATK_HYPERTEXT(hypertext), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(hypertext), 0); + const AccessibilityObject::AccessibilityChildrenVector& children = core(hypertext)->children(); gint linksFound = 0; - for (size_t i = 0; i < children.size(); i++) { - AccessibilityObject* coreChild = children.at(i).get(); + for (const auto& child : children) { + AccessibilityObject* coreChild = child.get(); if (!coreChild->accessibilityIsIgnored()) { AtkObject* axObject = coreChild->wrapper(); if (axObject && ATK_IS_HYPERLINK_IMPL(axObject)) @@ -81,6 +85,9 @@ static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext) static gint webkitAccessibleHypertextGetLinkIndex(AtkHypertext* hypertext, gint charIndex) { + g_return_val_if_fail(ATK_HYPERTEXT(hypertext), -1); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(hypertext), -1); + size_t linksCount = webkitAccessibleHypertextGetNLinks(hypertext); if (!linksCount) return -1; diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp index 6d7df1279..843969b42 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceImage.cpp @@ -51,18 +51,27 @@ static AccessibilityObject* core(AtkImage* image) static void webkitAccessibleImageGetImagePosition(AtkImage* image, gint* x, gint* y, AtkCoordType coordType) { - IntRect rect = pixelSnappedIntRect(core(image)->elementRect()); + g_return_if_fail(ATK_IMAGE(image)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(image)); + + IntRect rect = snappedIntRect(core(image)->elementRect()); contentsRelativeToAtkCoordinateType(core(image), coordType, rect, x, y); } static const gchar* webkitAccessibleImageGetImageDescription(AtkImage* image) { + g_return_val_if_fail(ATK_IMAGE(image), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(image), 0); + return cacheAndReturnAtkProperty(ATK_OBJECT(image), AtkCachedImageDescription, accessibilityDescription(core(image))); } static void webkitAccessibleImageGetImageSize(AtkImage* image, gint* width, gint* height) { - IntSize size = core(image)->pixelSnappedSize(); + g_return_if_fail(ATK_IMAGE(image)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(image)); + + IntSize size = snappedIntRect(core(image)->elementRect()).size(); if (width) *width = size.width(); diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp index 7e024b02c..0f79b0085 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceSelection.cpp @@ -37,6 +37,7 @@ #include "AccessibilityObject.h" #include "HTMLSelectElement.h" #include "RenderObject.h" +#include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" using namespace WebCore; @@ -44,7 +45,7 @@ using namespace WebCore; static AccessibilityObject* core(AtkSelection* selection) { if (!WEBKIT_IS_ACCESSIBLE(selection)) - return 0; + return nullptr; return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(selection)); } @@ -55,7 +56,7 @@ static AccessibilityObject* listObjectForSelection(AtkSelection* selection) // Only list boxes and menu lists supported so far. if (!coreSelection->isListBox() && !coreSelection->isMenuList()) - return 0; + return nullptr; // For list boxes the list object is just itself. if (coreSelection->isListBox()) @@ -64,13 +65,13 @@ static AccessibilityObject* listObjectForSelection(AtkSelection* selection) // For menu lists we need to return the first accessible child, // with role MenuListPopupRole, since that's the one holding the list // of items with role MenuListOptionRole. - AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children(); + const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children(); if (!children.size()) - return 0; + return nullptr; AccessibilityObject* listObject = children.at(0).get(); if (!listObject->isMenuListPopup()) - return 0; + return nullptr; return listObject; } @@ -79,18 +80,18 @@ static AccessibilityObject* optionFromList(AtkSelection* selection, gint index) { AccessibilityObject* coreSelection = core(selection); if (!coreSelection || index < 0) - return 0; + return nullptr; // Need to select the proper list object depending on the type. AccessibilityObject* listObject = listObjectForSelection(selection); if (!listObject) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector options = listObject->children(); + const AccessibilityObject::AccessibilityChildrenVector& options = listObject->children(); if (index < static_cast<gint>(options.size())) return options.at(index).get(); - return 0; + return nullptr; } static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint index) @@ -99,34 +100,33 @@ static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint in AccessibilityObject* coreSelection = core(selection); if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || index < 0) - return 0; + return nullptr; - AccessibilityObject::AccessibilityChildrenVector selectedItems; - if (coreSelection->isListBox()) - coreSelection->selectedChildren(selectedItems); - else if (coreSelection->isMenuList()) { + int selectedIndex = index; + if (coreSelection->isMenuList()) { RenderObject* renderer = coreSelection->renderer(); if (!renderer) - return 0; + return nullptr; - HTMLSelectElement* selectNode = toHTMLSelectElement(renderer->node()); - int selectedIndex = selectNode->selectedIndex(); - const Vector<HTMLElement*> listItems = selectNode->listItems(); + HTMLSelectElement* selectNode = downcast<HTMLSelectElement>(renderer->node()); + if (!selectNode) + return nullptr; - if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size())) - return 0; + selectedIndex = selectNode->selectedIndex(); + const auto& listItems = selectNode->listItems(); - return optionFromList(selection, selectedIndex); + if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size())) + return nullptr; } - if (index < static_cast<gint>(selectedItems.size())) - return selectedItems.at(index).get(); - - return 0; + return optionFromList(selection, selectedIndex); } static gboolean webkitAccessibleSelectionAddSelection(AtkSelection* selection, gint index) { + g_return_val_if_fail(ATK_SELECTION(selection), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection) return FALSE; @@ -142,23 +142,29 @@ static gboolean webkitAccessibleSelectionAddSelection(AtkSelection* selection, g static gboolean webkitAccessibleSelectionClearSelection(AtkSelection* selection) { + g_return_val_if_fail(ATK_SELECTION(selection), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection) return FALSE; AccessibilityObject::AccessibilityChildrenVector selectedItems; - if (coreSelection->isListBox() || coreSelection->isMenuList()) { + if (is<AccessibilityListBox>(*coreSelection)) { // Set the list of selected items to an empty list; then verify that it worked. - AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); - listBox->setSelectedChildren(selectedItems); - listBox->selectedChildren(selectedItems); - return !selectedItems.size(); + auto& listBox = downcast<AccessibilityListBox>(*coreSelection); + listBox.setSelectedChildren(selectedItems); + listBox.selectedChildren(selectedItems); + return selectedItems.isEmpty(); } return FALSE; } static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, gint index) { + g_return_val_if_fail(ATK_SELECTION(selection), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), nullptr); + AccessibilityObject* option = optionFromSelection(selection, index); if (option) { AtkObject* child = option->wrapper(); @@ -166,11 +172,14 @@ static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, return child; } - return 0; + return nullptr; } static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection) { + g_return_val_if_fail(ATK_SELECTION(selection), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), 0); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection || !coreSelection->isAccessibilityRenderObject()) return 0; @@ -186,8 +195,8 @@ static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection) if (!renderer) return 0; - int selectedIndex = toHTMLSelectElement(renderer->node())->selectedIndex(); - return selectedIndex >= 0 && selectedIndex < static_cast<int>(toHTMLSelectElement(renderer->node())->listItems().size()); + int selectedIndex = downcast<HTMLSelectElement>(renderer->node())->selectedIndex(); + return selectedIndex >= 0 && selectedIndex < static_cast<int>(downcast<HTMLSelectElement>(renderer->node())->listItems().size()); } return 0; @@ -195,9 +204,12 @@ static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection) static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection, gint index) { + g_return_val_if_fail(ATK_SELECTION(selection), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection) - return 0; + return FALSE; AccessibilityObject* option = optionFromList(selection, index); if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) @@ -208,9 +220,12 @@ static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection static gboolean webkitAccessibleSelectionRemoveSelection(AtkSelection* selection, gint index) { + g_return_val_if_fail(ATK_SELECTION(selection), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection) - return 0; + return FALSE; // TODO: This is only getting called if i == 0. What is preventing the rest? AccessibilityObject* option = optionFromSelection(selection, index); @@ -224,16 +239,19 @@ static gboolean webkitAccessibleSelectionRemoveSelection(AtkSelection* selection static gboolean webkitAccessibleSelectionSelectAllSelection(AtkSelection* selection) { + g_return_val_if_fail(ATK_SELECTION(selection), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE); + AccessibilityObject* coreSelection = core(selection); if (!coreSelection || !coreSelection->isMultiSelectable()) return FALSE; - AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children(); - if (coreSelection->isListBox()) { - AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); - listBox->setSelectedChildren(children); + if (is<AccessibilityListBox>(*coreSelection)) { + const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children(); + AccessibilityListBox& listBox = downcast<AccessibilityListBox>(*coreSelection); + listBox.setSelectedChildren(children); AccessibilityObject::AccessibilityChildrenVector selectedItems; - listBox->selectedChildren(selectedItems); + listBox.selectedChildren(selectedItems); return selectedItems.size() == children.size(); } diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp index 668474575..d089a54bb 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTable.cpp @@ -40,8 +40,9 @@ #include "HTMLSelectElement.h" #include "HTMLTableCaptionElement.h" #include "HTMLTableElement.h" -#include "RenderObject.h" +#include "RenderElement.h" #include "WebKitAccessibleInterfaceText.h" +#include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" using namespace WebCore; @@ -49,7 +50,7 @@ using namespace WebCore; static AccessibilityObject* core(AtkTable* table) { if (!WEBKIT_IS_ACCESSIBLE(table)) - return 0; + return nullptr; return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(table)); } @@ -57,9 +58,9 @@ static AccessibilityObject* core(AtkTable* table) static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) { AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row); - return 0; + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).cellForColumnAndRow(column, row); + return nullptr; } static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable) @@ -78,19 +79,20 @@ static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTabl static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index) { AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { + if (is<AccessibilityTable>(*accTable)) { AccessibilityObject::AccessibilityChildrenVector allCells; - static_cast<AccessibilityTable*>(accTable)->cells(allCells); - if (0 <= index && static_cast<unsigned>(index) < allCells.size()) { - AccessibilityObject* accCell = allCells.at(index).get(); - return static_cast<AccessibilityTableCell*>(accCell); - } + downcast<AccessibilityTable>(*accTable).cells(allCells); + if (0 <= index && static_cast<unsigned>(index) < allCells.size()) + return downcast<AccessibilityTableCell>(allCells[index].get()); } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableRefAt(AtkTable* table, gint row, gint column) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityTableCell* axCell = cell(table, row, column); if (!axCell) return 0; @@ -106,16 +108,22 @@ static AtkObject* webkitAccessibleTableRefAt(AtkTable* table, gint row, gint col static gint webkitAccessibleTableGetIndexAt(AtkTable* table, gint row, gint column) { + g_return_val_if_fail(ATK_TABLE(table), -1); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), -1); + AccessibilityTableCell* axCell = cell(table, row, column); - AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table)); + AccessibilityTable* axTable = downcast<AccessibilityTable>(core(table)); return cellIndex(axCell, axTable); } static gint webkitAccessibleTableGetColumnAtIndex(AtkTable* table, gint index) { + g_return_val_if_fail(ATK_TABLE(table), -1); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), -1); + AccessibilityTableCell* axCell = cellAtIndex(table, index); if (axCell) { - pair<unsigned, unsigned> columnRange; + std::pair<unsigned, unsigned> columnRange; axCell->columnIndexRange(columnRange); return columnRange.first; } @@ -124,9 +132,12 @@ static gint webkitAccessibleTableGetColumnAtIndex(AtkTable* table, gint index) static gint webkitAccessibleTableGetRowAtIndex(AtkTable* table, gint index) { + g_return_val_if_fail(ATK_TABLE(table), -1); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), -1); + AccessibilityTableCell* axCell = cellAtIndex(table, index); if (axCell) { - pair<unsigned, unsigned> rowRange; + std::pair<unsigned, unsigned> rowRange; axCell->rowIndexRange(rowRange); return rowRange.first; } @@ -135,25 +146,34 @@ static gint webkitAccessibleTableGetRowAtIndex(AtkTable* table, gint index) static gint webkitAccessibleTableGetNColumns(AtkTable* table) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return static_cast<AccessibilityTable*>(accTable)->columnCount(); + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).columnCount(); return 0; } static gint webkitAccessibleTableGetNRows(AtkTable* table) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) - return static_cast<AccessibilityTable*>(accTable)->rowCount(); + if (is<AccessibilityTable>(*accTable)) + return downcast<AccessibilityTable>(*accTable).rowCount(); return 0; } static gint webkitAccessibleTableGetColumnExtentAt(AtkTable* table, gint row, gint column) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityTableCell* axCell = cell(table, row, column); if (axCell) { - pair<unsigned, unsigned> columnRange; + std::pair<unsigned, unsigned> columnRange; axCell->columnIndexRange(columnRange); return columnRange.second; } @@ -162,9 +182,12 @@ static gint webkitAccessibleTableGetColumnExtentAt(AtkTable* table, gint row, gi static gint webkitAccessibleTableGetRowExtentAt(AtkTable* table, gint row, gint column) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityTableCell* axCell = cell(table, row, column); if (axCell) { - pair<unsigned, unsigned> rowRange; + std::pair<unsigned, unsigned> rowRange; axCell->rowIndexRange(rowRange); return rowRange.second; } @@ -173,56 +196,66 @@ static gint webkitAccessibleTableGetRowExtentAt(AtkTable* table, gint row, gint static AtkObject* webkitAccessibleTableGetColumnHeader(AtkTable* table, gint column) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { - AccessibilityObject::AccessibilityChildrenVector allColumnHeaders; - static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders); - unsigned columnCount = allColumnHeaders.size(); - for (unsigned k = 0; k < columnCount; ++k) { - pair<unsigned, unsigned> columnRange; - AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get()); - cell->columnIndexRange(columnRange); + if (is<AccessibilityTable>(*accTable)) { + AccessibilityObject::AccessibilityChildrenVector columnHeaders; + downcast<AccessibilityTable>(*accTable).columnHeaders(columnHeaders); + + for (const auto& columnHeader : columnHeaders) { + std::pair<unsigned, unsigned> columnRange; + downcast<AccessibilityTableCell>(*columnHeader).columnIndexRange(columnRange); if (columnRange.first <= static_cast<unsigned>(column) && static_cast<unsigned>(column) < columnRange.first + columnRange.second) - return allColumnHeaders[k]->wrapper(); + return columnHeader->wrapper(); } } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableGetRowHeader(AtkTable* table, gint row) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AccessibilityObject* accTable = core(table); - if (accTable->isAccessibilityRenderObject()) { - AccessibilityObject::AccessibilityChildrenVector allRowHeaders; - static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders); - unsigned rowCount = allRowHeaders.size(); - for (unsigned k = 0; k < rowCount; ++k) { - pair<unsigned, unsigned> rowRange; - AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get()); - cell->rowIndexRange(rowRange); + if (is<AccessibilityTable>(*accTable)) { + AccessibilityObject::AccessibilityChildrenVector rowHeaders; + downcast<AccessibilityTable>(*accTable).rowHeaders(rowHeaders); + + for (const auto& rowHeader : rowHeaders) { + std::pair<unsigned, unsigned> rowRange; + downcast<AccessibilityTableCell>(*rowHeader).rowIndexRange(rowRange); if (rowRange.first <= static_cast<unsigned>(row) && static_cast<unsigned>(row) < rowRange.first + rowRange.second) - return allRowHeaders[k]->wrapper(); + return rowHeader->wrapper(); } } - return 0; + return nullptr; } static AtkObject* webkitAccessibleTableGetCaption(AtkTable* table) { + g_return_val_if_fail(ATK_TABLE(table), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), nullptr); + AccessibilityObject* accTable = core(table); if (accTable->isAccessibilityRenderObject()) { Node* node = accTable->node(); - if (node && isHTMLTableElement(node)) { - HTMLTableCaptionElement* caption = toHTMLTableElement(node)->caption(); + if (is<HTMLTableElement>(node)) { + HTMLTableCaptionElement* caption = downcast<HTMLTableElement>(*node).caption(); if (caption) - return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper(); + return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->element())->wrapper(); } } - return 0; + return nullptr; } static const gchar* webkitAccessibleTableGetColumnDescription(AtkTable* table, gint column) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AtkObject* columnHeader = atk_table_get_column_header(table, column); if (columnHeader && ATK_IS_TEXT(columnHeader)) return atk_text_get_text(ATK_TEXT(columnHeader), 0, -1); @@ -232,6 +265,9 @@ static const gchar* webkitAccessibleTableGetColumnDescription(AtkTable* table, g static const gchar* webkitAccessibleTableGetRowDescription(AtkTable* table, gint row) { + g_return_val_if_fail(ATK_TABLE(table), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0); + AtkObject* rowHeader = atk_table_get_row_header(table, row); if (rowHeader && ATK_IS_TEXT(rowHeader)) return atk_text_get_text(ATK_TEXT(rowHeader), 0, -1); diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp new file mode 100644 index 000000000..3d1e96211 --- /dev/null +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 Samsung Electronics. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "WebKitAccessibleInterfaceTableCell.h" + +#if HAVE(ACCESSIBILITY) +#if ATK_CHECK_VERSION(2,11,90) +#include "AccessibilityObject.h" +#include "AccessibilityTable.h" +#include "AccessibilityTableCell.h" +#include "WebKitAccessibleUtil.h" +#include "WebKitAccessibleWrapperAtk.h" + +using namespace WebCore; + +static GPtrArray* convertToGPtrArray(const AccessibilityObject::AccessibilityChildrenVector& children) +{ + GPtrArray* array = g_ptr_array_new(); + for (const auto& child : children) { + if (AtkObject* atkObject = child->wrapper()) + g_ptr_array_add(array, atkObject); + } + return array; +} + +static AccessibilityObject* core(AtkTableCell* cell) +{ + if (!WEBKIT_IS_ACCESSIBLE(cell)) + return nullptr; + + return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(cell)); +} + +GPtrArray* webkitAccessibleTableCellGetColumnHeaderCells(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return nullptr; + + AccessibilityObject::AccessibilityChildrenVector columnHeaders; + downcast<AccessibilityTableCell>(*axObject).columnHeaders(columnHeaders); + + return convertToGPtrArray(columnHeaders); +} + +GPtrArray* webkitAccessibleTableCellGetRowHeaderCells(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return nullptr; + + AccessibilityObject::AccessibilityChildrenVector rowHeaders; + downcast<AccessibilityTableCell>(*axObject).rowHeaders(rowHeaders); + + return convertToGPtrArray(rowHeaders); +} + +gint webkitAccessibleTableCellGetColumnSpan(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), 0); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return 0; + + std::pair<unsigned, unsigned> columnRange; + downcast<AccessibilityTableCell>(*axObject).columnIndexRange(columnRange); + + return columnRange.second; +} + +gint webkitAccessibleTableCellGetRowSpan(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), 0); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return 0; + + std::pair<unsigned, unsigned> rowRange; + downcast<AccessibilityTableCell>(*axObject).rowIndexRange(rowRange); + + return rowRange.second; +} + +gboolean webkitAccessibleTableCellGetPosition(AtkTableCell* cell, gint* row, gint* column) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), false); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), false); + + AccessibilityObject* axObject = core(cell); + if (!is<AccessibilityTableCell>(axObject)) + return false; + + std::pair<unsigned, unsigned> columnRowRange; + if (row) { + downcast<AccessibilityTableCell>(*axObject).rowIndexRange(columnRowRange); + *row = columnRowRange.first; + } + if (column) { + downcast<AccessibilityTableCell>(*axObject).columnIndexRange(columnRowRange); + *column = columnRowRange.first; + } + + return true; +} + +AtkObject* webkitAccessibleTableCellGetTable(AtkTableCell* cell) +{ + g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr); + + AccessibilityObject* axObject = core(cell); + if (!axObject || !axObject->isTableCell()) + return nullptr; + + AtkObject* table = atk_object_get_parent(axObject->wrapper()); + if (!table || !ATK_IS_TABLE(table)) + return nullptr; + + return ATK_OBJECT(g_object_ref(table)); +} + +void webkitAccessibleTableCellInterfaceInit(AtkTableCellIface* iface) +{ + iface->get_column_header_cells = webkitAccessibleTableCellGetColumnHeaderCells; + iface->get_row_header_cells = webkitAccessibleTableCellGetRowHeaderCells; + iface->get_column_span = webkitAccessibleTableCellGetColumnSpan; + iface->get_row_span = webkitAccessibleTableCellGetRowSpan; + iface->get_position = webkitAccessibleTableCellGetPosition; + iface->get_table = webkitAccessibleTableCellGetTable; +} + +#endif // ATK_CHECK_VERSION(2,11,90) +#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h new file mode 100644 index 000000000..8fa4d7a8f --- /dev/null +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceTableCell.h @@ -0,0 +1,13 @@ +#ifndef WebKitAccessibleInterfaceTableCell_h +#define WebKitAccessibleInterfaceTableCell_h + +#if HAVE(ACCESSIBILITY) + +#include <atk/atk.h> + +#if ATK_CHECK_VERSION(2,11,90) +void webkitAccessibleTableCellInterfaceInit(AtkTableCellIface*); +#endif + +#endif // HAVE(ACCESSIBILITY) +#endif // WebKitAccessibleInterfaceTableCell_h diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp index 55cc1f2c4..043a69451 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceText.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2008 Nuanti Ltd. * Copyright (C) 2009 Jan Alonzo * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L. + * Copyright (C) 2013 Samsung Electronics. All rights reserved. * * Portions from Mozilla a11y, copyright as follows: * @@ -35,8 +36,9 @@ #include "AccessibilityObject.h" #include "Document.h" -#include "Font.h" +#include "FontCascade.h" #include "FrameView.h" +#include "HTMLParserIdioms.h" #include "HostWindow.h" #include "InlineTextBox.h" #include "NotImplemented.h" @@ -49,16 +51,16 @@ #include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" #include "htmlediting.h" -#include <wtf/gobject/GOwnPtr.h> +#include <wtf/glib/GUniquePtr.h> #include <wtf/text/CString.h> -#if PLATFORM(GTK) -#include <libgail-util/gail-util.h> -#include <pango/pango.h> -#endif - using namespace WebCore; +// Text attribute to expose the ARIA 'aria-invalid' attribute. Initially initialized +// to ATK_TEXT_ATTR_INVALID (which means 'invalid' text attribute'), will later on +// hold a reference to the custom registered AtkTextAttribute that we will use. +static AtkTextAttribute atkTextAttributeInvalid = ATK_TEXT_ATTR_INVALID; + static AccessibilityObject* core(AtkText* text) { if (!WEBKIT_IS_ACCESSIBLE(text)) @@ -67,141 +69,13 @@ static AccessibilityObject* core(AtkText* text) return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(text)); } -static gchar* textForRenderer(RenderObject* renderer) -{ - GString* resultText = g_string_new(0); - - if (!renderer) - return g_string_free(resultText, FALSE); - - // For RenderBlocks, piece together the text from the RenderText objects they contain. - for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) { - if (object->isBR()) { - g_string_append(resultText, "\n"); - continue; - } - - RenderText* renderText; - if (object->isText()) - renderText = toRenderText(object); - else { - // List item's markers will be treated in an special way - // later on this function, so ignore them here. - if (object->isReplaced() && !object->isListMarker()) - g_string_append_unichar(resultText, objectReplacementCharacter); - - // We need to check children, if any, to consider when - // current object is not a text object but some of its - // children are, in order not to miss those portions of - // text by not properly handling those situations - if (object->firstChild()) { - GOwnPtr<char> objectText(textForRenderer(object)); - g_string_append(resultText, objectText.get()); - } - continue; - } - - InlineTextBox* box = renderText ? renderText->firstTextBox() : 0; - while (box) { - // WebCore introduces line breaks in the text that do not reflect - // the layout you see on the screen, replace them with spaces. - String text = String(renderText->characters(), renderText->textLength()).replace("\n", " "); - g_string_append(resultText, text.substring(box->start(), box->end() - box->start() + 1).utf8().data()); - - // Newline chars in the source result in separate text boxes, so check - // before adding a newline in the layout. See bug 25415 comment #78. - // If the next sibling is a BR, we'll add the newline when we examine that child. - if (!box->nextOnLineExists() && !(object->nextSibling() && object->nextSibling()->isBR())) { - // If there was a '\n' in the last position of the - // current text box, it would have been converted to a - // space in String::replace(), so remove it first. - if (renderText->characters()[box->end()] == '\n') - g_string_erase(resultText, resultText->len - 1, -1); - - g_string_append(resultText, "\n"); - } - box = box->nextTextBox(); - } - } - - // Insert the text of the marker for list item in the right place, if present - if (renderer->isListItem()) { - String markerText = toRenderListItem(renderer)->markerTextWithSuffix(); - if (renderer->style()->direction() == LTR) - g_string_prepend(resultText, markerText.utf8().data()); - else - g_string_append(resultText, markerText.utf8().data()); - } - - return g_string_free(resultText, FALSE); -} - -static gchar* textForObject(AccessibilityObject* coreObject) -{ - GString* str = g_string_new(0); - - // For text controls, we can get the text line by line. - if (coreObject->isTextControl()) { - unsigned textLength = coreObject->textLength(); - int lineNumber = 0; - PlainTextRange range = coreObject->doAXRangeForLine(lineNumber); - while (range.length) { - // When a line of text wraps in a text area, the final space is removed. - if (range.start + range.length < textLength) - range.length -= 1; - String lineText = coreObject->doAXStringForRange(range); - g_string_append(str, lineText.utf8().data()); - g_string_append(str, "\n"); - range = coreObject->doAXRangeForLine(++lineNumber); - } - } else if (coreObject->isAccessibilityRenderObject()) { - GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer())); - g_string_append(str, rendererText.get()); - } - - return g_string_free(str, FALSE); -} - -static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset); - -#if PLATFORM(GTK) -static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject) -{ - GailTextUtil* gailTextUtil = gail_text_util_new(); - GOwnPtr<char> text(webkitAccessibleTextGetText(textObject, 0, -1)); - gail_text_util_text_setup(gailTextUtil, text.get()); - return gailTextUtil; -} - -static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) -{ - AccessibilityObject* coreObject = core(textObject); - - Document* document = coreObject->document(); - if (!document) - return 0; - - HostWindow* hostWindow = document->view()->hostWindow(); - if (!hostWindow) - return 0; - PlatformPageClient webView = hostWindow->platformPageClient(); - if (!webView) - return 0; - - // Create a string with the layout as it appears on the screen - GOwnPtr<char> objectText(textForObject(coreObject)); - PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), objectText.get()); - return layout; -} -#endif - static int baselinePositionForRenderObject(RenderObject* renderObject) { // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was // removed in r70072. The implementation looks incorrect though, because this is not the // baseline of the underlying RenderObject, but of the AccessibilityRenderObject. - const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics(); - return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2; + const FontMetrics& fontMetrics = renderObject->firstLineStyle().fontMetrics(); + return fontMetrics.ascent() + (renderObject->firstLineStyle().computedLineHeight() - fontMetrics.height()) / 2; } static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object) @@ -210,21 +84,21 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit return 0; RenderObject* renderer = object->renderer(); - RenderStyle* style = renderer->style(); + RenderStyle* style = &renderer->style(); - AtkAttributeSet* result = 0; - GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); + AtkAttributeSet* result = nullptr; + GUniquePtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get()); Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor); if (bgColor.isValid()) { - buffer.set(g_strdup_printf("%i,%i,%i", bgColor.red(), bgColor.green(), bgColor.blue())); + buffer.reset(g_strdup_printf("%i,%i,%i", bgColor.red(), bgColor.green(), bgColor.blue())); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get()); } Color fgColor = style->visitedDependentColor(CSSPropertyColor); if (fgColor.isValid()) { - buffer.set(g_strdup_printf("%i,%i,%i", fgColor.red(), fgColor.green(), fgColor.blue())); + buffer.reset(g_strdup_printf("%i,%i,%i", fgColor.red(), fgColor.green(), fgColor.blue())); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get()); } @@ -246,24 +120,24 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit } if (includeRise) { - buffer.set(g_strdup_printf("%i", baselinePosition)); + buffer.reset(g_strdup_printf("%i", baselinePosition)); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get()); } if (!style->textIndent().isUndefined()) { - int indentation = valueForLength(style->textIndent(), object->size().width(), renderer->view()); - buffer.set(g_strdup_printf("%i", indentation)); + int indentation = valueForLength(style->textIndent(), object->size().width()); + buffer.reset(g_strdup_printf("%i", indentation)); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get()); } - String fontFamilyName = style->font().firstFamily(); + String fontFamilyName = style->fontCascade().firstFamily(); if (fontFamilyName.left(8) == "-webkit-") fontFamilyName = fontFamilyName.substring(8); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data()); int fontWeight = -1; - switch (style->font().weight()) { + switch (style->fontCascade().weight()) { case FontWeight100: fontWeight = 100; break; @@ -292,7 +166,7 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit fontWeight = 900; } if (fontWeight > 0) { - buffer.set(g_strdup_printf("%i", fontWeight)); + buffer.reset(g_strdup_printf("%i", fontWeight)); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get()); } @@ -318,18 +192,27 @@ static AtkAttributeSet* getAttributeSetForAccessibilityObject(const Accessibilit result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & TextDecorationUnderline) ? "single" : "none"); - result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal"); + result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->fontCascade().italic() ? "italic" : "normal"); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & TextDecorationLineThrough) ? "true" : "false"); result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false"); - result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true"); + result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->canSetValueAttribute() ? "true" : "false"); String language = object->language(); if (!language.isEmpty()) result = addToAtkAttributeSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE), language.utf8().data()); + String invalidStatus = object->invalidStatus(); + if (invalidStatus != "false") { + // Register the custom attribute for 'aria-invalid' if not done yet. + if (atkTextAttributeInvalid == ATK_TEXT_ATTR_INVALID) + atkTextAttributeInvalid = atk_text_attribute_register("invalid"); + + result = addToAtkAttributeSet(result, atk_text_attribute_get_name(atkTextAttributeInvalid), invalidStatus.utf8().data()); + } + return result; } @@ -348,7 +231,7 @@ static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, A AtkAttributeSet* currentSet = attributeSet1; AtkAttributeSet* found; - AtkAttributeSet* toDelete = 0; + AtkAttributeSet* toDelete = nullptr; while (currentSet) { found = g_slist_find_custom(attributeSet2, currentSet->data, (GCompareFunc)compareAttribute); @@ -366,6 +249,8 @@ static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* attributeSet1, A return attributeSet1; } +static gchar* webkitAccessibleTextGetText(AtkText*, gint startOffset, gint endOffset); + static guint accessibilityObjectLength(const AccessibilityObject* object) { // Non render objects are not taken into account @@ -376,7 +261,7 @@ static guint accessibilityObjectLength(const AccessibilityObject* object) // well known API to always get the text in a consistent way AtkObject* atkObj = ATK_OBJECT(object->wrapper()); if (ATK_IS_TEXT(atkObj)) { - GOwnPtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1)); + GUniquePtr<gchar> text(webkitAccessibleTextGetText(ATK_TEXT(atkObj), 0, -1)); return g_utf8_strlen(text.get(), -1); } @@ -385,9 +270,9 @@ static guint accessibilityObjectLength(const AccessibilityObject* object) // for those cases when it's needed to take it into account // separately (as in getAccessibilityObjectForOffset) RenderObject* renderer = object->renderer(); - if (renderer && renderer->isListMarker()) { - RenderListMarker* marker = toRenderListMarker(renderer); - return marker->text().length() + marker->suffix().length(); + if (is<RenderListMarker>(renderer)) { + auto& marker = downcast<RenderListMarker>(*renderer); + return marker.text().length() + marker.suffix().length(); } return 0; @@ -433,7 +318,7 @@ static const AccessibilityObject* getAccessibilityObjectForOffset(const Accessib return result; } -static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) +static AtkAttributeSet* getRunAttributesFromAccessibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) { const AccessibilityObject* child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset); if (!child) { @@ -450,7 +335,7 @@ static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const Accessibili static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords) { - GOwnPtr<char> textContent(webkitAccessibleTextGetText(text, startOffset, -1)); + GUniquePtr<char> textContent(webkitAccessibleTextGetText(text, startOffset, -1)); gint textLength = g_utf8_strlen(textContent.get(), -1); // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps. @@ -478,8 +363,8 @@ static int offsetAdjustmentForListItem(const AccessibilityObject* object) // We need to adjust the offsets for the list item marker in // Left-To-Right text, since we expose it together with the text. RenderObject* renderer = object->renderer(); - if (renderer && renderer->isListItem() && renderer->style()->direction() == LTR) - return toRenderListItem(renderer)->markerTextWithSuffix().length(); + if (is<RenderListItem>(renderer) && renderer->style().direction() == LTR) + return downcast<RenderListItem>(*renderer).markerTextWithSuffix().length(); return 0; } @@ -501,65 +386,101 @@ static int atkOffsetToWebCoreOffset(AtkText* text, int offset) return offset - offsetAdjustmentForListItem(coreObject); } +static Node* getNodeForAccessibilityObject(AccessibilityObject* coreObject) +{ + if (!coreObject->isNativeTextControl()) + return coreObject->node(); + + // For text controls, we get the first visible position on it (which will + // belong to its inner element, unreachable from the DOM) and return its + // parent node, so we have a "bounding node" for the accessibility object. + VisiblePosition positionInTextControlInnerElement = coreObject->visiblePositionForIndex(0, true); + Node* innerMostNode = positionInTextControlInnerElement.deepEquivalent().anchorNode(); + if (!innerMostNode) + return 0; + + return innerMostNode->parentNode(); +} + static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset) { - if (!coreObject->isAccessibilityRenderObject()) + // Default values, unless the contrary is proved. + startOffset = 0; + endOffset = 0; + + Node* node = getNodeForAccessibilityObject(coreObject); + if (!node) return; - // Early return if the selection doesn't affect the selected node. - if (!selectionBelongsToObject(coreObject, selection)) + if (selection.isNone()) return; - // We need to find the exact start and end positions in the - // selected node that intersects the selection, to later on get - // the right values for the effective start and end offsets. - Position nodeRangeStart; - Position nodeRangeEnd; - Node* node = coreObject->node(); - RefPtr<Range> selRange = selection.toNormalizedRange(); - - // If the selection affects the selected node and its first - // possible position is also in the selection, we must set - // nodeRangeStart to that position, otherwise to the selection's - // start position (it would belong to the node anyway). - Node* firstLeafNode = node->firstDescendant(); - if (selRange->isPointInRange(firstLeafNode, 0, IGNORE_EXCEPTION)) - nodeRangeStart = firstPositionInOrBeforeNode(firstLeafNode); - else - nodeRangeStart = selRange->startPosition(); - - // If the selection affects the selected node and its last - // possible position is also in the selection, we must set - // nodeRangeEnd to that position, otherwise to the selection's - // end position (it would belong to the node anyway). - Node* lastLeafNode = node->lastDescendant(); - if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), IGNORE_EXCEPTION)) - nodeRangeEnd = lastPositionInOrAfterNode(lastLeafNode); - else - nodeRangeEnd = selRange->endPosition(); + // We need to limit our search to positions that fall inside the domain of the current object. + Position firstValidPosition = firstPositionInOrBeforeNode(node->firstDescendant()); + Position lastValidPosition = lastPositionInOrAfterNode(node->lastDescendant()); + + // Early return with proper values if the selection falls entirely out of the object. + if (!selectionBelongsToObject(coreObject, selection)) { + startOffset = comparePositions(selection.start(), firstValidPosition) <= 0 ? 0 : accessibilityObjectLength(coreObject); + endOffset = startOffset; + return; + } + + // Find the proper range for the selection that falls inside the object. + Position nodeRangeStart = selection.start(); + if (comparePositions(nodeRangeStart, firstValidPosition) < 0) + nodeRangeStart = firstValidPosition; + + Position nodeRangeEnd = selection.end(); + if (comparePositions(nodeRangeEnd, lastValidPosition) > 0) + nodeRangeEnd = lastValidPosition; // Calculate position of the selected range inside the object. Position parentFirstPosition = firstPositionInOrBeforeNode(node); RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart); - - // Set values for start and end offsets. + // Set values for start offsets and calculate initial range length. + // These values might be adjusted later to cover special cases. startOffset = webCoreOffsetToAtkOffset(coreObject, TextIterator::rangeLength(rangeInParent.get(), true)); - RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd); - endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true); + int rangeLength = TextIterator::rangeLength(nodeRange.get(), true); + + // Special cases that are only relevant when working with *_END boundaries. + if (selection.affinity() == UPSTREAM) { + VisiblePosition visibleStart(nodeRangeStart, UPSTREAM); + VisiblePosition visibleEnd(nodeRangeEnd, UPSTREAM); + + // We need to adjust offsets when finding wrapped lines so the position at the end + // of the line is properly taking into account when calculating the offsets. + if (isEndOfLine(visibleStart) && !lineBreakExistsAtVisiblePosition(visibleStart)) { + if (isStartOfLine(visibleStart.next())) + rangeLength++; + + if (!isEndOfBlock(visibleStart)) + startOffset = std::max(startOffset - 1, 0); + } + + if (isEndOfLine(visibleEnd) && !lineBreakExistsAtVisiblePosition(visibleEnd) && !isEndOfBlock(visibleEnd)) + rangeLength--; + } + + endOffset = std::min(startOffset + rangeLength, static_cast<int>(accessibilityObjectLength(coreObject))); } static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint endOffset) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + AccessibilityObject* coreObject = core(text); - int end = endOffset; - if (endOffset == -1) { - end = coreObject->stringValue().length(); - if (!end) - end = coreObject->textUnderElement(TextUnderElementModeIncludeAllChildren).length(); +#if ENABLE(INPUT_TYPE_COLOR) + if (coreObject->roleValue() == ColorWellRole) { + int r, g, b; + coreObject->colorValue(r, g, b); + return g_strdup_printf("rgb %7.5f %7.5f %7.5f 1", r / 255., g / 255., b / 255.); } +#endif String ret; if (coreObject->isTextControl()) @@ -567,31 +488,22 @@ static gchar* webkitAccessibleTextGetText(AtkText* text, gint startOffset, gint else { ret = coreObject->stringValue(); if (!ret) - ret = coreObject->textUnderElement(TextUnderElementModeIncludeAllChildren); - } - - if (!ret.length()) { - // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs) - // In such instances, there may also be embedded objects. The object replacement character - // is something ATs want included and we have to account for the fact that it is multibyte. - GOwnPtr<char> objectText(textForObject(coreObject)); - ret = String::fromUTF8(objectText.get()); - if (!end) - end = ret.length(); + ret = coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)); } // Prefix a item number/bullet if needed + int actualEndOffset = endOffset == -1 ? ret.length() : endOffset; if (coreObject->roleValue() == ListItemRole) { RenderObject* objRenderer = coreObject->renderer(); - if (objRenderer && objRenderer->isListItem()) { - String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix(); - ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText; + if (is<RenderListItem>(objRenderer)) { + String markerText = downcast<RenderListItem>(*objRenderer).markerTextWithSuffix(); + ret = objRenderer->style().direction() == LTR ? markerText + ret : ret + markerText; if (endOffset == -1) - end += markerText.length(); + actualEndOffset = ret.length() + markerText.length(); } } - ret = ret.substring(startOffset, end - startOffset); + ret = ret.substring(startOffset, actualEndOffset - startOffset); return g_strdup(ret.utf8().data()); } @@ -601,19 +513,23 @@ enum GetTextRelativePosition { GetTextPositionAfter }; -static gchar* webkitAccessibleTextGetChar(AtkText* text, gint offset, GetTextRelativePosition textPosition, gint* startOffset, gint* endOffset) +// Convenience function to be used in early returns. +static char* emptyTextSelectionAtOffset(int offset, int* startOffset, int* endOffset) { - AccessibilityObject* coreObject = core(text); - if (!coreObject || !coreObject->isAccessibilityRenderObject()) - return g_strdup(""); + *startOffset = offset; + *endOffset = offset; + return g_strdup(""); +} +static char* webkitAccessibleTextGetChar(AtkText* text, int offset, GetTextRelativePosition textPosition, int* startOffset, int* endOffset) +{ int actualOffset = offset; if (textPosition == GetTextPositionBefore) actualOffset--; else if (textPosition == GetTextPositionAfter) actualOffset++; - GOwnPtr<char> textData(webkitAccessibleTextGetText(text, 0, -1)); + GUniquePtr<char> textData(webkitAccessibleTextGetText(text, 0, -1)); int textLength = g_utf8_strlen(textData.get(), -1); *startOffset = std::max(0, actualOffset); @@ -628,65 +544,481 @@ static gchar* webkitAccessibleTextGetChar(AtkText* text, gint offset, GetTextRel return g_utf8_substring(textData.get(), *startOffset, *endOffset); } -static gchar* webkitAccessibleTextGetTextForOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, gint* startOffset, gint* endOffset) +static VisiblePosition nextWordStartPosition(const VisiblePosition &position) { - // Make sure we always return valid valid values for offsets. - *startOffset = 0; - *endOffset = 0; + VisiblePosition positionAfterCurrentWord = nextWordPosition(position); - if (boundaryType == ATK_TEXT_BOUNDARY_CHAR) - return webkitAccessibleTextGetChar(text, offset, textPosition, startOffset, endOffset); + // In order to skip spaces when moving right, we advance one word further + // and then move one word back. This will put us at the beginning of the + // word following. + VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord); + + if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord) + positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord); + + bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(position)); + if (movingBackwardsMovedPositionToStartOfCurrentWord) + positionAfterCurrentWord = positionAfterSpacingAndFollowingWord; + + return positionAfterCurrentWord; +} + +static VisiblePosition previousWordEndPosition(const VisiblePosition &position) +{ + // We move forward and then backward to position ourselves at the beginning + // of the current word for this boundary, making the most of the semantics + // of previousWordPosition() and nextWordPosition(). + VisiblePosition positionAtStartOfCurrentWord = previousWordPosition(nextWordPosition(position)); + VisiblePosition positionAtPreviousWord = previousWordPosition(position); + + // Need to consider special cases (punctuation) when we are in the last word of a sentence. + if (isStartOfWord(position) && positionAtPreviousWord != position && positionAtPreviousWord == positionAtStartOfCurrentWord) + return nextWordPosition(positionAtStartOfCurrentWord); + + // In order to skip spaces when moving left, we advance one word backwards + // and then move one word forward. This will put us at the beginning of + // the word following. + VisiblePosition positionBeforeSpacingAndPreceedingWord = previousWordPosition(positionAtStartOfCurrentWord); + + if (positionBeforeSpacingAndPreceedingWord != positionAtStartOfCurrentWord) + positionAtStartOfCurrentWord = nextWordPosition(positionBeforeSpacingAndPreceedingWord); + + bool movingForwardMovedPositionToEndOfCurrentWord = nextWordPosition(positionAtStartOfCurrentWord) == previousWordPosition(nextWordPosition(position)); + if (movingForwardMovedPositionToEndOfCurrentWord) + positionAtStartOfCurrentWord = positionBeforeSpacingAndPreceedingWord; + + return positionAtStartOfCurrentWord; +} + +static VisibleSelection wordAtPositionForAtkBoundary(const AccessibilityObject* /*coreObject*/, const VisiblePosition& position, AtkTextBoundary boundaryType) +{ + VisiblePosition startPosition; + VisiblePosition endPosition; + + switch (boundaryType) { + case ATK_TEXT_BOUNDARY_WORD_START: + // isStartOfWord() returns true both when at the beginning of a "real" word + // as when at the beginning of a whitespace range between two "real" words, + // since that whitespace is considered a "word" as well. And in case we are + // already at the beginning of a "real" word we do not need to look backwards. + if (isStartOfWord(position) && isWhitespace(position.characterBefore())) + startPosition = position; + else + startPosition = previousWordPosition(position); + endPosition = nextWordStartPosition(startPosition); + + // We need to make sure that we look for the word in the current line when + // we are at the beginning of a new line, and not look into the previous one + // at all, which might happen when lines belong to different nodes. + if (isStartOfLine(position) && isStartOfLine(endPosition)) { + startPosition = endPosition; + endPosition = nextWordStartPosition(startPosition); + } + break; + + case ATK_TEXT_BOUNDARY_WORD_END: + startPosition = previousWordEndPosition(position); + endPosition = nextWordPosition(startPosition); + break; + + default: + ASSERT_NOT_REACHED(); + } + + VisibleSelection selectedWord(startPosition, endPosition); + + // We mark the selection as 'upstream' so we can use that information later, + // when finding the actual offsets in getSelectionOffsetsForObject(). + if (boundaryType == ATK_TEXT_BOUNDARY_WORD_END) + selectedWord.setAffinity(UPSTREAM); + + return selectedWord; +} + +static int numberOfReplacedElementsBeforeOffset(AtkText* text, unsigned offset) +{ + GUniquePtr<char> textForObject(webkitAccessibleTextGetText(text, 0, offset)); + String textBeforeOffset = String::fromUTF8(textForObject.get()); + + int count = 0; + size_t index = textBeforeOffset.find(objectReplacementCharacter, 0); + while (index < offset && index != WTF::notFound) { + index = textBeforeOffset.find(objectReplacementCharacter, index + 1); + count++; + } + return count; +} + +static char* webkitAccessibleTextWordForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset) +{ + AccessibilityObject* coreObject = core(text); + Document* document = coreObject->document(); + if (!document) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + Node* node = getNodeForAccessibilityObject(coreObject); + if (!node) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + int actualOffset = atkOffsetToWebCoreOffset(text, offset); + + // Besides of the usual conversion from ATK offsets to WebCore offsets, + // we need to consider the potential embedded objects that might have been + // inserted in the text exposed through AtkText when calculating the offset. + actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset); + + VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset); + VisibleSelection currentWord = wordAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType); + + // Take into account other relative positions, if needed, by + // calculating the new position that we would need to consider. + VisiblePosition newPosition = caretPosition; + switch (textPosition) { + case GetTextPositionAt: + break; + + case GetTextPositionBefore: + // Early return if asking for the previous word while already at the beginning. + if (isFirstVisiblePositionInNode(currentWord.visibleStart(), node)) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + if (isStartOfLine(currentWord.end())) + newPosition = currentWord.visibleStart().previous(); + else + newPosition = startOfWord(currentWord.start(), LeftWordIfOnBoundary); + break; + + case GetTextPositionAfter: + // Early return if asking for the following word while already at the end. + if (isLastVisiblePositionInNode(currentWord.visibleEnd(), node)) + return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset); + + if (isEndOfLine(currentWord.end())) + newPosition = currentWord.visibleEnd().next(); + else + newPosition = endOfWord(currentWord.end(), RightWordIfOnBoundary); + break; + + default: + ASSERT_NOT_REACHED(); + } -#if PLATFORM(GTK) - // FIXME: Get rid of the code below once every single get_text_*_offset - // function has been properly implemented without using Pango/Cairo. - GailOffsetType offsetType = GAIL_AT_OFFSET; + // Determine the relevant word we are actually interested in + // and calculate the ATK offsets for it, then return everything. + VisibleSelection selectedWord = newPosition != caretPosition ? wordAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentWord; + getSelectionOffsetsForObject(coreObject, selectedWord, *startOffset, *endOffset); + return webkitAccessibleTextGetText(text, *startOffset, *endOffset); +} + +static bool isSentenceBoundary(const VisiblePosition &pos) +{ + if (pos.isNull()) + return false; + + // It's definitely a sentence boundary if there's nothing before. + if (pos.previous().isNull()) + return true; + + // We go backwards and forward to make sure about this. + VisiblePosition startOfPreviousSentence = startOfSentence(pos); + return startOfPreviousSentence.isNotNull() && pos == endOfSentence(startOfPreviousSentence); +} + +static bool isWhiteSpaceBetweenSentences(const VisiblePosition& position) +{ + if (position.isNull()) + return false; + + if (!isWhitespace(position.characterAfter())) + return false; + + VisiblePosition startOfWhiteSpace = startOfWord(position, RightWordIfOnBoundary); + VisiblePosition endOfWhiteSpace = endOfWord(startOfWhiteSpace, RightWordIfOnBoundary); + if (!isSentenceBoundary(startOfWhiteSpace) && !isSentenceBoundary(endOfWhiteSpace)) + return false; + + return comparePositions(startOfWhiteSpace, position) <= 0 && comparePositions(endOfWhiteSpace, position) >= 0; +} + +static VisibleSelection sentenceAtPositionForAtkBoundary(const AccessibilityObject*, const VisiblePosition& position, AtkTextBoundary boundaryType) +{ + VisiblePosition startPosition; + VisiblePosition endPosition; + + bool isAtStartOfSentenceForEndBoundary = isWhiteSpaceBetweenSentences(position) || isSentenceBoundary(position); + if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_START || !isAtStartOfSentenceForEndBoundary) { + startPosition = isSentenceBoundary(position) ? position : startOfSentence(position); + // startOfSentence might stop at a linebreak in the HTML source code, + // but we don't want to stop there yet, so keep going. + while (!isSentenceBoundary(startPosition) && isHTMLLineBreak(startPosition.characterBefore())) + startPosition = startOfSentence(startPosition); + + endPosition = endOfSentence(startPosition); + } + + if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_END) { + if (isAtStartOfSentenceForEndBoundary) { + startPosition = position; + endPosition = endOfSentence(endOfWord(position, RightWordIfOnBoundary)); + } + + // startOfSentence returns a position after any white space previous to + // the sentence, so we might need to adjust that offset for this boundary. + if (isWhitespace(startPosition.characterBefore())) + startPosition = startOfWord(startPosition, LeftWordIfOnBoundary); + + // endOfSentence returns a position after any white space after the + // sentence, so we might need to adjust that offset for this boundary. + if (isWhitespace(endPosition.characterBefore())) + endPosition = startOfWord(endPosition, LeftWordIfOnBoundary); + + // Finally, do some additional adjustments that might be needed if + // positions are at the start or the end of a line. + if (isStartOfLine(startPosition) && !isStartOfBlock(startPosition)) + startPosition = startPosition.previous(); + if (isStartOfLine(endPosition) && !isStartOfBlock(endPosition)) + endPosition = endPosition.previous(); + } + + VisibleSelection selectedSentence(startPosition, endPosition); + + // We mark the selection as 'upstream' so we can use that information later, + // when finding the actual offsets in getSelectionOffsetsForObject(). + if (boundaryType == ATK_TEXT_BOUNDARY_SENTENCE_END) + selectedSentence.setAffinity(UPSTREAM); + + return selectedSentence; +} + +static char* webkitAccessibleTextSentenceForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset) +{ + AccessibilityObject* coreObject = core(text); + Document* document = coreObject->document(); + if (!document) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + Node* node = getNodeForAccessibilityObject(coreObject); + if (!node) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + int actualOffset = atkOffsetToWebCoreOffset(text, offset); + + // Besides of the usual conversion from ATK offsets to WebCore offsets, + // we need to consider the potential embedded objects that might have been + // inserted in the text exposed through AtkText when calculating the offset. + actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset); + + VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset); + VisibleSelection currentSentence = sentenceAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType); + + // Take into account other relative positions, if needed, by + // calculating the new position that we would need to consider. + VisiblePosition newPosition = caretPosition; switch (textPosition) { + case GetTextPositionAt: + break; + case GetTextPositionBefore: - offsetType = GAIL_BEFORE_OFFSET; + // Early return if asking for the previous sentence while already at the beginning. + if (isFirstVisiblePositionInNode(currentSentence.visibleStart(), node)) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + newPosition = currentSentence.visibleStart().previous(); + break; + + case GetTextPositionAfter: + // Early return if asking for the following word while already at the end. + if (isLastVisiblePositionInNode(currentSentence.visibleEnd(), node)) + return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset); + newPosition = currentSentence.visibleEnd().next(); + break; + + default: + ASSERT_NOT_REACHED(); + } + + // Determine the relevant sentence we are actually interested in + // and calculate the ATK offsets for it, then return everything. + VisibleSelection selectedSentence = newPosition != caretPosition ? sentenceAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentSentence; + getSelectionOffsetsForObject(coreObject, selectedSentence, *startOffset, *endOffset); + return webkitAccessibleTextGetText(text, *startOffset, *endOffset); +} + +static VisibleSelection lineAtPositionForAtkBoundary(const AccessibilityObject* coreObject, const VisiblePosition& position, AtkTextBoundary boundaryType) +{ + UNUSED_PARAM(coreObject); + VisiblePosition startPosition; + VisiblePosition endPosition; + + switch (boundaryType) { + case ATK_TEXT_BOUNDARY_LINE_START: + startPosition = isStartOfLine(position) ? position : logicalStartOfLine(position); + endPosition = logicalEndOfLine(position); + + // In addition to checking that we are not at the end of a block, we need + // to check that endPosition has not UPSTREAM affinity, since that would + // cause trouble inside of text controls (we would be advancing too much). + if (!isEndOfBlock(endPosition) && endPosition.affinity() != UPSTREAM) + endPosition = endPosition.next(); + break; + + case ATK_TEXT_BOUNDARY_LINE_END: + startPosition = isEndOfLine(position) ? position : logicalStartOfLine(position); + if (!isStartOfBlock(startPosition)) + startPosition = startPosition.previous(); + endPosition = logicalEndOfLine(position); break; + default: + ASSERT_NOT_REACHED(); + } + + VisibleSelection selectedLine(startPosition, endPosition); + + // We mark the selection as 'upstream' so we can use that information later, + // when finding the actual offsets in getSelectionOffsetsForObject(). + if (boundaryType == ATK_TEXT_BOUNDARY_LINE_END) + selectedLine.setAffinity(UPSTREAM); + + return selectedLine; +} + +static char* webkitAccessibleTextLineForBoundary(AtkText* text, int offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, int* startOffset, int* endOffset) +{ + AccessibilityObject* coreObject = core(text); + Document* document = coreObject->document(); + if (!document) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + Node* node = getNodeForAccessibilityObject(coreObject); + if (!node) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + int actualOffset = atkOffsetToWebCoreOffset(text, offset); + + // Besides the usual conversion from ATK offsets to WebCore offsets, + // we need to consider the potential embedded objects that might have been + // inserted in the text exposed through AtkText when calculating the offset. + actualOffset -= numberOfReplacedElementsBeforeOffset(text, actualOffset); + + VisiblePosition caretPosition = coreObject->visiblePositionForIndex(actualOffset); + VisibleSelection currentLine = lineAtPositionForAtkBoundary(coreObject, caretPosition, boundaryType); + + // Take into account other relative positions, if needed, by + // calculating the new position that we would need to consider. + VisiblePosition newPosition = caretPosition; + switch (textPosition) { case GetTextPositionAt: + // No need to do additional work if we are using the "at" position, we just + // explicitly list this case option to catch invalid values in the default case. + break; + + case GetTextPositionBefore: + // Early return if asking for the previous line while already at the beginning. + if (isFirstVisiblePositionInNode(currentLine.visibleStart(), node)) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + newPosition = currentLine.visibleStart().previous(); break; case GetTextPositionAfter: - offsetType = GAIL_AFTER_OFFSET; + // Early return if asking for the following word while already at the end. + if (isLastVisiblePositionInNode(currentLine.visibleEnd(), node)) + return emptyTextSelectionAtOffset(accessibilityObjectLength(coreObject), startOffset, endOffset); + newPosition = currentLine.visibleEnd().next(); break; default: ASSERT_NOT_REACHED(); } - return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), offsetType, boundaryType, offset, startOffset, endOffset); -#endif + // Determine the relevant line we are actually interested in + // and calculate the ATK offsets for it, then return everything. + VisibleSelection selectedLine = newPosition != caretPosition ? lineAtPositionForAtkBoundary(coreObject, newPosition, boundaryType) : currentLine; + getSelectionOffsetsForObject(coreObject, selectedLine, *startOffset, *endOffset); - notImplemented(); + // We might need to adjust the start or end offset to include the list item marker, + // if present, when printing the first or the last full line for a list item. + RenderObject* renderer = coreObject->renderer(); + if (renderer->isListItem()) { + // For Left-to-Right, the list item marker is at the beginning of the exposed text. + if (renderer->style().direction() == LTR && isFirstVisiblePositionInNode(selectedLine.visibleStart(), node)) + *startOffset = 0; + + // For Right-to-Left, the list item marker is at the end of the exposed text. + if (renderer->style().direction() == RTL && isLastVisiblePositionInNode(selectedLine.visibleEnd(), node)) + *endOffset = accessibilityObjectLength(coreObject); + } + + return webkitAccessibleTextGetText(text, *startOffset, *endOffset); +} + +static gchar* webkitAccessibleTextGetTextForOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, GetTextRelativePosition textPosition, gint* startOffset, gint* endOffset) +{ + AccessibilityObject* coreObject = core(text); + if (!coreObject || !coreObject->isAccessibilityRenderObject()) + return emptyTextSelectionAtOffset(0, startOffset, endOffset); + + switch (boundaryType) { + case ATK_TEXT_BOUNDARY_CHAR: + return webkitAccessibleTextGetChar(text, offset, textPosition, startOffset, endOffset); + + case ATK_TEXT_BOUNDARY_WORD_START: + case ATK_TEXT_BOUNDARY_WORD_END: + return webkitAccessibleTextWordForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset); + + case ATK_TEXT_BOUNDARY_LINE_START: + case ATK_TEXT_BOUNDARY_LINE_END: + return webkitAccessibleTextLineForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset); + + case ATK_TEXT_BOUNDARY_SENTENCE_START: + case ATK_TEXT_BOUNDARY_SENTENCE_END: + return webkitAccessibleTextSentenceForBoundary(text, offset, boundaryType, textPosition, startOffset, endOffset); + + default: + ASSERT_NOT_REACHED(); + } + + // This should never be reached. return 0; } static gchar* webkitAccessibleTextGetTextAfterOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAfter, startOffset, endOffset); } static gchar* webkitAccessibleTextGetTextAtOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAt, startOffset, endOffset); } static gchar* webkitAccessibleTextGetTextBeforeOffset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionBefore, startOffset, endOffset); } -static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText*, gint) +static gunichar webkitAccessibleTextGetCharacterAtOffset(AtkText* text, gint) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + notImplemented(); return 0; } static gint webkitAccessibleTextGetCaretOffset(AtkText* text) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + // coreObject is the unignored object whose offset the caller is requesting. // focusedObject is the object with the caret. It is likely ignored -- unless it's a link. AccessibilityObject* coreObject = core(text); @@ -708,6 +1040,9 @@ static gint webkitAccessibleTextGetCaretOffset(AtkText* text) static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + AccessibilityObject* coreObject = core(text); AtkAttributeSet* result; @@ -720,7 +1055,7 @@ static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint if (offset == -1) offset = atk_text_get_caret_offset(text); - result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset); + result = getRunAttributesFromAccessibilityObject(coreObject, offset, startOffset, endOffset); if (*startOffset < 0) { *startOffset = offset; @@ -732,6 +1067,9 @@ static AtkAttributeSet* webkitAccessibleTextGetRunAttributes(AtkText* text, gint static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + AccessibilityObject* coreObject = core(text); if (!coreObject || !coreObject->isAccessibilityRenderObject()) return 0; @@ -741,6 +1079,9 @@ static AtkAttributeSet* webkitAccessibleTextGetDefaultAttributes(AtkText* text) static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords) { + g_return_if_fail(ATK_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + IntRect extents = textExtents(text, offset, 1, coords); *x = extents.x(); *y = extents.y(); @@ -750,6 +1091,9 @@ static void webkitAccessibleTextGetCharacterExtents(AtkText* text, gint offset, static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect) { + g_return_if_fail(ATK_TEXT(text)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text)); + IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords); rect->x = extents.x(); rect->y = extents.y(); @@ -759,11 +1103,17 @@ static void webkitAccessibleTextGetRangeExtents(AtkText* text, gint startOffset, static gint webkitAccessibleTextGetCharacterCount(AtkText* text) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + return accessibilityObjectLength(core(text)); } static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, AtkCoordType) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + // FIXME: Use the AtkCoordType // TODO: Is it correct to ignore range.length? IntPoint pos(x, y); @@ -773,6 +1123,9 @@ static gint webkitAccessibleTextGetOffsetAtPoint(AtkText* text, gint x, gint y, static gint webkitAccessibleTextGetNSelections(AtkText* text) { + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); + AccessibilityObject* coreObject = core(text); VisibleSelection selection = coreObject->selection(); @@ -791,8 +1144,8 @@ static gint webkitAccessibleTextGetNSelections(AtkText* text) static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset) { - // Default values, unless the contrary is proved - *startOffset = *endOffset = 0; + g_return_val_if_fail(ATK_TEXT(text), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), 0); // WebCore does not support multiple selection, so anything but 0 does not make sense for now. if (selectionNum) @@ -811,14 +1164,20 @@ static gchar* webkitAccessibleTextGetSelection(AtkText* text, gint selectionNum, return webkitAccessibleTextGetText(text, *startOffset, *endOffset); } -static gboolean webkitAccessibleTextAddSelection(AtkText*, gint, gint) +static gboolean webkitAccessibleTextAddSelection(AtkText* text, gint, gint) { + g_return_val_if_fail(ATK_TEXT(text), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE); + notImplemented(); return FALSE; } static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset) { + g_return_val_if_fail(ATK_TEXT(text), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE); + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. if (selectionNum) return FALSE; @@ -855,6 +1214,9 @@ static gboolean webkitAccessibleTextSetSelection(AtkText* text, gint selectionNu static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectionNum) { + g_return_val_if_fail(ATK_TEXT(text), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(text), FALSE); + // WebCore does not support multiple selection, so anything but 0 does not make sense for now. if (selectionNum) return FALSE; @@ -871,28 +1233,52 @@ static gboolean webkitAccessibleTextRemoveSelection(AtkText* text, gint selectio static gboolean webkitAccessibleTextSetCaretOffset(AtkText* text, gint offset) { - AccessibilityObject* coreObject = core(text); + // Internally, setting the caret offset is equivalent to set a zero-length + // selection, so delegate in that implementation and void duplicated code. + return webkitAccessibleTextSetSelection(text, 0, offset, offset); +} - if (!coreObject->isAccessibilityRenderObject()) - return FALSE; +#if ATK_CHECK_VERSION(2, 10, 0) +static gchar* webkitAccessibleTextGetStringAtOffset(AtkText* text, gint offset, AtkTextGranularity granularity, gint* startOffset, gint* endOffset) +{ + // This new API has been designed to simplify the AtkText interface and it has been + // designed to keep exactly the same behaviour the atk_text_get_text_at_text() for + // ATK_TEXT_BOUNDARY_*_START boundaries, so for now we just need to translate the + // granularity to the right old boundary and reuse the code for the old API. + // However, this should be simplified later on (and a lot of code removed) once + // WebKitGTK+ depends on ATK >= 2.9.4 *and* can safely assume that a version of + // AT-SPI2 new enough not to include the old APIs is being used. But until then, + // we will have to live with both the old and new APIs implemented here. + AtkTextBoundary boundaryType = ATK_TEXT_BOUNDARY_CHAR; + switch (granularity) { + case ATK_TEXT_GRANULARITY_CHAR: + break; - // We need to adjust the offsets for the list item marker. - int offsetAdjustment = offsetAdjustmentForListItem(coreObject); - if (offsetAdjustment) { - if (offset < offsetAdjustment) - return FALSE; + case ATK_TEXT_GRANULARITY_WORD: + boundaryType = ATK_TEXT_BOUNDARY_WORD_START; + break; - offset = atkOffsetToWebCoreOffset(text, offset); - } + case ATK_TEXT_GRANULARITY_SENTENCE: + boundaryType = ATK_TEXT_BOUNDARY_SENTENCE_START; + break; - PlainTextRange textRange(offset, 0); - VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange); - if (range.isNull()) - return FALSE; + case ATK_TEXT_GRANULARITY_LINE: + boundaryType = ATK_TEXT_BOUNDARY_LINE_START; + break; - coreObject->setSelectedVisiblePositionRange(range); - return TRUE; + case ATK_TEXT_GRANULARITY_PARAGRAPH: + // FIXME: This has not been a need with the old AtkText API, which means ATs won't + // need it yet for some time, so we can skip it for now. + notImplemented(); + return g_strdup(""); + + default: + ASSERT_NOT_REACHED(); + } + + return webkitAccessibleTextGetTextForOffset(text, offset, boundaryType, GetTextPositionAt, startOffset, endOffset); } +#endif void webkitAccessibleTextInterfaceInit(AtkTextIface* iface) { @@ -914,6 +1300,10 @@ void webkitAccessibleTextInterfaceInit(AtkTextIface* iface) iface->remove_selection = webkitAccessibleTextRemoveSelection; iface->set_selection = webkitAccessibleTextSetSelection; iface->set_caret_offset = webkitAccessibleTextSetCaretOffset; + +#if ATK_CHECK_VERSION(2, 10, 0) + iface->get_string_at_offset = webkitAccessibleTextGetStringAtOffset; +#endif } #endif diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp index 6ba7424aa..ac582fa66 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleInterfaceValue.cpp @@ -24,7 +24,9 @@ #include "AccessibilityObject.h" #include "HTMLNames.h" +#include "WebKitAccessibleUtil.h" #include "WebKitAccessibleWrapperAtk.h" +#include <wtf/text/CString.h> using namespace WebCore; @@ -36,8 +38,88 @@ static AccessibilityObject* core(AtkValue* value) return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(value)); } +static bool webkitAccessibleSetNewValue(AtkValue* coreValue, const gdouble newValue) +{ + AccessibilityObject* coreObject = core(coreValue); + if (!coreObject->canSetValueAttribute()) + return FALSE; + + // Check value against range limits + double value; + value = std::max(static_cast<double>(coreObject->minValueForRange()), newValue); + value = std::min(static_cast<double>(coreObject->maxValueForRange()), newValue); + + coreObject->setValue(String::number(value)); + return TRUE; +} + +static float webkitAccessibleGetIncrementValue(AccessibilityObject* coreObject) +{ + if (!coreObject->getAttribute(HTMLNames::stepAttr).isEmpty()) + return coreObject->stepValueForRange(); + + // If 'step' attribute is not defined, WebCore assumes a 5% of the + // range between minimum and maximum values. Implicit value of step should be one or larger. + float step = (coreObject->maxValueForRange() - coreObject->minValueForRange()) * 0.05; + return step < 1 ? 1 : step; +} + +#if ATK_CHECK_VERSION(2,11,92) +static void webkitAccessibleGetValueAndText(AtkValue* value, gdouble* currentValue, gchar** alternativeText) +{ + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return; + + if (currentValue) + *currentValue = coreObject->valueForRange(); + if (alternativeText) + *alternativeText = g_strdup_printf("%s", coreObject->valueDescription().utf8().data()); +} + +static double webkitAccessibleGetIncrement(AtkValue* value) +{ + g_return_val_if_fail(ATK_VALUE(value), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value), 0); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return 0; + + return webkitAccessibleGetIncrementValue(coreObject); +} + +static void webkitAccessibleSetValue(AtkValue* value, const gdouble newValue) +{ + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + + webkitAccessibleSetNewValue(value, newValue); +} + +static AtkRange* webkitAccessibleGetRange(AtkValue* value) +{ + g_return_val_if_fail(ATK_VALUE(value), nullptr); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value), nullptr); + + AccessibilityObject* coreObject = core(value); + if (!coreObject) + return nullptr; + + gdouble minValue = coreObject->minValueForRange(); + gdouble maxValue = coreObject->maxValueForRange(); + gchar* valueDescription = g_strdup_printf("%s", coreObject->valueDescription().utf8().data()); + return atk_range_new(minValue, maxValue, valueDescription); +} +#endif static void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue) { + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + memset(gValue, 0, sizeof(GValue)); g_value_init(gValue, G_TYPE_FLOAT); g_value_set_float(gValue, core(value)->valueForRange()); @@ -45,6 +127,9 @@ static void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue static void webkitAccessibleValueGetMaximumValue(AtkValue* value, GValue* gValue) { + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + memset(gValue, 0, sizeof(GValue)); g_value_init(gValue, G_TYPE_FLOAT); g_value_set_float(gValue, core(value)->maxValueForRange()); @@ -52,6 +137,9 @@ static void webkitAccessibleValueGetMaximumValue(AtkValue* value, GValue* gValue static void webkitAccessibleValueGetMinimumValue(AtkValue* value, GValue* gValue) { + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + memset(gValue, 0, sizeof(GValue)); g_value_init(gValue, G_TYPE_FLOAT); g_value_set_float(gValue, core(value)->minValueForRange()); @@ -59,6 +147,9 @@ static void webkitAccessibleValueGetMinimumValue(AtkValue* value, GValue* gValue static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GValue* gValue) { + g_return_val_if_fail(ATK_VALUE(value), FALSE); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value), FALSE); + double newValue; if (G_VALUE_HOLDS_DOUBLE(gValue)) newValue = g_value_get_double(gValue); @@ -77,39 +168,31 @@ static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GVal else if (G_VALUE_HOLDS_UINT(gValue)) newValue = g_value_get_uint(gValue); else - return false; - - AccessibilityObject* coreObject = core(value); - if (!coreObject->canSetValueAttribute()) return FALSE; - // Check value against range limits - newValue = std::max(static_cast<double>(coreObject->minValueForRange()), newValue); - newValue = std::min(static_cast<double>(coreObject->maxValueForRange()), newValue); - - coreObject->setValue(String::number(newValue)); - return TRUE; + return webkitAccessibleSetNewValue(value, newValue); } static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gValue) { + g_return_if_fail(ATK_VALUE(value)); + returnIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(value)); + memset(gValue, 0, sizeof(GValue)); g_value_init(gValue, G_TYPE_FLOAT); AccessibilityObject* coreObject = core(value); - if (!coreObject->getAttribute(HTMLNames::stepAttr).isEmpty()) { - g_value_set_float(gValue, coreObject->stepValueForRange()); - return; - } - - // If 'step' attribute is not defined, WebCore assumes a 5% of the - // range between minimum and maximum values, so return that. - float range = coreObject->maxValueForRange() - coreObject->minValueForRange(); - g_value_set_float(gValue, range * 0.05); + g_value_set_float(gValue, webkitAccessibleGetIncrementValue(coreObject)); } void webkitAccessibleValueInterfaceInit(AtkValueIface* iface) { +#if ATK_CHECK_VERSION(2,11,92) + iface->get_value_and_text = webkitAccessibleGetValueAndText; + iface->get_increment = webkitAccessibleGetIncrement; + iface->set_value = webkitAccessibleSetValue; + iface->get_range = webkitAccessibleGetRange; +#endif iface->get_current_value = webkitAccessibleValueGetCurrentValue; iface->get_maximum_value = webkitAccessibleValueGetMaximumValue; iface->get_minimum_value = webkitAccessibleValueGetMinimumValue; diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp index 34a71f178..94a7a2378 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.cpp @@ -94,10 +94,7 @@ String accessibilityTitle(AccessibilityObject* coreObject) Vector<AccessibilityText> textOrder; coreObject->accessibilityText(textOrder); - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - + for (const AccessibilityText& text : textOrder) { // Once we encounter visible text, or the text from our children that should be used foremost. if (text.textSource == VisibleText || text.textSource == ChildrenText) return text.text; @@ -116,6 +113,7 @@ String accessibilityTitle(AccessibilityObject* coreObject) if (text.textSource == TitleTagText && !titleTagShouldBeUsedInDescriptionField(coreObject)) return text.text; } + return String(); } @@ -124,14 +122,21 @@ String accessibilityDescription(AccessibilityObject* coreObject) Vector<AccessibilityText> textOrder; coreObject->accessibilityText(textOrder); - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - + bool visibleTextAvailable = false; + for (const AccessibilityText& text : textOrder) { if (text.textSource == AlternativeText) return text.text; - if (text.textSource == TitleTagText && titleTagShouldBeUsedInDescriptionField(coreObject)) + switch (text.textSource) { + case VisibleText: + case ChildrenText: + case LabelByElementText: + visibleTextAvailable = true; + default: + break; + } + + if (text.textSource == TitleTagText && !visibleTextAvailable) return text.text; } @@ -157,8 +162,8 @@ bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& Node* node = coreObject->node(); Node* lastDescendant = node->lastDescendant(); return (range->intersectsNode(node, IGNORE_EXCEPTION) - && (range->endContainer() != node || range->endOffset()) - && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant))); + && (&range->endContainer() != node || range->endOffset()) + && (&range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant))); } #endif diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h index 35c78f379..e2938a597 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleUtil.h @@ -33,9 +33,35 @@ class IntRect; class VisibleSelection; } +// An existing accessibility object is considered to be invalid whether it's already +// detached or if it's not but just updating the layout will detach it anyway. +#define returnIfWebKitAccessibleIsInvalid(webkitAccessible) G_STMT_START { \ + if (!webkitAccessible || webkitAccessibleIsDetached(webkitAccessible)) { \ + return; \ + } else { \ + AccessibilityObject* coreObject = webkitAccessibleGetAccessibilityObject(webkitAccessible); \ + if (!coreObject || !coreObject->document()) \ + return; \ + coreObject->updateBackingStore(); \ + if (webkitAccessibleIsDetached(webkitAccessible)) \ + return; \ + }; } G_STMT_END + +#define returnValIfWebKitAccessibleIsInvalid(webkitAccessible, val) G_STMT_START { \ + if (!webkitAccessible || webkitAccessibleIsDetached(webkitAccessible)) { \ + return (val); \ + } else { \ + AccessibilityObject* coreObject = webkitAccessibleGetAccessibilityObject(webkitAccessible); \ + if (!coreObject || !coreObject->document()) \ + return (val); \ + coreObject->updateBackingStore(); \ + if (webkitAccessibleIsDetached(webkitAccessible)) \ + return (val); \ + }; } G_STMT_END + AtkAttributeSet* addToAtkAttributeSet(AtkAttributeSet*, const char* name, const char* value); -void contentsRelativeToAtkCoordinateType(WebCore::AccessibilityObject*, AtkCoordType, WebCore::IntRect, gint* x, gint* y, gint* width = 0, gint* height = 0); +void contentsRelativeToAtkCoordinateType(WebCore::AccessibilityObject*, AtkCoordType, WebCore::IntRect, gint* x, gint* y, gint* width = nullptr, gint* height = nullptr); String accessibilityTitle(WebCore::AccessibilityObject*); diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp index 679a73915..353b0b1b8 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.cpp @@ -35,6 +35,9 @@ #if HAVE(ACCESSIBILITY) #include "AXObjectCache.h" +#include "AccessibilityList.h" +#include "AccessibilityListBoxOption.h" +#include "AccessibilityTable.h" #include "Document.h" #include "Frame.h" #include "FrameView.h" @@ -55,6 +58,7 @@ #include "WebKitAccessibleInterfaceImage.h" #include "WebKitAccessibleInterfaceSelection.h" #include "WebKitAccessibleInterfaceTable.h" +#include "WebKitAccessibleInterfaceTableCell.h" #include "WebKitAccessibleInterfaceText.h" #include "WebKitAccessibleInterfaceValue.h" #include "WebKitAccessibleUtil.h" @@ -62,10 +66,6 @@ #include <glib/gprintf.h> #include <wtf/text/CString.h> -#if PLATFORM(GTK) -#include <gtk/gtk.h> -#endif - using namespace WebCore; struct _WebKitAccessiblePrivate { @@ -91,120 +91,90 @@ struct _WebKitAccessiblePrivate { static AccessibilityObject* fallbackObject() { - // FIXME: An AXObjectCache with a Document is meaningless. - static AXObjectCache* fallbackCache = new AXObjectCache(0); - static AccessibilityObject* object = 0; - if (!object) { - // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack - object = fallbackCache->getOrCreate(ListBoxOptionRole); - object->ref(); - } - + static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef(); return object; } -static AccessibilityObject* core(WebKitAccessible* accessible) -{ - if (!accessible) - return 0; - - return accessible->m_object; -} - static AccessibilityObject* core(AtkObject* object) { if (!WEBKIT_IS_ACCESSIBLE(object)) return 0; - return core(WEBKIT_ACCESSIBLE(object)); + return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(object)); } static const gchar* webkitAccessibleGetName(AtkObject* object) { - AccessibilityObject* coreObject = core(object); - if (!coreObject->isAccessibilityRenderObject()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); - - if (coreObject->isFieldset()) { - AccessibilityObject* label = coreObject->titleUIElement(); - if (label) { - AtkObject* atkObject = label->wrapper(); - if (ATK_IS_TEXT(atkObject)) - return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); - } - } - - if (coreObject->isControl()) { - AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); - if (label) { - AtkObject* atkObject = label->wrapper(); - if (ATK_IS_TEXT(atkObject)) - return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); - } + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); - // Try text under the node. - String textUnder = coreObject->textUnderElement(); - if (textUnder.length()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, textUnder); - } + Vector<AccessibilityText> textOrder; + core(object)->accessibilityText(textOrder); - if (coreObject->isImage() || coreObject->isInputImage()) { - Node* node = coreObject->node(); - if (node && node->isHTMLElement()) { - // Get the attribute rather than altText String so as not to fall back on title. - String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr); - if (!alt.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, alt); - } - } + for (const auto& text : textOrder) { + // FIXME: This check is here because AccessibilityNodeObject::titleElementText() + // appends an empty String for the LabelByElementText source when there is a + // titleUIElement(). Removing this check makes some fieldsets lose their name. + if (text.text.isEmpty()) + continue; - // Fallback for the webArea object: just return the document's title. - if (coreObject->isWebArea()) { - Document* document = coreObject->document(); - if (document) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, document->title()); + // WebCore Accessibility should provide us with the text alternative computation + // in the order defined by that spec. So take the first thing that our platform + // does not expose via the AtkObject description. + if (text.textSource != HelpText && text.textSource != SummaryText) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, text.text); } - // Nothing worked so far, try with the AccessibilityObject's - // title() before going ahead with stringValue(). - String axTitle = accessibilityTitle(coreObject); - if (!axTitle.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, axTitle); - - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, ""); } static const gchar* webkitAccessibleGetDescription(AtkObject* object) { - AccessibilityObject* coreObject = core(object); - Node* node = 0; - if (coreObject->isAccessibilityRenderObject()) - node = coreObject->node(); - if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); - - // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. - if (coreObject->roleValue() == TableRole) { - String summary = toHTMLTableElement(node)->summary(); - if (!summary.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, summary); + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + + Vector<AccessibilityText> textOrder; + core(object)->accessibilityText(textOrder); + + bool nameTextAvailable = false; + for (const auto& text : textOrder) { + // WebCore Accessibility should provide us with the text alternative computation + // in the order defined by that spec. So take the first thing that our platform + // does not expose via the AtkObject name. + if (text.textSource == HelpText || text.textSource == SummaryText) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text); + + // If there is no other text alternative, the title tag contents will have been + // used for the AtkObject name. We don't want to duplicate it here. + if (text.textSource == TitleTagText && nameTextAvailable) + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text); + + nameTextAvailable = true; } - // The title attribute should be reliably available as the object's descripton. - // We do not want to fall back on other attributes in its absence. See bug 25524. - String title = toHTMLElement(node)->title(); - if (!title.isEmpty()) - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, title); + return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, ""); +} - return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); +static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType relationType) +{ + int count = atk_relation_set_get_n_relations(relationSet); + for (int i = 0; i < count; i++) { + AtkRelation* relation = atk_relation_set_get_relation(relationSet, i); + if (atk_relation_get_relation_type(relation) == relationType) { + atk_relation_set_remove(relationSet, relation); + break; + } + } } static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) { if (coreObject->isFieldset()) { AccessibilityObject* label = coreObject->titleUIElement(); - if (label) + if (label) { + removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } return; } @@ -220,16 +190,45 @@ static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, Atk if (coreObject->isControl()) { AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); - if (label) + if (label) { + removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); + } } else { AccessibilityObject* control = coreObject->correspondingControlForLabelElement(); if (control) atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); } + + // Check whether object supports aria-flowto + if (coreObject->supportsARIAFlowTo()) { + removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO); + AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements; + coreObject->ariaFlowToElements(ariaFlowToElements); + for (const auto& accessibilityObject : ariaFlowToElements) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, accessibilityObject->wrapper()); + } + + // Check whether object supports aria-describedby. It provides an additional information for the user. + if (coreObject->supportsARIADescribedBy()) { + removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY); + AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements; + coreObject->ariaDescribedByElements(ariaDescribedByElements); + for (const auto& accessibilityObject : ariaDescribedByElements) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, accessibilityObject->wrapper()); + } + + // Check whether object supports aria-controls. It provides information about elements that are controlled by the current object. + if (coreObject->supportsARIAControls()) { + removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLER_FOR); + AccessibilityObject::AccessibilityChildrenVector ariaControls; + coreObject->ariaControlsElements(ariaControls); + for (const auto& accessibilityObject : ariaControls) + atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLER_FOR, accessibilityObject->wrapper()); + } } -static gpointer webkitAccessibleParentClass = 0; +static gpointer webkitAccessibleParentClass = nullptr; static bool isRootObject(AccessibilityObject* coreObject) { @@ -257,18 +256,6 @@ static AtkObject* atkParentOfRootObject(AtkObject* object) Document* document = coreObject->document(); if (!document) return 0; - -#if PLATFORM(GTK) - HostWindow* hostWindow = document->view()->hostWindow(); - if (hostWindow) { - PlatformPageClient scrollView = hostWindow->platformPageClient(); - if (scrollView) { - GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView); - if (scrollViewParent) - return gtk_widget_get_accessible(scrollViewParent); - } - } -#endif // PLATFORM(GTK) } if (!coreParent) @@ -279,6 +266,9 @@ static AtkObject* atkParentOfRootObject(AtkObject* object) static AtkObject* webkitAccessibleGetParent(AtkObject* object) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + // Check first if the parent has been already set. AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->get_parent(object); if (accessibleParent) @@ -297,88 +287,34 @@ static AtkObject* webkitAccessibleGetParent(AtkObject* object) if (!coreParent) return 0; - // We don't expose table rows to Assistive technologies, but we - // need to have them anyway in the hierarchy from WebCore to - // properly perform coordinates calculations when requested. - if (coreParent->isTableRow() && coreObject->isTableCell()) - coreParent = coreParent->parentObjectUnignored(); - return coreParent->wrapper(); } -static gint getNChildrenForTable(AccessibilityObject* coreObject) -{ - AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children(); - size_t tableChildrenCount = tableChildren.size(); - size_t cellsCount = 0; - - // Look for the actual index of the cell inside the table. - for (unsigned i = 0; i < tableChildrenCount; ++i) { - if (tableChildren[i]->isTableRow()) { - AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children(); - cellsCount += rowChildren.size(); - } else - cellsCount++; - } - - return cellsCount; -} - static gint webkitAccessibleGetNChildren(AtkObject* object) { - AccessibilityObject* coreObject = core(object); + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); - // Tables should be treated in a different way because rows should - // be bypassed when exposing the accessible hierarchy. - if (coreObject->isAccessibilityTable()) - return getNChildrenForTable(coreObject); + AccessibilityObject* coreObject = core(object); return coreObject->children().size(); } -static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index) -{ - AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children(); - size_t tableChildrenCount = tableChildren.size(); - size_t cellsCount = 0; - - // Look for the actual index of the cell inside the table. - size_t current = static_cast<size_t>(index); - for (unsigned i = 0; i < tableChildrenCount; ++i) { - if (tableChildren[i]->isTableRow()) { - AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children(); - size_t rowChildrenCount = rowChildren.size(); - if (current < cellsCount + rowChildrenCount) - return rowChildren.at(current - cellsCount).get(); - cellsCount += rowChildrenCount; - } else if (cellsCount == current) - return tableChildren[i].get(); - else - cellsCount++; - } - - // Shouldn't reach if the child was found. - return 0; -} - static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + if (index < 0) return 0; AccessibilityObject* coreObject = core(object); - AccessibilityObject* coreChild = 0; - - // Tables are special cases because rows should be bypassed, but - // still taking their cells into account. - if (coreObject->isAccessibilityTable()) - coreChild = getChildForTable(coreObject, index); - else { - AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); - if (static_cast<unsigned>(index) >= children.size()) - return 0; - coreChild = children.at(index).get(); - } + AccessibilityObject* coreChild = nullptr; + + const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); + if (static_cast<size_t>(index) >= children.size()) + return 0; + coreChild = children.at(index).get(); if (!coreChild) return 0; @@ -390,43 +326,11 @@ static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) return child; } -static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject) -{ - AccessibilityObject* parent = coreObject->parentObjectUnignored(); - if (!parent) - return -1; - - AccessibilityObject* grandParent = parent->parentObjectUnignored(); - if (!grandParent) - return -1; - - AccessibilityObject::AccessibilityChildrenVector rows = grandParent->children(); - size_t rowsCount = rows.size(); - size_t previousCellsCount = 0; - - // Look for the actual index of the cell inside the table. - for (unsigned i = 0; i < rowsCount; ++i) { - if (!rows[i]->isTableRow()) - continue; - - AccessibilityObject::AccessibilityChildrenVector cells = rows[i]->children(); - size_t cellsCount = cells.size(); - - if (rows[i] == parent) { - for (unsigned j = 0; j < cellsCount; ++j) { - if (cells[j] == coreObject) - return previousCellsCount + j; - } - } - - previousCellsCount += cellsCount; - } - - return -1; -} - static gint webkitAccessibleGetIndexInParent(AtkObject* object) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), -1); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), -1); + AccessibilityObject* coreObject = core(object); AccessibilityObject* parent = coreObject->parentObjectUnignored(); @@ -445,11 +349,6 @@ static gint webkitAccessibleGetIndexInParent(AtkObject* object) } } - // Need to calculate the index of the cell in the table, as - // rows won't be exposed to assistive technologies. - if (parent && parent->isTableRow() && coreObject->isTableCell()) - return getIndexInParentForCellInRow(coreObject); - if (!parent) return -1; @@ -459,7 +358,10 @@ static gint webkitAccessibleGetIndexInParent(AtkObject* object) static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) { - AtkAttributeSet* attributeSet = 0; + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + + AtkAttributeSet* attributeSet = nullptr; #if PLATFORM(GTK) attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk"); #elif PLATFORM(EFL) @@ -473,9 +375,12 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) // Hack needed for WebKit2 tests because obtaining an element by its ID // cannot be done from the UIProcess. Assistive technologies have no need // for this information. - Node* node = coreObject->node(); - if (node && node->isElementNode()) { - String id = toElement(node)->getIdAttribute().string(); + Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement(); + if (element) { + String tagName = element->tagName(); + if (!tagName.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data()); + String id = element->getIdAttribute().string(); if (!id.isEmpty()) attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data()); } @@ -486,9 +391,16 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data()); } + if (coreObject->roleValue() == MathElementRole) { + if (coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PreSubscript)) + attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre"); + else if (coreObject->isMathMultiscriptObject(PostSuperscript) || coreObject->isMathMultiscriptObject(PostSubscript)) + attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post"); + } + // Set the 'layout-guess' attribute to help Assistive // Technologies know when an exposed table is not data table. - if (coreObject->isAccessibilityTable() && !coreObject->isDataTable()) + if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposableThroughAccessibility() && !coreObject->isDataTable()) attributeSet = addToAtkAttributeSet(attributeSet, "layout-guess", "true"); String placeholder = coreObject->placeholderValue(); @@ -496,18 +408,71 @@ static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data()); if (coreObject->ariaHasPopup()) - attributeSet = addToAtkAttributeSet(attributeSet, "aria-haspopup", "true"); + attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", "true"); + + AccessibilitySortDirection sortDirection = coreObject->sortDirection(); + if (sortDirection != SortDirectionNone) { + // WAI-ARIA spec says to translate the value as is from the attribute. + const AtomicString& sortAttribute = coreObject->getAttribute(HTMLNames::aria_sortAttr); + attributeSet = addToAtkAttributeSet(attributeSet, "sort", sortAttribute.string().utf8().data()); + } + + if (coreObject->supportsARIAPosInSet()) + attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->ariaPosInSet()).utf8().data()); + + if (coreObject->supportsARIASetSize()) + attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->ariaSetSize()).utf8().data()); + + String isReadOnly = coreObject->ariaReadOnlyValue(); + if (!isReadOnly.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data()); + + String valueDescription = coreObject->valueDescription(); + if (!valueDescription.isEmpty()) + attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data()); + + // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules: + // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so." + // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string"). + // The computedRoleString is primarily for testing, and not limited to elements with ARIA roles. + // Because the computedRoleString currently contains the ARIA role string, we'll use it for + // both purposes, as the "computed-role" object attribute for all elements which have a value + // and also via the "xml-roles" attribute for elements with ARIA, as well as for landmarks. + String roleString = coreObject->computedRoleString(); + if (!roleString.isEmpty()) { + if (coreObject->ariaRoleAttribute() != UnknownRole || coreObject->isLandmark()) + attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data()); + attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", roleString.utf8().data()); + } return attributeSet; } -static AtkRole atkRole(AccessibilityRole role) +static AtkRole atkRole(AccessibilityObject* coreObject) { + AccessibilityRole role = coreObject->roleValue(); switch (role) { + case ApplicationAlertDialogRole: + case ApplicationAlertRole: + return ATK_ROLE_ALERT; + case ApplicationDialogRole: + return ATK_ROLE_DIALOG; + case ApplicationStatusRole: + return ATK_ROLE_STATUSBAR; case UnknownRole: return ATK_ROLE_UNKNOWN; + case AudioRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_AUDIO; +#endif + case VideoRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_VIDEO; +#endif + return ATK_ROLE_EMBEDDED; case ButtonRole: return ATK_ROLE_PUSH_BUTTON; + case SwitchRole: case ToggleButtonRole: return ATK_ROLE_TOGGLE_BUTTON; case RadioButtonRole: @@ -521,11 +486,15 @@ static AtkRole atkRole(AccessibilityRole role) return ATK_ROLE_PAGE_TAB_LIST; case TextFieldRole: case TextAreaRole: + case SearchFieldRole: return ATK_ROLE_ENTRY; case StaticTextRole: return ATK_ROLE_TEXT; case OutlineRole: + case TreeRole: return ATK_ROLE_TREE; + case TreeItemRole: + return ATK_ROLE_TREE_ITEM; case MenuBarRole: return ATK_ROLE_MENU_BAR; case MenuListPopupRole: @@ -534,19 +503,21 @@ static AtkRole atkRole(AccessibilityRole role) case MenuListOptionRole: case MenuItemRole: return ATK_ROLE_MENU_ITEM; + case MenuItemCheckboxRole: + return ATK_ROLE_CHECK_MENU_ITEM; + case MenuItemRadioRole: + return ATK_ROLE_RADIO_MENU_ITEM; case ColumnRole: // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? return ATK_ROLE_UNKNOWN; // Matches Mozilla case RowRole: - // return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? - return ATK_ROLE_LIST_ITEM; // Matches Mozilla + return ATK_ROLE_TABLE_ROW; case ToolbarRole: return ATK_ROLE_TOOL_BAR; case BusyIndicatorRole: return ATK_ROLE_PROGRESS_BAR; // Is this right? case ProgressIndicatorRole: - // return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp - return ATK_ROLE_PROGRESS_BAR; + return coreObject->isMeter() ? ATK_ROLE_LEVEL_BAR : ATK_ROLE_PROGRESS_BAR; case WindowRole: return ATK_ROLE_WINDOW; case PopUpButtonRole: @@ -555,45 +526,64 @@ static AtkRole atkRole(AccessibilityRole role) case SplitGroupRole: return ATK_ROLE_SPLIT_PANE; case SplitterRole: - return ATK_ROLE_UNKNOWN; + return ATK_ROLE_SEPARATOR; case ColorWellRole: +#if PLATFORM(GTK) + // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button). + return ATK_ROLE_PUSH_BUTTON; +#elif PLATFORM(EFL) return ATK_ROLE_COLOR_CHOOSER; +#endif case ListRole: return ATK_ROLE_LIST; case ScrollBarRole: return ATK_ROLE_SCROLL_BAR; case ScrollAreaRole: return ATK_ROLE_SCROLL_PANE; - case GridRole: // Is this right? + case GridRole: case TableRole: return ATK_ROLE_TABLE; case ApplicationRole: return ATK_ROLE_APPLICATION; - case GroupRole: + case DocumentRegionRole: case RadioGroupRole: case TabPanelRole: return ATK_ROLE_PANEL; - case RowHeaderRole: // Row headers are cells after all. - case ColumnHeaderRole: // Column headers are cells after all. + case GroupRole: + return coreObject->isStyleFormatGroup() ? ATK_ROLE_SECTION : ATK_ROLE_PANEL; + case RowHeaderRole: + return ATK_ROLE_ROW_HEADER; + case ColumnHeaderRole: + return ATK_ROLE_COLUMN_HEADER; + case CaptionRole: + return ATK_ROLE_CAPTION; case CellRole: - return ATK_ROLE_TABLE_CELL; + case GridCellRole: + return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL; case LinkRole: case WebCoreLinkRole: case ImageMapLinkRole: return ATK_ROLE_LINK; case ImageMapRole: + return ATK_ROLE_IMAGE_MAP; case ImageRole: return ATK_ROLE_IMAGE; case ListMarkerRole: return ATK_ROLE_TEXT; - case WebAreaRole: - // return ATK_ROLE_HTML_CONTAINER; // Is this right? + case DocumentArticleRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_ARTICLE; +#endif + case DocumentRole: return ATK_ROLE_DOCUMENT_FRAME; + case DocumentNoteRole: + return ATK_ROLE_COMMENT; case HeadingRole: return ATK_ROLE_HEADING; case ListBoxRole: - return ATK_ROLE_LIST; + return ATK_ROLE_LIST_BOX; case ListItemRole: + return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM; case ListBoxOptionRole: return ATK_ROLE_LIST_ITEM; case ParagraphRole: @@ -601,8 +591,15 @@ static AtkRole atkRole(AccessibilityRole role) case LabelRole: case LegendRole: return ATK_ROLE_LABEL; + case BlockquoteRole: +#if ATK_CHECK_VERSION(2, 11, 3) + return ATK_ROLE_BLOCK_QUOTE; +#endif case DivRole: + case PreRole: return ATK_ROLE_SECTION; + case FooterRole: + return ATK_ROLE_FOOTER; case FormRole: return ATK_ROLE_FORM; case CanvasRole: @@ -613,6 +610,71 @@ static AtkRole atkRole(AccessibilityRole role) return ATK_ROLE_SPIN_BUTTON; case TabRole: return ATK_ROLE_PAGE_TAB; + case UserInterfaceTooltipRole: + return ATK_ROLE_TOOL_TIP; + case WebAreaRole: + return ATK_ROLE_DOCUMENT_WEB; + case LandmarkApplicationRole: + return ATK_ROLE_EMBEDDED; +#if ATK_CHECK_VERSION(2, 11, 3) + case ApplicationLogRole: + return ATK_ROLE_LOG; + case ApplicationMarqueeRole: + return ATK_ROLE_MARQUEE; + case ApplicationTimerRole: + return ATK_ROLE_TIMER; + case DefinitionRole: + return ATK_ROLE_DEFINITION; + case DocumentMathRole: + return ATK_ROLE_MATH; + case MathElementRole: + if (coreObject->isMathRow()) + return ATK_ROLE_PANEL; + if (coreObject->isMathTable()) + return ATK_ROLE_TABLE; + if (coreObject->isMathTableRow()) + return ATK_ROLE_TABLE_ROW; + if (coreObject->isMathTableCell()) + return ATK_ROLE_TABLE_CELL; + if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript()) + return ATK_ROLE_SECTION; +#if ATK_CHECK_VERSION(2, 15, 4) + if (coreObject->isMathFraction()) + return ATK_ROLE_MATH_FRACTION; + if (coreObject->isMathSquareRoot() || coreObject->isMathRoot()) + return ATK_ROLE_MATH_ROOT; + if (coreObject->isMathScriptObject(Subscript) + || coreObject->isMathMultiscriptObject(PreSubscript) || coreObject->isMathMultiscriptObject(PostSubscript)) + return ATK_ROLE_SUBSCRIPT; + if (coreObject->isMathScriptObject(Superscript) + || coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PostSuperscript)) + return ATK_ROLE_SUPERSCRIPT; +#endif +#if ATK_CHECK_VERSION(2, 15, 2) + if (coreObject->isMathToken()) + return ATK_ROLE_STATIC; +#endif + return ATK_ROLE_UNKNOWN; + case LandmarkBannerRole: + case LandmarkComplementaryRole: + case LandmarkContentInfoRole: + case LandmarkMainRole: + case LandmarkNavigationRole: + case LandmarkSearchRole: + return ATK_ROLE_LANDMARK; +#endif +#if ATK_CHECK_VERSION(2, 11, 4) + case DescriptionListRole: + return ATK_ROLE_DESCRIPTION_LIST; + case DescriptionListTermRole: + return ATK_ROLE_DESCRIPTION_TERM; + case DescriptionListDetailRole: + return ATK_ROLE_DESCRIPTION_VALUE; +#endif +#if ATK_CHECK_VERSION(2, 15, 2) + case InlineRole: + return ATK_ROLE_STATIC; +#endif default: return ATK_ROLE_UNKNOWN; } @@ -620,16 +682,21 @@ static AtkRole atkRole(AccessibilityRole role) static AtkRole webkitAccessibleGetRole(AtkObject* object) { + // ATK_ROLE_UNKNOWN should only be applied in cases where there is a valid + // WebCore accessible object for which the platform role mapping is unknown. + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), ATK_ROLE_INVALID); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), ATK_ROLE_INVALID); + AccessibilityObject* coreObject = core(object); if (!coreObject) - return ATK_ROLE_UNKNOWN; + return ATK_ROLE_INVALID; // Note: Why doesn't WebCore have a password field for this if (coreObject->isPasswordField()) return ATK_ROLE_PASSWORD_TEXT; - return atkRole(coreObject->roleValue()); + return atkRole(coreObject); } static bool isTextWithCaret(AccessibilityObject* coreObject) @@ -645,8 +712,7 @@ static bool isTextWithCaret(AccessibilityObject* coreObject) if (!frame) return false; - Settings* settings = frame->settings(); - if (!settings || !settings->caretBrowsingEnabled()) + if (!frame->settings().caretBrowsingEnabled()) return false; // Check text objects and paragraphs only. @@ -669,16 +735,18 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta bool isListBoxOption = parent && parent->isListBox(); // Please keep the state list in alphabetical order + if (isListBoxOption && coreObject->isSelectedOptionActive()) + atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE); + +#if ATK_CHECK_VERSION(2,11,2) + if (coreObject->supportsChecked() && coreObject->canSetValueAttribute()) + atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE); +#endif + if (coreObject->isChecked()) atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); - // FIXME: isReadOnly does not seem to do the right thing for - // controls, so check explicitly for them. In addition, because - // isReadOnly is false for listBoxOptions, we need to add one - // more check so that we do not present them as being "editable". - if ((!coreObject->isReadOnly() - || (coreObject->isControl() && coreObject->canSetValueAttribute())) - && !isListBoxOption) + if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute()) atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); // FIXME: Put both ENABLED and SENSITIVE together here for now @@ -707,6 +775,14 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta if (coreObject->isIndeterminate()) atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); + if (coreObject->isCheckboxOrRadio() || coreObject->isMenuItem()) { + if (coreObject->checkboxOrRadioValue() == ButtonStateMixed) + atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); + } + + if (coreObject->invalidStatus() != "false") + atk_state_set_add_state(stateSet, ATK_STATE_INVALID_ENTRY); + if (coreObject->isMultiSelectable()) atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); @@ -715,6 +791,11 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta if (coreObject->isPressed()) atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); +#if ATK_CHECK_VERSION(2,15,3) + if (!coreObject->canSetValueAttribute() && (coreObject->supportsARIAReadOnly())) + atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY); +#endif + if (coreObject->isRequired()) atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED); @@ -761,9 +842,15 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_state_set(object); AccessibilityObject* coreObject = core(object); + // Make sure the layout is updated to really know whether the object + // is defunct or not, so we can return the proper state. + coreObject->updateBackingStore(); + if (coreObject == fallbackObject()) { atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); return stateSet; @@ -780,6 +867,9 @@ static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object) static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object) { + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_relation_set(object); AccessibilityObject* coreObject = core(object); @@ -800,18 +890,21 @@ static void webkitAccessibleInit(AtkObject* object, gpointer data) static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object) { - if (ATK_IS_DOCUMENT(object)) { - AccessibilityObject* coreObject = core(object); - if (!coreObject) - return 0; + g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0); + returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0); + AccessibilityObject* coreObject = core(object); + if (!coreObject) + return 0; + + if (ATK_IS_DOCUMENT(object)) { // TODO: Should we fall back on lang xml:lang when the following comes up empty? String language = coreObject->language(); if (!language.isEmpty()) return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language); } else if (ATK_IS_TEXT(object)) { - const gchar* locale = 0; + const gchar* locale = nullptr; AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object)); for (AtkAttributeSet* attributes = textAttributes; attributes; attributes = attributes->next) { @@ -892,6 +985,9 @@ static const GInterfaceInfo AtkInterfacesInitFunctions[] = { {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0}, +#if ATK_CHECK_VERSION(2,11,90) + {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableCellInterfaceInit), 0, 0}, +#endif {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0}, {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0}, @@ -899,43 +995,50 @@ static const GInterfaceInfo AtkInterfacesInitFunctions[] = { }; enum WAIType { - WAI_ACTION, - WAI_SELECTION, - WAI_EDITABLE_TEXT, - WAI_TEXT, - WAI_COMPONENT, - WAI_IMAGE, - WAI_TABLE, - WAI_HYPERTEXT, - WAI_HYPERLINK, - WAI_DOCUMENT, - WAI_VALUE, + WAIAction, + WAISelection, + WAIEditableText, + WAIText, + WAIComponent, + WAIImage, + WAITable, +#if ATK_CHECK_VERSION(2,11,90) + WAITableCell, +#endif + WAIHypertext, + WAIHyperlink, + WAIDocument, + WAIValue, }; static GType GetAtkInterfaceTypeFromWAIType(WAIType type) { switch (type) { - case WAI_ACTION: + case WAIAction: return ATK_TYPE_ACTION; - case WAI_SELECTION: + case WAISelection: return ATK_TYPE_SELECTION; - case WAI_EDITABLE_TEXT: + case WAIEditableText: return ATK_TYPE_EDITABLE_TEXT; - case WAI_TEXT: + case WAIText: return ATK_TYPE_TEXT; - case WAI_COMPONENT: + case WAIComponent: return ATK_TYPE_COMPONENT; - case WAI_IMAGE: + case WAIImage: return ATK_TYPE_IMAGE; - case WAI_TABLE: + case WAITable: return ATK_TYPE_TABLE; - case WAI_HYPERTEXT: +#if ATK_CHECK_VERSION(2,11,90) + case WAITableCell: + return ATK_TYPE_TABLE_CELL; +#endif + case WAIHypertext: return ATK_TYPE_HYPERTEXT; - case WAI_HYPERLINK: + case WAIHyperlink: return ATK_TYPE_HYPERLINK_IMPL; - case WAI_DOCUMENT: + case WAIDocument: return ATK_TYPE_DOCUMENT; - case WAI_VALUE: + case WAIValue: return ATK_TYPE_VALUE; } @@ -944,7 +1047,9 @@ static GType GetAtkInterfaceTypeFromWAIType(WAIType type) static bool roleIsTextType(AccessibilityRole role) { - return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole || role == ListItemRole; + return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole + || role == LinkRole || role == WebCoreLinkRole || role == ListItemRole || role == PreRole + || role == GridCellRole; } static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) @@ -952,7 +1057,7 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) guint16 interfaceMask = 0; // Component interface is always supported - interfaceMask |= 1 << WAI_COMPONENT; + interfaceMask |= 1 << WAIComponent; AccessibilityRole role = coreObject->roleValue(); @@ -962,63 +1067,72 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) // object, and only supports having one action per object), it is // better just to implement this interface for every instance of // the WebKitAccessible class and let WebCore decide what to do. - interfaceMask |= 1 << WAI_ACTION; + interfaceMask |= 1 << WAIAction; // Selection if (coreObject->isListBox() || coreObject->isMenuList()) - interfaceMask |= 1 << WAI_SELECTION; + interfaceMask |= 1 << WAISelection; // Get renderer if available. - RenderObject* renderer = 0; + RenderObject* renderer = nullptr; if (coreObject->isAccessibilityRenderObject()) renderer = coreObject->renderer(); // Hyperlink (links and embedded objects). if (coreObject->isLink() || (renderer && renderer->isReplaced())) - interfaceMask |= 1 << WAI_HYPERLINK; + interfaceMask |= 1 << WAIHyperlink; - // Text & Editable Text + // Text, Editable Text & Hypertext if (role == StaticTextRole || coreObject->isMenuListOption()) - interfaceMask |= 1 << WAI_TEXT; - else { - if (coreObject->isTextControl()) { - interfaceMask |= 1 << WAI_TEXT; - if (!coreObject->isReadOnly()) - interfaceMask |= 1 << WAI_EDITABLE_TEXT; - } else { - if (role != TableRole) { - interfaceMask |= 1 << WAI_HYPERTEXT; - if ((renderer && renderer->childrenInline()) || roleIsTextType(role)) - interfaceMask |= 1 << WAI_TEXT; - } + interfaceMask |= 1 << WAIText; + else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) { + interfaceMask |= 1 << WAIText; + if (coreObject->canSetValueAttribute()) + interfaceMask |= 1 << WAIEditableText; + } else if (!coreObject->isWebArea()) { + if (role != TableRole) { + interfaceMask |= 1 << WAIHypertext; + if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken()) + interfaceMask |= 1 << WAIText; + } - // Add the TEXT interface for list items whose - // first accessible child has a text renderer - if (role == ListItemRole) { - AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); - if (children.size()) { - AccessibilityObject* axRenderChild = children.at(0).get(); - interfaceMask |= getInterfaceMaskFromObject(axRenderChild); - } + // Add the TEXT interface for list items whose + // first accessible child has a text renderer + if (role == ListItemRole) { + const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children(); + if (children.size()) { + AccessibilityObject* axRenderChild = children.at(0).get(); + interfaceMask |= getInterfaceMaskFromObject(axRenderChild); } } } // Image if (coreObject->isImage()) - interfaceMask |= 1 << WAI_IMAGE; + interfaceMask |= 1 << WAIImage; // Table - if (role == TableRole) - interfaceMask |= 1 << WAI_TABLE; + if (role == TableRole || role == GridRole) + interfaceMask |= 1 << WAITable; + +#if ATK_CHECK_VERSION(2,11,90) + if (role == CellRole || role == ColumnHeaderRole || role == RowHeaderRole) + interfaceMask |= 1 << WAITableCell; +#endif // Document if (role == WebAreaRole) - interfaceMask |= 1 << WAI_DOCUMENT; + interfaceMask |= 1 << WAIDocument; // Value - if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole) - interfaceMask |= 1 << WAI_VALUE; + if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole || role == ProgressIndicatorRole) + interfaceMask |= 1 << WAIValue; + +#if ENABLE(INPUT_TYPE_COLOR) + // Color type. + if (role == ColorWellRole) + interfaceMask |= 1 << WAIText; +#endif return interfaceMask; } @@ -1085,8 +1199,8 @@ void webkitAccessibleDetach(WebKitAccessible* accessible) { ASSERT(accessible->m_object); - if (core(accessible)->roleValue() == WebAreaRole) - g_signal_emit_by_name(accessible, "state-change", "defunct", true); + if (accessible->m_object->roleValue() == WebAreaRole) + atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_DEFUNCT, true); // We replace the WebCore AccessibilityObject with a fallback object that // provides default implementations to avoid repetitive null-checking after @@ -1094,16 +1208,10 @@ void webkitAccessibleDetach(WebKitAccessible* accessible) accessible->m_object = fallbackObject(); } -AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible* accessible) +bool webkitAccessibleIsDetached(WebKitAccessible* accessible) { - if (!accessible->m_object) - return 0; - - RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); - if (!focusedObj) - return 0; - - return focusedObj->wrapper(); + ASSERT(accessible->m_object); + return accessible->m_object == fallbackObject(); } AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset) @@ -1134,8 +1242,10 @@ AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* r if (!firstUnignoredParent) return 0; - // Don't ignore links if the offset is being requested for a link. - if (!referenceObject->isLink() && firstUnignoredParent->isLink()) + // Don't ignore links if the offset is being requested for a link + // or if the link is a block. + if (!referenceObject->isLink() && firstUnignoredParent->isLink() + && !(firstUnignoredParent->renderer() && !firstUnignoredParent->renderer()->isInline())) firstUnignoredParent = firstUnignoredParent->parentObjectUnignored(); if (!firstUnignoredParent) return 0; @@ -1145,7 +1255,7 @@ AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* r if (referenceObject->isDescendantOfObject(firstUnignoredParent)) referenceObject = firstUnignoredParent; - Node* startNode = 0; + Node* startNode = nullptr; if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) { // We need to use the first child's node of the reference // object as the start point to calculate the caret offset @@ -1187,7 +1297,7 @@ AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* r const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value) { WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv; - CString* propertyPtr = 0; + CString* propertyPtr = nullptr; switch (property) { case AtkCachedAccessibleName: diff --git a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h index 3080aeb93..ae3a0bea0 100644 --- a/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h +++ b/Source/WebCore/accessibility/atk/WebKitAccessibleWrapperAtk.h @@ -77,7 +77,7 @@ WebCore::AccessibilityObject* webkitAccessibleGetAccessibilityObject(WebKitAcces void webkitAccessibleDetach(WebKitAccessible*); -AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible*); +bool webkitAccessibleIsDetached(WebKitAccessible*); WebCore::AccessibilityObject* objectFocusedAndCaretOffsetUnignored(WebCore::AccessibilityObject*, int& offset); diff --git a/Source/WebCore/accessibility/ios/AXObjectCacheIOS.mm b/Source/WebCore/accessibility/ios/AXObjectCacheIOS.mm deleted file mode 100644 index 479a2ca5b..000000000 --- a/Source/WebCore/accessibility/ios/AXObjectCacheIOS.mm +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2010 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 COMPUTER, 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 - * 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. - */ - -#import "config.h" -#import "AXObjectCache.h" - -#if HAVE(ACCESSIBILITY) && PLATFORM(IOS) - -#import "AccessibilityObject.h" -#import "WebAccessibilityObjectWrapperIOS.h" -#import "RenderObject.h" - -#import <wtf/PassRefPtr.h> -#import <wtf/RetainPtr.h> - -namespace WebCore { - -void AXObjectCache::detachWrapper(AccessibilityObject* obj) -{ - [obj->wrapper() detach]; - obj->setWrapper(0); -} - -void AXObjectCache::attachWrapper(AccessibilityObject* obj) -{ - RetainPtr<AccessibilityObjectWrapper> wrapper = adoptNS([[WebAccessibilityObjectWrapper alloc] initWithAccessibilityObject:obj]); - obj->setWrapper(wrapper.get()); -} - -void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) -{ - if (!obj) - return; - - NSString *notificationString = nil; - switch (notification) { - case AXActiveDescendantChanged: - case AXFocusedUIElementChanged: - [obj->wrapper() postFocusChangeNotification]; - notificationString = @"AXFocusChanged"; - break; - case AXSelectedTextChanged: - [obj->wrapper() postSelectedTextChangeNotification]; - break; - case AXLayoutComplete: - [obj->wrapper() postLayoutChangeNotification]; - break; - case AXLiveRegionChanged: - [obj->wrapper() postLiveRegionChangeNotification]; - break; - case AXChildrenChanged: - [obj->wrapper() postChildrenChangedNotification]; - break; - case AXLoadComplete: - [obj->wrapper() postLoadCompleteNotification]; - break; - case AXInvalidStatusChanged: - [obj->wrapper() postInvalidStatusChangedNotification]; - break; - case AXSelectedChildrenChanged: - case AXValueChanged: - case AXCheckedStateChanged: - default: - break; - } - - // Used by DRT to know when notifications are posted. - [obj->wrapper() accessibilityPostedNotification:notificationString]; -} - -void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) -{ -} - -void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent) -{ -} - -void AXObjectCache::handleFocusedUIElementChanged(Node*, Node* newNode) -{ - postNotification(newNode, AXFocusedUIElementChanged, true, PostAsynchronously); -} - -void AXObjectCache::handleScrolledToAnchor(const Node*) -{ -} - -} - -#endif // HAVE(ACCESSIBILITY) && PLATFORM(IOS) diff --git a/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm b/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm deleted file mode 100644 index dcde88ace..000000000 --- a/Source/WebCore/accessibility/ios/AccessibilityObjectIOS.mm +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 COMPUTER, 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 - * 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. - */ - -#import "config.h" -#import "AccessibilityObject.h" -#import "AccessibilityRenderObject.h" -#import "HTMLInputElement.h" -#import "RenderObject.h" -#import "WAKView.h" - -#if HAVE(ACCESSIBILITY) && PLATFORM(IOS) - -#import "WebAccessibilityObjectWrapperIOS.h" - -@interface WAKView (iOSAccessibility) -- (BOOL)accessibilityIsIgnored; -@end - -@implementation WAKView (iOSAccessibility) - -- (BOOL)accessibilityIsIgnored -{ - return YES; -} - -@end - -namespace WebCore { - -void AccessibilityObject::detachFromParent() -{ -} - -void AccessibilityObject::overrideAttachmentParent(AccessibilityObject*) -{ -} - -// In iPhone only code for now. It's debateable whether this is desired on all platforms. -int AccessibilityObject::accessibilityPasswordFieldLength() -{ - if (!isPasswordField()) - return 0; - RenderObject* renderObject = static_cast<AccessibilityRenderObject*>(this)->renderer(); - - if (!renderObject || !renderObject->node() || !renderObject->node()->isHTMLElement()) - return false; - - HTMLInputElement* inputElement = toHTMLInputElement(renderObject->node()); - if (!inputElement) - return false; - - return inputElement->value().length(); -} - -bool AccessibilityObject::accessibilityIgnoreAttachment() const -{ - return [[wrapper() attachmentView] accessibilityIsIgnored]; -} - -AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const -{ - return DefaultBehavior; -} - -} // WebCore - -#endif // HAVE(ACCESSIBILITY) && PLATFORM(IOS) diff --git a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.h b/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.h deleted file mode 100644 index b750201eb..000000000 --- a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2008, 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 COMPUTER, 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 - * 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. - */ - -#ifndef AccessibilityObjectWrapperIOS_h -#define AccessibilityObjectWrapperIOS_h - -#if HAVE(ACCESSIBILITY) && PLATFORM(IOS) - -#include "AXObjectCache.h" -#include "AccessibilityObject.h" -#include "WebAccessibilityObjectWrapperBase.h" - -@interface WebAccessibilityObjectWrapper : WebAccessibilityObjectWrapperBase { - // Cached data to avoid frequent re-computation. - int m_isAccessibilityElement; - uint64_t m_accessibilityTraitsFromAncestor; -} - -- (id)accessibilityHitTest:(CGPoint)point; -- (AccessibilityObjectWrapper *)accessibilityPostProcessHitTest:(CGPoint)point; -- (BOOL)accessibilityCanFuzzyHitTest; - -- (BOOL)isAccessibilityElement; -- (NSString *)accessibilityLabel; -- (CGRect)accessibilityFrame; -- (NSString *)accessibilityValue; - -- (NSInteger)accessibilityElementCount; -- (id)accessibilityElementAtIndex:(NSInteger)index; -- (NSInteger)indexOfAccessibilityElement:(id)element; - -- (BOOL)isAttachment; - -- (void)postFocusChangeNotification; -- (void)postSelectedTextChangeNotification; -- (void)postLayoutChangeNotification; -- (void)postLiveRegionChangeNotification; -- (void)postLoadCompleteNotification; -- (void)postChildrenChangedNotification; -- (void)postInvalidStatusChangedNotification; - -@end - -#endif // HAVE(ACCESSIBILITY) && PLATFORM(IOS) - -#endif // AccessibilityObjectWrapperIOS_h diff --git a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm b/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm deleted file mode 100644 index bc35baa20..000000000 --- a/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm +++ /dev/null @@ -1,2242 +0,0 @@ -/* - * Copyright (C) 2008, 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 COMPUTER, 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 - * 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. - */ - -#import "config.h" -#import "WebAccessibilityObjectWrapperIOS.h" - -#if HAVE(ACCESSIBILITY) && PLATFORM(IOS) - -#import "AccessibilityRenderObject.h" -#import "AccessibilityTable.h" -#import "AccessibilityTableCell.h" -#import "Font.h" -#import "Frame.h" -#import "FrameSelection.h" -#import "FrameView.h" -#import "HitTestResult.h" -#import "HTMLFrameOwnerElement.h" -#import "HTMLInputElement.h" -#import "HTMLNames.h" -#import "IntRect.h" -#import "IntSize.h" -#import "Range.h" -#import "RenderView.h" -#import "RuntimeApplicationChecksIOS.h" -#import "SVGNames.h" -#import "TextIterator.h" -#import "WAKScrollView.h" -#import "WAKView.h" -#import "WAKWindow.h" -#import "WebCoreThread.h" -#import "visible_units.h" - -#import <GraphicsServices/GraphicsServices.h> - -@interface NSObject (AccessibilityPrivate) -- (void)_accessibilityUnregister; -- (NSString *)accessibilityLabel; -- (NSString *)accessibilityValue; -- (BOOL)isAccessibilityElement; -- (NSInteger)accessibilityElementCount; -- (id)accessibilityElementAtIndex:(NSInteger)index; -- (NSInteger)indexOfAccessibilityElement:(id)element; -@end - -@interface WebAccessibilityObjectWrapper (AccessibilityPrivate) -- (id)_accessibilityWebDocumentView; -- (id)accessibilityContainer; -- (void)setAccessibilityLabel:(NSString *)label; -- (void)setAccessibilityValue:(NSString *)value; -- (BOOL)containsUnnaturallySegmentedChildren; -- (NSInteger)positionForTextMarker:(id)marker; -@end - -@interface WAKView (iOSAccessibility) -- (BOOL)accessibilityIsIgnored; -@end - -using namespace WebCore; -using namespace HTMLNames; - -// These are tokens accessibility uses to denote attributes. -static NSString * const UIAccessibilityTokenBlockquoteLevel = @"UIAccessibilityTokenBlockquoteLevel"; -static NSString * const UIAccessibilityTokenHeadingLevel = @"UIAccessibilityTokenHeadingLevel"; -static NSString * const UIAccessibilityTokenFontName = @"UIAccessibilityTokenFontName"; -static NSString * const UIAccessibilityTokenFontFamily = @"UIAccessibilityTokenFontFamily"; -static NSString * const UIAccessibilityTokenFontSize = @"UIAccessibilityTokenFontSize"; -static NSString * const UIAccessibilityTokenBold = @"UIAccessibilityTokenBold"; -static NSString * const UIAccessibilityTokenItalic = @"UIAccessibilityTokenItalic"; -static NSString * const UIAccessibilityTokenUnderline = @"UIAccessibilityTokenUnderline"; - -static AccessibilityObjectWrapper* AccessibilityUnignoredAncestor(AccessibilityObjectWrapper *wrapper) -{ - while (wrapper && ![wrapper isAccessibilityElement]) { - AccessibilityObject* object = [wrapper accessibilityObject]; - if (!object) - break; - - if ([wrapper isAttachment] && ![[wrapper attachmentView] accessibilityIsIgnored]) - break; - - AccessibilityObject* parentObject = object->parentObjectUnignored(); - if (!parentObject) - break; - - wrapper = parentObject->wrapper(); - } - return wrapper; -} - -#pragma mark Accessibility Text Marker - -@interface WebAccessibilityTextMarker : NSObject -{ - AXObjectCache* _cache; - TextMarkerData _textMarkerData; -} - -+ (WebAccessibilityTextMarker *)textMarkerWithVisiblePosition:(VisiblePosition&)visiblePos cache:(AXObjectCache*)cache; - -@end - -@implementation WebAccessibilityTextMarker - -- (id)initWithTextMarker:(TextMarkerData *)data cache:(AXObjectCache*)cache -{ - if (!(self = [super init])) - return nil; - - _cache = cache; - memcpy(&_textMarkerData, data, sizeof(TextMarkerData)); - return self; -} - -- (id)initWithData:(NSData *)data cache:(AXObjectCache*)cache -{ - if (!(self = [super init])) - return nil; - - _cache = cache; - [data getBytes:&_textMarkerData length:sizeof(TextMarkerData)]; - - return self; -} - -// This is needed for external clients to be able to create a text marker without having a pointer to the cache. -- (id)initWithData:(NSData *)data accessibilityObject:(AccessibilityObjectWrapper *)wrapper -{ - WebCore::AccessibilityObject* axObject = [wrapper accessibilityObject]; - if (!axObject) - return nil; - - return [self initWithData:data cache:axObject->axObjectCache()]; -} - -+ (WebAccessibilityTextMarker *)textMarkerWithVisiblePosition:(VisiblePosition&)visiblePos cache:(AXObjectCache*)cache -{ - TextMarkerData textMarkerData; - cache->textMarkerDataForVisiblePosition(textMarkerData, visiblePos); - - return [[[WebAccessibilityTextMarker alloc] initWithTextMarker:&textMarkerData cache:cache] autorelease]; -} - -- (NSData *)dataRepresentation -{ - return [NSData dataWithBytes:&_textMarkerData length:sizeof(TextMarkerData)]; -} - -- (VisiblePosition)visiblePosition -{ - return _cache->visiblePositionForTextMarkerData(_textMarkerData); -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"[AXTextMarker %p] = node: %p offset: %d", self, _textMarkerData.node, _textMarkerData.offset]; -} - -@end - -@implementation WebAccessibilityObjectWrapper - -- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject -{ - self = [super initWithAccessibilityObject:axObject]; - if (!self) - return nil; - - // Initialize to a sentinel value. - m_accessibilityTraitsFromAncestor = ULLONG_MAX; - m_isAccessibilityElement = -1; - - return self; -} - -- (void)detach -{ - // rdar://8798960 Make sure the object is gone early, so that anything _accessibilityUnregister - // does can't call back into the render tree. - m_object = 0; - - if ([self respondsToSelector:@selector(_accessibilityUnregister)]) - [self _accessibilityUnregister]; -} - -- (void)dealloc -{ - // We should have been detached before deallocated. - ASSERT(!m_object); - [super dealloc]; -} - -- (BOOL)_prepareAccessibilityCall -{ - // rdar://7980318 if we start a call, then block in WebThreadLock(), then we're dealloced on another, thread, we could - // crash, so we should retain ourself for the duration of usage here. - [[self retain] autorelease]; - - WebThreadLock(); - - // If we came back from our thread lock and we were detached, we will no longer have an m_object. - if (!m_object) - return NO; - - m_object->updateBackingStore(); - if (!m_object) - return NO; - - return YES; -} - -// These are here so that we don't have to import AXRuntime. -// The methods will be swizzled when the accessibility bundle is loaded. - -- (uint64_t)_axLinkTrait { return (1 << 0); } -- (uint64_t)_axVisitedTrait { return (1 << 1); } -- (uint64_t)_axHeaderTrait { return (1 << 2); } -- (uint64_t)_axContainedByListTrait { return (1 << 3); } -- (uint64_t)_axContainedByTableTrait { return (1 << 4); } -- (uint64_t)_axContainedByLandmarkTrait { return (1 << 5); } -- (uint64_t)_axWebContentTrait { return (1 << 6); } -- (uint64_t)_axSecureTextFieldTrait { return (1 << 7); } -- (uint64_t)_axTextEntryTrait { return (1 << 8); } -- (uint64_t)_axHasTextCursorTrait { return (1 << 9); } -- (uint64_t)_axTextOperationsAvailableTrait { return (1 << 10); } -- (uint64_t)_axImageTrait { return (1 << 11); } -- (uint64_t)_axTabButtonTrait { return (1 << 12); } -- (uint64_t)_axButtonTrait { return (1 << 13); } -- (uint64_t)_axToggleTrait { return (1 << 14); } -- (uint64_t)_axPopupButtonTrait { return (1 << 15); } -- (uint64_t)_axStaticTextTrait { return (1 << 16); } -- (uint64_t)_axAdjustableTrait { return (1 << 17); } -- (uint64_t)_axMenuItemTrait { return (1 << 18); } -- (uint64_t)_axSelectedTrait { return (1 << 19); } -- (uint64_t)_axNotEnabledTrait { return (1 << 20); } -- (uint64_t)_axRadioButtonTrait { return (1 << 21); } - -- (BOOL)accessibilityCanFuzzyHitTest -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityRole role = m_object->roleValue(); - // Elements that can be returned when performing fuzzy hit testing. - switch (role) { - case ButtonRole: - case CheckBoxRole: - case ComboBoxRole: - case DisclosureTriangleRole: - case HeadingRole: - case ImageMapLinkRole: - case ImageRole: - case LinkRole: - case ListBoxRole: - case ListBoxOptionRole: - case MenuButtonRole: - case MenuItemRole: - case PopUpButtonRole: - case RadioButtonRole: - case ScrollBarRole: - case SliderRole: - case StaticTextRole: - case TabRole: - case TextFieldRole: - return !m_object->accessibilityIsIgnored(); - default: - return false; - } -} - -- (AccessibilityObjectWrapper *)accessibilityPostProcessHitTest:(CGPoint)point -{ - UNUSED_PARAM(point); - // The UIKit accessibility wrapper will override this and perform the post process hit test. - return nil; -} - -- (id)accessibilityHitTest:(CGPoint)point -{ - if (![self _prepareAccessibilityCall]) - return nil; - - // Try a fuzzy hit test first to find an accessible element. - RefPtr<AccessibilityObject> axObject; - { - AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); - axObject = m_object->accessibilityHitTest(IntPoint(point)); - } - - if (!axObject) - return nil; - - // If this is a good accessible object to return, no extra work is required. - if ([axObject->wrapper() accessibilityCanFuzzyHitTest]) - return AccessibilityUnignoredAncestor(axObject->wrapper()); - - // Check to see if we can post-process this hit test to find a better candidate. - AccessibilityObjectWrapper* wrapper = [axObject->wrapper() accessibilityPostProcessHitTest:point]; - if (wrapper) - return AccessibilityUnignoredAncestor(wrapper); - - // Fall back to default behavior. - return AccessibilityUnignoredAncestor(axObject->wrapper()); -} - -- (NSInteger)accessibilityElementCount -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); - if ([self isAttachment]) - return [[self attachmentView] accessibilityElementCount]; - - return m_object->children().size(); -} - -- (id)accessibilityElementAtIndex:(NSInteger)index -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); - if ([self isAttachment]) - return [[self attachmentView] accessibilityElementAtIndex:index]; - - AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); - if (static_cast<unsigned>(index) >= children.size()) - return nil; - - AccessibilityObjectWrapper* wrapper = children[index]->wrapper(); - if (children[index]->isAttachment()) - return [wrapper attachmentView]; - - return wrapper; -} - -- (NSInteger)indexOfAccessibilityElement:(id)element -{ - if (![self _prepareAccessibilityCall]) - return NSNotFound; - - AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); - if ([self isAttachment]) - return [[self attachmentView] indexOfAccessibilityElement:element]; - - AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); - unsigned count = children.size(); - for (unsigned k = 0; k < count; ++k) { - AccessibilityObjectWrapper* wrapper = children[k]->wrapper(); - if (wrapper == element || (children[k]->isAttachment() && [wrapper attachmentView] == element)) - return k; - } - - return NSNotFound; -} - -- (CGPathRef)_accessibilityPath -{ - if (![self _prepareAccessibilityCall]) - return NULL; - - if (!m_object->supportsPath()) - return NULL; - - Path path = m_object->elementPath(); - if (path.isEmpty()) - return NULL; - - return [self convertPathToScreenSpace:path]; -} - -- (NSString *)accessibilityLanguage -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->language(); -} - -- (BOOL)_accessibilityIsLandmarkRole:(AccessibilityRole)role -{ - switch (role) { - case LandmarkApplicationRole: - case LandmarkBannerRole: - case LandmarkComplementaryRole: - case LandmarkContentInfoRole: - case LandmarkMainRole: - case LandmarkNavigationRole: - case LandmarkSearchRole: - return YES; - default: - return NO; - } -} - -- (AccessibilityObjectWrapper*)_accessibilityListAncestor -{ - for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { - AccessibilityRole role = parent->roleValue(); - if (role == ListRole || role == ListBoxRole) - return parent->wrapper(); - } - - return nil; -} - -- (AccessibilityObjectWrapper*)_accessibilityLandmarkAncestor -{ - for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { - if ([self _accessibilityIsLandmarkRole:parent->roleValue()]) - return parent->wrapper(); - } - - return nil; -} - -- (AccessibilityObjectWrapper*)_accessibilityTableAncestor -{ - for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { - if (parent->roleValue() == TableRole) - return parent->wrapper(); - } - - return nil; -} - -- (uint64_t)_accessibilityTraitsFromAncestors -{ - uint64_t traits = 0; - AccessibilityRole role = m_object->roleValue(); - - // Trait information also needs to be gathered from the parents above the object. - // The parentObject is needed instead of the unignoredParentObject, because a table might be ignored, but information still needs to be gathered from it. - for (AccessibilityObject* parent = m_object->parentObject(); parent != nil; parent = parent->parentObject()) { - AccessibilityRole parentRole = parent->roleValue(); - if (parentRole == WebAreaRole) - break; - - switch (parentRole) { - case LinkRole: - case WebCoreLinkRole: - traits |= [self _axLinkTrait]; - if (parent->isVisited()) - traits |= [self _axVisitedTrait]; - break; - case HeadingRole: - { - traits |= [self _axHeaderTrait]; - // If this object has the header trait, we should set the value - // to the heading level. If it was a static text element, we need to store - // the value as the label, because the heading level needs to the value. - AccessibilityObjectWrapper* wrapper = parent->wrapper(); - if (role == StaticTextRole) - [self setAccessibilityLabel:m_object->stringValue()]; - [self setAccessibilityValue:[wrapper accessibilityValue]]; - break; - } - case ListBoxRole: - case ListRole: - traits |= [self _axContainedByListTrait]; - break; - case TableRole: - traits |= [self _axContainedByTableTrait]; - break; - default: - if ([self _accessibilityIsLandmarkRole:parentRole]) - traits |= [self _axContainedByLandmarkTrait]; - break; - } - } - - return traits; -} - -- (uint64_t)accessibilityTraits -{ - if (![self _prepareAccessibilityCall]) - return 0; - - AccessibilityRole role = m_object->roleValue(); - uint64_t traits = [self _axWebContentTrait]; - switch (role) { - case LinkRole: - case WebCoreLinkRole: - traits |= [self _axLinkTrait]; - if (m_object->isVisited()) - traits |= [self _axVisitedTrait]; - break; - // TextFieldRole is intended to fall through to TextAreaRole, in order to pick up the text entry and text cursor traits. - case TextFieldRole: - if (m_object->isPasswordField()) - traits |= [self _axSecureTextFieldTrait]; - case TextAreaRole: - traits |= [self _axTextEntryTrait]; - if (m_object->isFocused()) - traits |= ([self _axHasTextCursorTrait] | [self _axTextOperationsAvailableTrait]); - break; - case ImageRole: - traits |= [self _axImageTrait]; - break; - case TabRole: - traits |= [self _axTabButtonTrait]; - break; - case ButtonRole: - traits |= [self _axButtonTrait]; - if (m_object->isPressed()) - traits |= [self _axToggleTrait]; - break; - case PopUpButtonRole: - traits |= [self _axPopupButtonTrait]; - break; - case RadioButtonRole: - traits |= [self _axRadioButtonTrait] | [self _axToggleTrait]; - break; - case CheckBoxRole: - traits |= ([self _axButtonTrait] | [self _axToggleTrait]); - break; - case HeadingRole: - traits |= [self _axHeaderTrait]; - break; - case StaticTextRole: - traits |= [self _axStaticTextTrait]; - break; - case SliderRole: - traits |= [self _axAdjustableTrait]; - break; - case MenuButtonRole: - case MenuItemRole: - traits |= [self _axMenuItemTrait]; - break; - default: - break; - } - - if (m_object->isSelected()) - traits |= [self _axSelectedTrait]; - - if (!m_object->isEnabled()) - traits |= [self _axNotEnabledTrait]; - - if (m_accessibilityTraitsFromAncestor == ULLONG_MAX) - m_accessibilityTraitsFromAncestor = [self _accessibilityTraitsFromAncestors]; - - traits |= m_accessibilityTraitsFromAncestor; - - return traits; -} - -- (BOOL)isSVGGroupElement -{ - // If an SVG group element has a title, it should be an accessible element on iOS. -#if ENABLE(SVG) - Node* node = m_object->node(); - if (node && node->hasTagName(SVGNames::gTag) && [[self accessibilityLabel] length] > 0) - return YES; -#endif - - return NO; -} - -- (BOOL)determineIsAccessibilityElement -{ - if (!m_object) - return false; - - // Honor when something explicitly makes this an element (super will contain that logic) - if ([super isAccessibilityElement]) - return YES; - - m_object->updateBackingStore(); - - switch (m_object->roleValue()) { - case TextFieldRole: - case TextAreaRole: - case ButtonRole: - case PopUpButtonRole: - case CheckBoxRole: - case RadioButtonRole: - case SliderRole: - case MenuButtonRole: - case ValueIndicatorRole: - case ImageRole: - case ProgressIndicatorRole: - case MenuItemRole: - case IncrementorRole: - case ComboBoxRole: - case DisclosureTriangleRole: - case ImageMapRole: - case ListMarkerRole: - case ListBoxOptionRole: - case TabRole: - case DocumentMathRole: - return true; - case StaticTextRole: - { - // Many text elements only contain a space. - if (![[[self accessibilityLabel] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length]) - return false; - - // Text elements that are just pieces of links or headers should not be exposed. - if ([AccessibilityUnignoredAncestor([self accessibilityContainer]) containsUnnaturallySegmentedChildren]) - return false; - return true; - } - - // Don't expose headers as elements; instead expose their children as elements, with the header trait (unless they have no children) - case HeadingRole: - if (![self accessibilityElementCount]) - return true; - return false; - - // Links can sometimes be elements (when they only contain static text or don't contain anything). - // They should not be elements when containing text and other types. - case WebCoreLinkRole: - case LinkRole: - if ([self containsUnnaturallySegmentedChildren] || ![self accessibilityElementCount]) - return true; - return false; - case GroupRole: - if ([self isSVGGroupElement]) - return true; - // All other elements are ignored on the iphone. - default: - case UnknownRole: - case TabGroupRole: - case ScrollAreaRole: - case TableRole: - case ApplicationRole: - case RadioGroupRole: - case ListRole: - case ListBoxRole: - case ScrollBarRole: - case MenuBarRole: - case MenuRole: - case ColumnRole: - case RowRole: - case ToolbarRole: - case BusyIndicatorRole: - case WindowRole: - case DrawerRole: - case SystemWideRole: - case OutlineRole: - case BrowserRole: - case SplitGroupRole: - case SplitterRole: - case ColorWellRole: - case GrowAreaRole: - case SheetRole: - case HelpTagRole: - case MatteRole: - case RulerRole: - case RulerMarkerRole: - case GridRole: - case WebAreaRole: - return false; - } -} - -- (BOOL)isAccessibilityElement -{ - if (![self _prepareAccessibilityCall]) - return NO; - - if (m_isAccessibilityElement == -1) - m_isAccessibilityElement = [self determineIsAccessibilityElement]; - - return m_isAccessibilityElement; -} - -- (BOOL)stringValueShouldBeUsedInLabel -{ - if (m_object->isTextControl()) - return NO; - if (m_object->roleValue() == PopUpButtonRole) - return NO; - if (m_object->isFileUploadButton()) - return NO; - - return YES; -} - -- (BOOL)fileUploadButtonReturnsValueInTitle -{ - return NO; -} - -static void appendStringToResult(NSMutableString *result, NSString *string) -{ - ASSERT(result); - if (![string length]) - return; - if ([result length]) - [result appendString:@", "]; - [result appendString:string]; -} - -- (CGFloat)_accessibilityMinValue -{ - return m_object->minValueForRange(); -} - -- (CGFloat)_accessibilityMaxValue -{ - return m_object->maxValueForRange(); -} - -- (NSString *)accessibilityLabel -{ - if (![self _prepareAccessibilityCall]) - return nil; - - // check if the label was overriden - NSString *label = [super accessibilityLabel]; - if (label) - return label; - - // iOS doesn't distinguish between a title and description field, - // so concatentation will yield the best result. - NSString *axTitle = [self accessibilityTitle]; - NSString *axDescription = [self accessibilityDescription]; - NSString *landmarkDescription = [self ariaLandmarkRoleDescription]; - - NSMutableString *result = [NSMutableString string]; - - appendStringToResult(result, axTitle); - appendStringToResult(result, axDescription); - if ([self stringValueShouldBeUsedInLabel]) { - NSString *valueLabel = m_object->stringValue(); - valueLabel = [valueLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - appendStringToResult(result, valueLabel); - } - appendStringToResult(result, landmarkDescription); - - return [result length] ? result : nil; -} - -- (AccessibilityTableCell*)tableCellParent -{ - // Find if this element is in a table cell. - AccessibilityObject* cell = 0; - for (cell = m_object; cell && !cell->isTableCell(); cell = cell->parentObject()) - { } - - if (!cell) - return 0; - - return static_cast<AccessibilityTableCell*>(cell); -} - -- (AccessibilityTable*)tableParent -{ - // Find if the parent table for the table cell. - AccessibilityObject* parentTable = 0; - for (parentTable = m_object; parentTable && !parentTable->isDataTable(); parentTable = parentTable->parentObject()) - { } - - if (!parentTable) - return 0; - - return static_cast<AccessibilityTable*>(parentTable); -} - -- (id)accessibilityTitleElement -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityObject* titleElement = m_object->titleUIElement(); - if (titleElement) - return titleElement->wrapper(); - - return nil; -} - -// Meant to return row or column headers (or other things as the future permits). -- (NSArray *)accessibilityHeaderElements -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityTableCell* tableCell = [self tableCellParent]; - if (!tableCell) - return nil; - - AccessibilityTable* table = [self tableParent]; - if (!table) - return nil; - - // Get the row and column range, so we can use them to find the headers. - pair<unsigned, unsigned> rowRange; - pair<unsigned, unsigned> columnRange; - tableCell->rowIndexRange(rowRange); - tableCell->columnIndexRange(columnRange); - - AccessibilityObject::AccessibilityChildrenVector rowHeaders; - AccessibilityObject::AccessibilityChildrenVector columnHeaders; - table->rowHeaders(rowHeaders); - table->columnHeaders(columnHeaders); - - NSMutableArray *headers = [NSMutableArray array]; - - unsigned columnRangeIndex = static_cast<unsigned>(columnRange.first); - if (columnRangeIndex < columnHeaders.size()) { - RefPtr<AccessibilityObject> columnHeader = columnHeaders[columnRange.first]; - AccessibilityObjectWrapper* wrapper = columnHeader->wrapper(); - if (wrapper) - [headers addObject:wrapper]; - } - - unsigned rowRangeIndex = static_cast<unsigned>(rowRange.first); - if (rowRangeIndex < rowHeaders.size()) { - RefPtr<AccessibilityObject> rowHeader = rowHeaders[rowRange.first]; - AccessibilityObjectWrapper* wrapper = rowHeader->wrapper(); - if (wrapper) - [headers addObject:wrapper]; - } - - return headers; -} - -- (id)accessibilityElementForRow:(NSInteger)row andColumn:(NSInteger)column -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityTable* table = [self tableParent]; - if (!table) - return nil; - - AccessibilityTableCell* cell = table->cellForColumnAndRow(column, row); - if (!cell) - return nil; - return cell->wrapper(); -} - -- (NSRange)accessibilityRowRange -{ - if (![self _prepareAccessibilityCall]) - return NSMakeRange(NSNotFound, 0); - - if (m_object->isRadioButton()) { - AccessibilityObject::AccessibilityChildrenVector radioButtonSiblings; - m_object->linkedUIElements(radioButtonSiblings); - if (radioButtonSiblings.size() <= 1) - return NSMakeRange(NSNotFound, 0); - - return NSMakeRange(radioButtonSiblings.find(m_object), radioButtonSiblings.size()); - } - - AccessibilityTableCell* tableCell = [self tableCellParent]; - if (!tableCell) - return NSMakeRange(NSNotFound, 0); - - pair<unsigned, unsigned> rowRange; - tableCell->rowIndexRange(rowRange); - return NSMakeRange(rowRange.first, rowRange.second); -} - -- (NSRange)accessibilityColumnRange -{ - if (![self _prepareAccessibilityCall]) - return NSMakeRange(NSNotFound, 0); - - AccessibilityTableCell* tableCell = [self tableCellParent]; - if (!tableCell) - return NSMakeRange(NSNotFound, 0); - - pair<unsigned, unsigned> columnRange; - tableCell->columnIndexRange(columnRange); - return NSMakeRange(columnRange.first, columnRange.second); -} - -- (NSString *)accessibilityPlaceholderValue -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->placeholderValue(); -} - -- (NSString *)accessibilityValue -{ - if (![self _prepareAccessibilityCall]) - return nil; - - // check if the value was overriden - NSString *value = [super accessibilityValue]; - if (value) - return value; - - if (m_object->isCheckboxOrRadio()) { - switch (m_object->checkboxOrRadioValue()) { - case ButtonStateOff: - return [NSString stringWithFormat:@"%d", 0]; - case ButtonStateOn: - return [NSString stringWithFormat:@"%d", 1]; - case ButtonStateMixed: - return [NSString stringWithFormat:@"%d", 2]; - } - ASSERT_NOT_REACHED(); - return [NSString stringWithFormat:@"%d", 0]; - } - - if (m_object->isButton() && m_object->isPressed()) - return [NSString stringWithFormat:@"%d", 1]; - - // rdar://8131388 WebKit should expose the same info as UIKit for its password fields. - if (m_object->isPasswordField()) { - int passwordLength = m_object->accessibilityPasswordFieldLength(); - NSMutableString* string = [NSMutableString string]; - for (int k = 0; k < passwordLength; ++k) - [string appendString:@"•"]; - return string; - } - - // A text control should return its text data as the axValue (per iPhone AX API). - if (![self stringValueShouldBeUsedInLabel]) - return m_object->stringValue(); - - if (m_object->isProgressIndicator() || m_object->isSlider()) { - // Prefer a valueDescription if provided by the author (through aria-valuetext). - String valueDescription = m_object->valueDescription(); - if (!valueDescription.isEmpty()) - return valueDescription; - - return [NSString stringWithFormat:@"%.2f", m_object->valueForRange()]; - } - - if (m_object->isHeading()) - return [NSString stringWithFormat:@"%d", m_object->headingLevel()]; - - return nil; -} - -- (BOOL)accessibilityIsComboBox -{ - if (![self _prepareAccessibilityCall]) - return NO; - - return m_object->roleValue() == ComboBoxRole; -} - -- (NSString *)accessibilityHint -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return [self accessibilityHelpText]; -} - -- (NSURL *)accessibilityURL -{ - if (![self _prepareAccessibilityCall]) - return nil; - - KURL url = m_object->url(); - if (url.isNull()) - return nil; - return (NSURL*)url; -} - -- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point -{ - if (!m_object) - return CGPointZero; - - CGPoint cgPoint = CGPointMake(point.x(), point.y()); - - FrameView* frameView = m_object->documentFrameView(); - if (frameView) { - WAKView* view = frameView->documentView(); - cgPoint = [view convertPoint:cgPoint toView:nil]; - } - - // we need the web document view to give us our final screen coordinates - // because that can take account of the scroller - id webDocument = [self _accessibilityWebDocumentView]; - if (webDocument) - cgPoint = [webDocument convertPoint:cgPoint toView:nil]; - - return cgPoint; -} - -- (CGRect)convertRectToScreenSpace:(IntRect &)rect -{ - if (!m_object) - return CGRectZero; - - CGSize size = CGSizeMake(rect.size().width(), rect.size().height()); - CGPoint point = CGPointMake(rect.x(), rect.y()); - - CGRect frame = CGRectMake(point.x, point.y, size.width, size.height); - - FrameView* frameView = m_object->documentFrameView(); - if (frameView) { - WAKView* view = frameView->documentView(); - frame = [view convertRect:frame toView:nil]; - } - - // we need the web document view to give us our final screen coordinates - // because that can take account of the scroller - id webDocument = [self _accessibilityWebDocumentView]; - if (webDocument) - frame = [webDocument convertRect:frame toView:nil]; - - return frame; -} - -// Used by UIKit accessibility bundle to help determine distance during a hit-test. -- (CGRect)accessibilityElementRect -{ - if (![self _prepareAccessibilityCall]) - return CGRectZero; - - LayoutRect rect = m_object->elementRect(); - return CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); -} - -// The "center point" is where VoiceOver will "press" an object. This may not be the actual -// center of the accessibilityFrame -- (CGPoint)accessibilityActivationPoint -{ - if (![self _prepareAccessibilityCall]) - return CGPointZero; - - IntRect rect = pixelSnappedIntRect(m_object->boundingBoxRect()); - CGRect cgRect = [self convertRectToScreenSpace:rect]; - return CGPointMake(CGRectGetMidX(cgRect), CGRectGetMidY(cgRect)); -} - -- (CGRect)accessibilityFrame -{ - if (![self _prepareAccessibilityCall]) - return CGRectZero; - - IntRect rect = pixelSnappedIntRect(m_object->elementRect()); - return [self convertRectToScreenSpace:rect]; -} - -// Checks whether a link contains only static text and images (and has been divided unnaturally by <spans> and other nefarious mechanisms). -- (BOOL)containsUnnaturallySegmentedChildren -{ - if (!m_object) - return NO; - - AccessibilityRole role = m_object->roleValue(); - if (role != LinkRole && role != WebCoreLinkRole) - return NO; - - AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); - unsigned childrenSize = children.size(); - - // If there's only one child, then it doesn't have segmented children. - if (childrenSize == 1) - return NO; - - for (unsigned i = 0; i < childrenSize; ++i) { - AccessibilityRole role = children[i]->roleValue(); - if (role != StaticTextRole && role != ImageRole && role != GroupRole) - return NO; - } - - return YES; -} - -- (id)accessibilityContainer -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AXAttributeCacheEnabler enableCache(m_object->axObjectCache()); - - // As long as there's a parent wrapper, that's the correct chain to climb. - AccessibilityObject* parent = m_object->parentObjectUnignored(); - if (parent) - return parent->wrapper(); - - // The only object without a parent wrapper should be a scroll view. - ASSERT(m_object->isAccessibilityScrollView()); - - // Verify this is the top document. If not, we might need to go through the platform widget. - FrameView* frameView = m_object->documentFrameView(); - Document* document = m_object->document(); - if (document && frameView && document != document->topDocument()) - return frameView->platformWidget(); - - // The top scroll view's parent is the web document view. - return [self _accessibilityWebDocumentView]; -} - -- (id)accessibilityFocusedUIElement -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityObject* focusedObj = m_object->focusedUIElement(); - - if (!focusedObj) - return nil; - - return focusedObj->wrapper(); -} - -- (id)_accessibilityWebDocumentView -{ - if (![self _prepareAccessibilityCall]) - return nil; - - // This method performs the crucial task of connecting to the UIWebDocumentView. - // This is needed to correctly calculate the screen position of the AX object. - static Class webViewClass = nil; - if (!webViewClass) - webViewClass = NSClassFromString(@"WebView"); - - if (!webViewClass) - return nil; - - FrameView* frameView = m_object->documentFrameView(); - - if (!frameView) - return nil; - - // If this is the top level frame, the UIWebDocumentView should be returned. - id parentView = frameView->documentView(); - while (parentView && ![parentView isKindOfClass:webViewClass]) - parentView = [parentView superview]; - - // The parentView should have an accessibilityContainer, if the UIKit accessibility bundle was loaded. - // The exception is DRT, which tests accessibility without the entire system turning accessibility on. Hence, - // this check should be valid for everything except DRT. - ASSERT([parentView accessibilityContainer] || applicationIsDumpRenderTree()); - - return [parentView accessibilityContainer]; -} - -- (NSArray *)_accessibilityNextElementsWithCount:(UInt32)count -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return [[self _accessibilityWebDocumentView] _accessibilityNextElementsWithCount:count]; -} - -- (NSArray *)_accessibilityPreviousElementsWithCount:(UInt32)count -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return [[self _accessibilityWebDocumentView] _accessibilityPreviousElementsWithCount:count]; -} - -- (BOOL)accessibilityRequired -{ - if (![self _prepareAccessibilityCall]) - return NO; - - return m_object->isRequired(); -} - -- (NSArray *)accessibilityFlowToElements -{ - if (![self _prepareAccessibilityCall]) - return nil; - - AccessibilityObject::AccessibilityChildrenVector children; - m_object->ariaFlowToElements(children); - - unsigned length = children.size(); - NSMutableArray* array = [NSMutableArray arrayWithCapacity:length]; - for (unsigned i = 0; i < length; ++i) { - AccessibilityObjectWrapper* wrapper = children[i]->wrapper(); - ASSERT(wrapper); - if (!wrapper) - continue; - - if (children[i]->isAttachment() && [wrapper attachmentView]) - [array addObject:[wrapper attachmentView]]; - else - [array addObject:wrapper]; - } - return array; -} - -- (id)accessibilityLinkedElement -{ - if (![self _prepareAccessibilityCall]) - return nil; - - // If this static text inside of a link, it should use its parent's linked element. - AccessibilityObject* element = m_object; - if (m_object->roleValue() == StaticTextRole && m_object->parentObjectUnignored()->isLink()) - element = m_object->parentObjectUnignored(); - - AccessibilityObject::AccessibilityChildrenVector children; - element->linkedUIElements(children); - if (children.size() == 0) - return nil; - - return children[0]->wrapper(); -} - - -- (BOOL)isAttachment -{ - if (!m_object) - return NO; - - return m_object->isAttachment(); -} - -- (void)_accessibilityActivate -{ - if (![self _prepareAccessibilityCall]) - return; - - m_object->press(); -} - -- (id)attachmentView -{ - if (![self _prepareAccessibilityCall]) - return nil; - - ASSERT([self isAttachment]); - Widget* widget = m_object->widgetForAttachmentView(); - if (!widget) - return nil; - return widget->platformWidget(); -} - -static RenderObject* rendererForView(WAKView* view) -{ - if (![view conformsToProtocol:@protocol(WebCoreFrameView)]) - return 0; - - WAKView<WebCoreFrameView>* frameView = (WAKView<WebCoreFrameView>*)view; - Frame* frame = [frameView _web_frame]; - if (!frame) - return 0; - - Node* node = frame->document()->ownerElement(); - if (!node) - return 0; - - return node->renderer(); -} - -- (id)_accessibilityParentForSubview:(id)subview -{ - RenderObject* renderer = rendererForView(subview); - if (!renderer) - return nil; - - AccessibilityObject* obj = renderer->document()->axObjectCache()->getOrCreate(renderer); - if (obj) - return obj->parentObjectUnignored()->wrapper(); - return nil; -} - -- (void)postFocusChangeNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postSelectedTextChangeNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postLayoutChangeNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postLiveRegionChangeNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postLoadCompleteNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postChildrenChangedNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)postInvalidStatusChangedNotification -{ - // The UIKit accessibility wrapper will override and post appropriate notification. -} - -- (void)accessibilityElementDidBecomeFocused -{ - if (![self _prepareAccessibilityCall]) - return; - - // The focused VoiceOver element might be the text inside a link. - // In those cases we should focus on the link itself. - for (AccessibilityObject* object = m_object; object != nil; object = object->parentObject()) { - if (object->roleValue() == WebAreaRole) - break; - - if (object->canSetFocusAttribute()) { - object->setFocused(true); - break; - } - } -} - -- (void)accessibilityModifySelection:(TextGranularity)granularity increase:(BOOL)increase -{ - if (![self _prepareAccessibilityCall]) - return; - - FrameSelection* frameSelection = m_object->document()->frame()->selection(); - VisibleSelection selection = m_object->selection(); - VisiblePositionRange range = m_object->visiblePositionRange(); - - // Before a selection with length exists, the cursor position needs to move to the right starting place. - // That should be the beginning of this element (range.start). However, if the cursor is already within the - // range of this element (the cursor is represented by selection), then the cursor does not need to move. - if (frameSelection->isNone() && (selection.visibleStart() < range.start || selection.visibleEnd() > range.end)) - frameSelection->moveTo(range.start, UserTriggered); - - frameSelection->modify(FrameSelection::AlterationExtend, (increase) ? DirectionRight : DirectionLeft, granularity, UserTriggered); -} - -- (void)accessibilityIncreaseSelection:(TextGranularity)granularity -{ - [self accessibilityModifySelection:granularity increase:YES]; -} - -- (void)accessibilityDecreaseSelection:(TextGranularity)granularity -{ - [self accessibilityModifySelection:granularity increase:NO]; -} - -- (void)accessibilityMoveSelectionToMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return; - - VisiblePosition visiblePosition = [marker visiblePosition]; - if (visiblePosition.isNull()) - return; - - FrameSelection* frameSelection = m_object->document()->frame()->selection(); - frameSelection->moveTo(visiblePosition, UserTriggered); -} - -- (void)accessibilityIncrement -{ - if (![self _prepareAccessibilityCall]) - return; - - m_object->increment(); -} - -- (void)accessibilityDecrement -{ - if (![self _prepareAccessibilityCall]) - return; - - m_object->decrement(); -} - -#pragma mark Accessibility Text Marker Handlers - -- (BOOL)_addAccessibilityObject:(AccessibilityObject*)axObject toTextMarkerArray:(NSMutableArray *)array -{ - if (!axObject) - return NO; - - AccessibilityObjectWrapper* wrapper = axObject->wrapper(); - if (!wrapper) - return NO; - - // Don't add the same object twice, but since this has already been added, we should return - // YES because we want to inform that it's in the array - if ([array containsObject:wrapper]) - return YES; - - // Explicity set that this is now an element (in case other logic tries to override). - [wrapper setValue:[NSNumber numberWithBool:YES] forKey:@"isAccessibilityElement"]; - [array addObject:wrapper]; - return YES; -} - -- (NSString *)stringForTextMarkers:(NSArray *)markers -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if ([markers count] != 2) - return nil; - - WebAccessibilityTextMarker* startMarker = [markers objectAtIndex:0]; - WebAccessibilityTextMarker* endMarker = [markers objectAtIndex:1]; - if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) - return nil; - - // extract the start and end VisiblePosition - VisiblePosition startVisiblePosition = [startMarker visiblePosition]; - if (startVisiblePosition.isNull()) - return nil; - - VisiblePosition endVisiblePosition = [endMarker visiblePosition]; - if (endVisiblePosition.isNull()) - return nil; - - VisiblePositionRange visiblePosRange = VisiblePositionRange(startVisiblePosition, endVisiblePosition); - return m_object->stringForVisiblePositionRange(visiblePosRange); -} - -static int blockquoteLevel(RenderObject* renderer) -{ - if (!renderer) - return 0; - - int result = 0; - for (Node* node = renderer->node(); node; node = node->parentNode()) { - if (node->hasTagName(blockquoteTag)) - result += 1; - } - - return result; -} - -static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - int quoteLevel = blockquoteLevel(renderer); - - if (quoteLevel) - [attrString addAttribute:UIAccessibilityTokenBlockquoteLevel value:[NSNumber numberWithInt:quoteLevel] range:range]; - else - [attrString removeAttribute:UIAccessibilityTokenBlockquoteLevel range:range]; -} - -static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - if (!renderer) - return; - - AccessibilityObject* parentObject = renderer->document()->axObjectCache()->getOrCreate(renderer->parent()); - int parentHeadingLevel = parentObject->headingLevel(); - - if (parentHeadingLevel) - [attrString addAttribute:UIAccessibilityTokenHeadingLevel value:[NSNumber numberWithInt:parentHeadingLevel] range:range]; - else - [attrString removeAttribute:UIAccessibilityTokenHeadingLevel range:range]; -} - -static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, GSFontRef font, NSRange range) -{ - if (!font) - return; - - const char* nameCStr = GSFontGetFullName(font); - const char* familyCStr = GSFontGetFamilyName(font); - NSNumber* size = [NSNumber numberWithFloat:GSFontGetSize(font)]; - NSNumber* bold = [NSNumber numberWithBool:GSFontIsBold(font)]; - GSFontTraitMask traits = GSFontGetTraits(font); - if (nameCStr) - [attrString addAttribute:UIAccessibilityTokenFontName value:[NSString stringWithUTF8String:nameCStr] range:range]; - if (familyCStr) - [attrString addAttribute:UIAccessibilityTokenFontFamily value:[NSString stringWithUTF8String:familyCStr] range:range]; - if ([size boolValue]) - [attrString addAttribute:UIAccessibilityTokenFontSize value:size range:range]; - if ([bold boolValue] || (traits & GSBoldFontMask)) - [attrString addAttribute:UIAccessibilityTokenBold value:[NSNumber numberWithBool:YES] range:range]; - if (traits & GSItalicFontMask) - [attrString addAttribute:UIAccessibilityTokenItalic value:[NSNumber numberWithBool:YES] range:range]; - -} - -static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range) -{ - if (number) - [attrString addAttribute:attribute value:number range:range]; - else - [attrString removeAttribute:attribute range:range]; -} - -static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - RenderStyle* style = renderer->style(); - - // set basic font info - AXAttributeStringSetFont(attrString, style->font().primaryFont()->getGSFont(), range); - - int decor = style->textDecorationsInEffect(); - if ((decor & (TextDecorationUnderline | TextDecorationLineThrough)) != 0) { - // find colors using quirk mode approach (strict mode would use current - // color for all but the root line box, which would use getTextDecorationColors) - Color underline, overline, linethrough; - renderer->getTextDecorationColors(decor, underline, overline, linethrough); - - if (decor & TextDecorationUnderline) - AXAttributeStringSetNumber(attrString, UIAccessibilityTokenUnderline, [NSNumber numberWithBool:YES], range); - } -} - -static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, const UChar* chars, int length) -{ - // skip invisible text - if (!node->renderer()) - return; - - // easier to calculate the range before appending the string - NSRange attrStringRange = NSMakeRange([attrString length], length); - - // append the string from this node - [[attrString mutableString] appendString:[NSString stringWithCharacters:chars length:length]]; - - // set new attributes - AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange); - AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange); - AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange); -} - - -// This method is intended to return an array of strings and accessibility elements that -// represent the objects on one line of rendered web content. The array of markers sent -// in should be ordered and contain only a start and end marker. -- (NSArray *)arrayOfTextForTextMarkers:(NSArray *)markers attributed:(BOOL)attributed -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if ([markers count] != 2) - return nil; - - WebAccessibilityTextMarker* startMarker = [markers objectAtIndex:0]; - WebAccessibilityTextMarker* endMarker = [markers objectAtIndex:1]; - if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) - return nil; - - // extract the start and end VisiblePosition - VisiblePosition startVisiblePosition = [startMarker visiblePosition]; - if (startVisiblePosition.isNull()) - return nil; - - VisiblePosition endVisiblePosition = [endMarker visiblePosition]; - if (endVisiblePosition.isNull()) - return nil; - - // iterate over the range to build the AX attributed string - NSMutableArray* array = [[NSMutableArray alloc] init]; - TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); - for (; !it.atEnd(); it.advance()) { - // locate the node and starting offset for this range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); - - // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length() != 0) { - if (!attributed) { - // First check if this is represented by a link. - AccessibilityObject* linkObject = AccessibilityObject::anchorElementForNode(node); - if ([self _addAccessibilityObject:linkObject toTextMarkerArray:array]) - continue; - - // Next check if this region is represented by a heading. - AccessibilityObject* headingObject = AccessibilityObject::headingElementForNode(node); - if ([self _addAccessibilityObject:headingObject toTextMarkerArray:array]) - continue; - - String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); - - if (!listMarkerText.isEmpty()) - [array addObject:[NSString stringWithCharacters:listMarkerText.characters() length:listMarkerText.length()]]; - // There was not an element representation, so just return the text. - [array addObject:[NSString stringWithCharacters:it.characters() length:it.length()]]; - } - else - { - String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); - - if (!listMarkerText.isEmpty()) { - NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init]; - AXAttributedStringAppendText(attrString, node, listMarkerText.characters(), listMarkerText.length()); - [array addObject:attrString]; - [attrString release]; - } - - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; - AXAttributedStringAppendText(attrString, node, it.characters(), it.length()); - [array addObject:attrString]; - [attrString release]; - } - } else { - Node* replacedNode = node->childNode(offset); - if (replacedNode) { - AccessibilityObject* obj = m_object->axObjectCache()->getOrCreate(replacedNode->renderer()); - if (obj && !obj->accessibilityIsIgnored()) - [self _addAccessibilityObject:obj toTextMarkerArray:array]; - } - } - } - - return [array autorelease]; -} - -- (NSRange)_convertToNSRange:(Range *)range -{ - if (!range || !range->startContainer()) - return NSMakeRange(NSNotFound, 0); - - Document* document = m_object->document(); - FrameSelection* frameSelection = document->frame()->selection(); - - Element* selectionRoot = frameSelection->rootEditableElement(); - Element* scope = selectionRoot ? selectionRoot : document->documentElement(); - - // Mouse events may cause TSM to attempt to create an NSRange for a portion of the view - // that is not inside the current editable region. These checks ensure we don't produce - // potentially invalid data when responding to such requests. - if (range->startContainer() != scope && !range->startContainer()->isDescendantOf(scope)) - return NSMakeRange(NSNotFound, 0); - if (range->endContainer() != scope && !range->endContainer()->isDescendantOf(scope)) - return NSMakeRange(NSNotFound, 0); - - RefPtr<Range> testRange = Range::create(scope->document(), scope, 0, range->startContainer(), range->startOffset()); - ASSERT(testRange->startContainer() == scope); - int startPosition = TextIterator::rangeLength(testRange.get()); - - ExceptionCode ec; - testRange->setEnd(range->endContainer(), range->endOffset(), ec); - ASSERT(testRange->startContainer() == scope); - int endPosition = TextIterator::rangeLength(testRange.get()); - return NSMakeRange(startPosition, endPosition - startPosition); -} - -- (PassRefPtr<Range>)_convertToDOMRange:(NSRange)nsrange -{ - if (nsrange.location > INT_MAX) - return 0; - if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) - nsrange.length = INT_MAX - nsrange.location; - - // our critical assumption is that we are only called by input methods that - // concentrate on a given area containing the selection - // We have to do this because of text fields and textareas. The DOM for those is not - // directly in the document DOM, so serialization is problematic. Our solution is - // to use the root editable element of the selection start as the positional base. - // That fits with AppKit's idea of an input context. - Document* document = m_object->document(); - FrameSelection* frameSelection = document->frame()->selection(); - Element* selectionRoot = frameSelection->rootEditableElement(); - Element* scope = selectionRoot ? selectionRoot : document->documentElement(); - return TextIterator::rangeFromLocationAndLength(scope, nsrange.location, nsrange.length); -} - -// This method is intended to take a text marker representing a VisiblePosition and convert it -// into a normalized location within the document. -- (NSInteger)positionForTextMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return NSNotFound; - - if (!marker) - return NSNotFound; - - VisibleSelection selection([marker visiblePosition]); - RefPtr<Range> range = selection.toNormalizedRange(); - NSRange nsRange = [self _convertToNSRange:range.get()]; - return nsRange.location; -} - -- (NSArray *)textMarkerRange -{ - if (![self _prepareAccessibilityCall]) - return nil; - - VisiblePositionRange range = m_object->visiblePositionRange(); - VisiblePosition startPosition = range.start; - VisiblePosition endPosition = range.end; - WebAccessibilityTextMarker* start = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:startPosition cache:m_object->axObjectCache()]; - WebAccessibilityTextMarker* end = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:endPosition cache:m_object->axObjectCache()]; - - return [NSArray arrayWithObjects:start, end, nil]; -} - -// A method to get the normalized text cursor range of an element. Used in DumpRenderTree. -- (NSRange)elementTextRange -{ - if (![self _prepareAccessibilityCall]) - return NSMakeRange(NSNotFound, 0); - - NSArray *markers = [self textMarkerRange]; - if ([markers count] != 2) - return NSMakeRange(NSNotFound, 0); - - WebAccessibilityTextMarker *startMarker = [markers objectAtIndex:0]; - WebAccessibilityTextMarker *endMarker = [markers objectAtIndex:1]; - - NSInteger startPosition = [self positionForTextMarker:startMarker]; - NSInteger endPosition = [self positionForTextMarker:endMarker]; - - return NSMakeRange(startPosition, endPosition - startPosition); -} - -- (AccessibilityObjectWrapper *)accessibilityObjectForTextMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (!marker) - return nil; - - VisiblePosition visiblePosition = [marker visiblePosition]; - AccessibilityObject* obj = m_object->accessibilityObjectForPosition(visiblePosition); - if (!obj) - return nil; - - return AccessibilityUnignoredAncestor(obj->wrapper()); -} - -- (NSArray *)textMarkerRangeForSelection -{ - if (![self _prepareAccessibilityCall]) - return nil; - - VisibleSelection selection = m_object->selection(); - if (selection.isNone()) - return nil; - VisiblePosition startPosition = selection.visibleStart(); - VisiblePosition endPosition = selection.visibleEnd(); - - WebAccessibilityTextMarker* startMarker = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:startPosition cache:m_object->axObjectCache()]; - WebAccessibilityTextMarker* endMarker = [WebAccessibilityTextMarker textMarkerWithVisiblePosition:endPosition cache:m_object->axObjectCache()]; - if (!startMarker || !endMarker) - return nil; - - return [NSArray arrayWithObjects:startMarker, endMarker, nil]; -} - -- (WebAccessibilityTextMarker *)textMarkerForPosition:(NSInteger)position -{ - if (![self _prepareAccessibilityCall]) - return nil; - - PassRefPtr<Range> range = [self _convertToDOMRange:NSMakeRange(position, 0)]; - if (!range) - return nil; - - VisibleSelection selection = VisibleSelection(range.get(), DOWNSTREAM); - - VisiblePosition visiblePosition = selection.visibleStart(); - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:visiblePosition cache:m_object->axObjectCache()]; -} - -- (id)_stringForRange:(NSRange)range attributed:(BOOL)attributed -{ - if (![self _prepareAccessibilityCall]) - return nil; - - WebAccessibilityTextMarker* startMarker = [self textMarkerForPosition:range.location]; - WebAccessibilityTextMarker* endMarker = [self textMarkerForPosition:NSMaxRange(range)]; - - // Clients don't always know the exact range, rather than force them to compute it, - // allow clients to overshoot and use the max text marker range. - if (!startMarker || !endMarker) { - NSArray *markers = [self textMarkerRange]; - if ([markers count] != 2) - return nil; - if (!startMarker) - startMarker = [markers objectAtIndex:0]; - if (!endMarker) - endMarker = [markers objectAtIndex:1]; - } - - NSArray* array = [self arrayOfTextForTextMarkers:[NSArray arrayWithObjects:startMarker, endMarker, nil] attributed:attributed]; - Class returnClass = attributed ? [NSMutableAttributedString class] : [NSMutableString class]; - id returnValue = [[[returnClass alloc] init] autorelease]; - - NSInteger count = [array count]; - for (NSInteger k = 0; k < count; ++k) { - id object = [array objectAtIndex:k]; - - if (![object isKindOfClass:returnClass]) - continue; - - if (attributed) - [(NSMutableAttributedString *)returnValue appendAttributedString:object]; - else - [(NSMutableString *)returnValue appendString:object]; - } - return returnValue; -} - - -// A convenience method for getting the text of a NSRange. Currently used only by DRT. -- (NSString *)stringForRange:(NSRange)range -{ - return [self _stringForRange:range attributed:NO]; -} - -- (NSAttributedString *)attributedStringForRange:(NSRange)range -{ - return [self _stringForRange:range attributed:YES]; -} - -// A convenience method for getting the accessibility objects of a NSRange. Currently used only by DRT. -- (NSArray *)elementsForRange:(NSRange)range -{ - if (![self _prepareAccessibilityCall]) - return nil; - - WebAccessibilityTextMarker* startMarker = [self textMarkerForPosition:range.location]; - WebAccessibilityTextMarker* endMarker = [self textMarkerForPosition:NSMaxRange(range)]; - if (!startMarker || !endMarker) - return nil; - - NSArray* array = [self arrayOfTextForTextMarkers:[NSArray arrayWithObjects:startMarker, endMarker, nil] attributed:NO]; - NSMutableArray* elements = [NSMutableArray array]; - for (id element in array) { - if (![element isKindOfClass:[AccessibilityObjectWrapper class]]) - continue; - [elements addObject:element]; - } - return elements; -} - -- (NSString *)selectionRangeString -{ - NSArray *markers = [self textMarkerRangeForSelection]; - return [self stringForTextMarkers:markers]; -} - -- (WebAccessibilityTextMarker *)selectedTextMarker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - VisibleSelection selection = m_object->selection(); - VisiblePosition position = selection.visibleStart(); - - // if there's no selection, start at the top of the document - if (position.isNull()) - position = startOfDocument(m_object->document()); - - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:position cache:m_object->axObjectCache()]; -} - -// This method is intended to return the marker at the end of the line starting at -// the marker that is passed into the method. -- (WebAccessibilityTextMarker *)lineEndMarkerForMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (!marker) - return nil; - - VisiblePosition start = [marker visiblePosition]; - VisiblePosition lineEnd = m_object->nextLineEndPosition(start); - - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:lineEnd cache:m_object->axObjectCache()]; -} - -// This method is intended to return the marker at the start of the line starting at -// the marker that is passed into the method. -- (WebAccessibilityTextMarker *)lineStartMarkerForMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (!marker) - return nil; - - VisiblePosition start = [marker visiblePosition]; - VisiblePosition lineStart = m_object->previousLineStartPosition(start); - - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:lineStart cache:m_object->axObjectCache()]; -} - -- (WebAccessibilityTextMarker *)nextMarkerForMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (!marker) - return nil; - - VisiblePosition start = [marker visiblePosition]; - VisiblePosition nextMarker = m_object->nextVisiblePosition(start); - - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:nextMarker cache:m_object->axObjectCache()]; -} - -// This method is intended to return the marker at the start of the line starting at -// the marker that is passed into the method. -- (WebAccessibilityTextMarker *)previousMarkerForMarker:(WebAccessibilityTextMarker *)marker -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (!marker) - return nil; - - VisiblePosition start = [marker visiblePosition]; - VisiblePosition previousMarker = m_object->previousVisiblePosition(start); - - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:previousMarker cache:m_object->axObjectCache()]; -} - -// This method is intended to return the bounds of a text marker range in screen coordinates. -- (CGRect)frameForTextMarkers:(NSArray *)array -{ - if (![self _prepareAccessibilityCall]) - return CGRectZero; - - if ([array count] != 2) - return CGRectZero; - - WebAccessibilityTextMarker* startMarker = [array objectAtIndex:0]; - WebAccessibilityTextMarker* endMarker = [array objectAtIndex:1]; - if (![startMarker isKindOfClass:[WebAccessibilityTextMarker class]] || ![endMarker isKindOfClass:[WebAccessibilityTextMarker class]]) - return CGRectZero; - - IntRect rect = m_object->boundsForVisiblePositionRange(VisiblePositionRange([startMarker visiblePosition], [endMarker visiblePosition])); - return [self convertRectToScreenSpace:rect]; -} - -- (WebAccessibilityTextMarker *)textMarkerForPoint:(CGPoint)point -{ - if (![self _prepareAccessibilityCall]) - return nil; - - VisiblePosition pos = m_object->visiblePositionForPoint(IntPoint(point)); - return [WebAccessibilityTextMarker textMarkerWithVisiblePosition:pos cache:m_object->axObjectCache()]; -} - -- (NSString *)accessibilityIdentifier -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->getAttribute(HTMLNames::idAttr); -} - -- (NSString *)accessibilitySpeechHint -{ - if (![self _prepareAccessibilityCall]) - return nil; - - switch (m_object->speakProperty()) { - default: - case SpeakNormal: - return @"normal"; - case SpeakNone: - return @"none"; - case SpeakSpellOut: - return @"spell-out"; - case SpeakDigits: - return @"digits"; - case SpeakLiteralPunctuation: - return @"literal-punctuation"; - case SpeakNoPunctuation: - return @"no-punctuation"; - } - - return nil; -} - -- (BOOL)accessibilityARIAIsBusy -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->ariaLiveRegionBusy(); -} - -- (NSString *)accessibilityARIALiveRegionStatus -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->ariaLiveRegionStatus(); -} - -- (NSString *)accessibilityARIARelevantStatus -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->ariaLiveRegionRelevant(); -} - -- (BOOL)accessibilityARIALiveRegionIsAtomic -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->ariaLiveRegionAtomic(); -} - -- (NSString *)accessibilityInvalidStatus -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->invalidStatus(); -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathRootIndexObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathRootIndexObject() ? m_object->mathRootIndexObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathRadicandObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathRadicandObject() ? m_object->mathRadicandObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathNumeratorObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathNumeratorObject() ? m_object->mathNumeratorObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathDenominatorObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathDenominatorObject() ? m_object->mathDenominatorObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathBaseObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathBaseObject() ? m_object->mathBaseObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathSubscriptObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathSubscriptObject() ? m_object->mathSubscriptObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathSuperscriptObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathSuperscriptObject() ? m_object->mathSuperscriptObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathUnderObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathUnderObject() ? m_object->mathUnderObject()->wrapper() : 0; -} - -- (WebAccessibilityObjectWrapper *)accessibilityMathOverObject -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathOverObject() ? m_object->mathOverObject()->wrapper() : 0; -} - -- (NSString *)accessibilityPlatformMathSubscriptKey -{ - return @"AXMSubscriptObject"; -} - -- (NSString *)accessibilityPlatformMathSuperscriptKey -{ - return @"AXMSuperscriptObject"; -} - -- (NSArray *)accessibilityMathPostscripts -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return [self accessibilityMathPostscriptPairs]; -} - -- (NSArray *)accessibilityMathPrescripts -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return [self accessibilityMathPrescriptPairs]; -} - -- (NSString *)accessibilityMathFencedOpenString -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathFencedOpenString(); -} - -- (NSString *)accessibilityMathFencedCloseString -{ - if (![self _prepareAccessibilityCall]) - return nil; - - return m_object->mathFencedCloseString(); -} - -- (BOOL)accessibilityIsMathTopObject -{ - if (![self _prepareAccessibilityCall]) - return NO; - - return m_object->roleValue() == DocumentMathRole; -} - -- (NSInteger)accessibilityMathLineThickness -{ - if (![self _prepareAccessibilityCall]) - return 0; - - return m_object->mathLineThickness(); -} - -- (NSString *)accessibilityMathType -{ - if (![self _prepareAccessibilityCall]) - return nil; - - if (m_object->roleValue() == MathElementRole) { - if (m_object->isMathFraction()) - return @"AXMathFraction"; - if (m_object->isMathFenced()) - return @"AXMathFenced"; - if (m_object->isMathSubscriptSuperscript()) - return @"AXMathSubscriptSuperscript"; - if (m_object->isMathRow()) - return @"AXMathRow"; - if (m_object->isMathUnderOver()) - return @"AXMathUnderOver"; - if (m_object->isMathSquareRoot()) - return @"AXMathSquareRoot"; - if (m_object->isMathRoot()) - return @"AXMathRoot"; - if (m_object->isMathText()) - return @"AXMathText"; - if (m_object->isMathNumber()) - return @"AXMathNumber"; - if (m_object->isMathIdentifier()) - return @"AXMathIdentifier"; - if (m_object->isMathTable()) - return @"AXMathTable"; - if (m_object->isMathTableRow()) - return @"AXMathTableRow"; - if (m_object->isMathTableCell()) - return @"AXMathTableCell"; - if (m_object->isMathFenceOperator()) - return @"AXMathFenceOperator"; - if (m_object->isMathSeparatorOperator()) - return @"AXMathSeparatorOperator"; - if (m_object->isMathOperator()) - return @"AXMathOperator"; - if (m_object->isMathMultiscript()) - return @"AXMathMultiscript"; - } - - return nil; -} - -- (CGPoint)accessibilityClickPoint -{ - return m_object->clickPoint(); -} - -// These are used by DRT so that it can know when notifications are sent. -// Since they are static, only one callback can be installed at a time (that's all DRT should need). -typedef void (*AXPostedNotificationCallback)(id element, NSString* notification, void* context); -static AXPostedNotificationCallback AXNotificationCallback = 0; -static void* AXPostedNotificationContext = 0; - -- (void)accessibilitySetPostedNotificationCallback:(AXPostedNotificationCallback)function withContext:(void*)context -{ - AXNotificationCallback = function; - AXPostedNotificationContext = context; -} - -- (void)accessibilityPostedNotification:(NSString *)notificationName -{ - if (AXNotificationCallback && notificationName) - AXNotificationCallback(self, notificationName, AXPostedNotificationContext); -} - -#ifndef NDEBUG -- (NSString *)description -{ - CGRect frame = [self accessibilityFrame]; - return [NSString stringWithFormat:@"Role: (%d) - Text: %@: Value: %@ -- Frame: %f %f %f %f", m_object ? m_object->roleValue() : 0, [self accessibilityLabel], [self accessibilityValue], frame.origin.x, frame.origin.y, frame.size.width, frame.size.height]; -} -#endif - -@end - -#endif // HAVE(ACCESSIBILITY) && PLATFORM(IOS) diff --git a/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm b/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm deleted file mode 100644 index 58870836c..000000000 --- a/Source/WebCore/accessibility/mac/AXObjectCacheMac.mm +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2012 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 COMPUTER, 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 - * 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. - */ - -#import "config.h" -#import "AXObjectCache.h" - -#if HAVE(ACCESSIBILITY) - -#import "AccessibilityObject.h" -#import "RenderObject.h" -#import "WebAccessibilityObjectWrapperMac.h" -#import "WebCoreSystemInterface.h" - -#import <wtf/PassRefPtr.h> - -#ifndef NSAccessibilityLiveRegionChangedNotification -#define NSAccessibilityLiveRegionChangedNotification @"AXLiveRegionChanged" -#endif - -// The simple Cocoa calls in this file don't throw exceptions. - -namespace WebCore { - -void AXObjectCache::detachWrapper(AccessibilityObject* obj) -{ - [obj->wrapper() detach]; - obj->setWrapper(0); -} - -void AXObjectCache::attachWrapper(AccessibilityObject* obj) -{ - RetainPtr<WebAccessibilityObjectWrapper> wrapper = adoptNS([[WebAccessibilityObjectWrapper alloc] initWithAccessibilityObject:obj]); - obj->setWrapper(wrapper.get()); -} - -void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) -{ - if (!obj) - return; - - // Some notifications are unique to Safari and do not have NSAccessibility equivalents. - NSString *macNotification; - switch (notification) { - case AXActiveDescendantChanged: - // An active descendant change for trees means a selected rows change. - if (obj->isTree()) - macNotification = NSAccessibilitySelectedRowsChangedNotification; - - // When a combobox uses active descendant, it means the selected item in its associated - // list has changed. In these cases we should use selected children changed, because - // we don't want the focus to change away from the combobox where the user is typing. - else if (obj->isComboBox()) - macNotification = NSAccessibilitySelectedChildrenChangedNotification; - else - macNotification = NSAccessibilityFocusedUIElementChangedNotification; - break; - case AXAutocorrectionOccured: - macNotification = @"AXAutocorrectionOccurred"; - break; - case AXFocusedUIElementChanged: - macNotification = NSAccessibilityFocusedUIElementChangedNotification; - break; - case AXLayoutComplete: - macNotification = @"AXLayoutComplete"; - break; - case AXLoadComplete: - macNotification = @"AXLoadComplete"; - break; - case AXInvalidStatusChanged: - macNotification = @"AXInvalidStatusChanged"; - break; - case AXSelectedChildrenChanged: - if (obj->isAccessibilityTable()) - macNotification = NSAccessibilitySelectedRowsChangedNotification; - else - macNotification = NSAccessibilitySelectedChildrenChangedNotification; - break; - case AXSelectedTextChanged: - macNotification = NSAccessibilitySelectedTextChangedNotification; - break; - case AXValueChanged: - macNotification = NSAccessibilityValueChangedNotification; - break; - case AXLiveRegionChanged: - macNotification = NSAccessibilityLiveRegionChangedNotification; - break; - case AXRowCountChanged: - macNotification = NSAccessibilityRowCountChangedNotification; - break; - case AXRowExpanded: - macNotification = NSAccessibilityRowExpandedNotification; - break; - case AXRowCollapsed: - macNotification = NSAccessibilityRowCollapsedNotification; - break; - // Does not exist on Mac. - case AXCheckedStateChanged: - default: - return; - } - - // NSAccessibilityPostNotification will call this method, (but not when running DRT), so ASSERT here to make sure it does not crash. - // https://bugs.webkit.org/show_bug.cgi?id=46662 - ASSERT([obj->wrapper() accessibilityIsIgnored] || true); - - NSAccessibilityPostNotification(obj->wrapper(), macNotification); - - // Used by DRT to know when notifications are posted. - [obj->wrapper() accessibilityPostedNotification:macNotification]; -} - -void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) -{ -} - -void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent) -{ -} - -void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) -{ - wkAccessibilityHandleFocusChanged(); -} - -void AXObjectCache::handleScrolledToAnchor(const Node*) -{ -} - -} - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm b/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm deleted file mode 100644 index 300ef18bd..000000000 --- a/Source/WebCore/accessibility/mac/AccessibilityObjectMac.mm +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2008 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 COMPUTER, 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 - * 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. - */ - -#import "config.h" -#import "AccessibilityObject.h" - -#if HAVE(ACCESSIBILITY) - -#import "WebAccessibilityObjectWrapperMac.h" -#import "Widget.h" - -namespace WebCore { - -void AccessibilityObject::detachFromParent() -{ - if (isAttachment()) - overrideAttachmentParent(0); -} - -void AccessibilityObject::overrideAttachmentParent(AccessibilityObject* parent) -{ - if (!isAttachment()) - return; - - id parentWrapper = nil; - if (parent) { - if (parent->accessibilityIsIgnored()) - parent = parent->parentObjectUnignored(); - parentWrapper = parent->wrapper(); - } - - [[wrapper() attachmentView] accessibilitySetOverrideValue:parentWrapper forAttribute:NSAccessibilityParentAttribute]; -} - -bool AccessibilityObject::accessibilityIgnoreAttachment() const -{ - // FrameView attachments are now handled by AccessibilityScrollView, - // so if this is the attachment, it should be ignored. - Widget* widget = 0; - if (isAttachment() && (widget = widgetForAttachmentView()) && widget->isFrameView()) - return true; - - if ([wrapper() attachmentView]) - return [[wrapper() attachmentView] accessibilityIsIgnored]; - - // Attachments are ignored by default (unless we determine that we should expose them). - return true; -} - -AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const -{ - if (isMenuListPopup() || isMenuListOption()) - return IgnoreObject; - - // Never expose an unknown object on the Mac. Clients of the AX API will not know what to do with it. - // Special case is when the unknown object is actually an attachment. - if (roleValue() == UnknownRole && !isAttachment()) - return IgnoreObject; - - return DefaultBehavior; -} - -} // WebCore - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.mm b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.mm deleted file mode 100644 index c5cd4056e..000000000 --- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperBase.mm +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright (C) 2008, 2009, 2010, 2011 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 Computer, 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. - */ - -#import "config.h" -#import "WebAccessibilityObjectWrapperBase.h" - -#if HAVE(ACCESSIBILITY) - -#import "AXObjectCache.h" -#import "AccessibilityARIAGridRow.h" -#import "AccessibilityList.h" -#import "AccessibilityListBox.h" -#import "AccessibilityRenderObject.h" -#import "AccessibilityScrollView.h" -#import "AccessibilitySpinButton.h" -#import "AccessibilityTable.h" -#import "AccessibilityTableCell.h" -#import "AccessibilityTableColumn.h" -#import "AccessibilityTableRow.h" -#import "Chrome.h" -#import "ChromeClient.h" -#import "ColorMac.h" -#import "ContextMenuController.h" -#import "Font.h" -#import "Frame.h" -#import "FrameLoaderClient.h" -#import "FrameSelection.h" -#import "HTMLAnchorElement.h" -#import "HTMLAreaElement.h" -#import "HTMLFrameOwnerElement.h" -#import "HTMLImageElement.h" -#import "HTMLInputElement.h" -#import "HTMLNames.h" -#import "HTMLTextAreaElement.h" -#import "LocalizedStrings.h" -#import "Page.h" -#import "RenderTextControl.h" -#import "RenderView.h" -#import "RenderWidget.h" -#import "ScrollView.h" -#import "SimpleFontData.h" -#import "TextCheckerClient.h" -#import "TextCheckingHelper.h" -#import "TextIterator.h" -#import "VisibleUnits.h" -#import "WebCoreFrameView.h" -#import "WebCoreObjCExtras.h" -#import "WebCoreSystemInterface.h" -#import "htmlediting.h" - -using namespace WebCore; -using namespace HTMLNames; -using namespace std; - -static NSArray *convertMathPairsToNSArray(const AccessibilityObject::AccessibilityMathMultiscriptPairs& pairs, NSString *subscriptKey, NSString *superscriptKey) -{ - unsigned length = pairs.size(); - NSMutableArray *array = [NSMutableArray arrayWithCapacity:length]; - for (unsigned i = 0; i < length; ++i) { - NSMutableDictionary *pairDictionary = [NSMutableDictionary dictionary]; - pair<AccessibilityObject*, AccessibilityObject*> pair = pairs[i]; - if (pair.first && pair.first->wrapper() && !pair.first->accessibilityIsIgnored()) - [pairDictionary setObject:pair.first->wrapper() forKey:subscriptKey]; - if (pair.second && pair.second->wrapper() && !pair.second->accessibilityIsIgnored()) - [pairDictionary setObject:pair.second->wrapper() forKey:superscriptKey]; - [array addObject:pairDictionary]; - } - return array; -} - -@implementation WebAccessibilityObjectWrapperBase - -- (id)initWithAccessibilityObject:(AccessibilityObject*)axObject -{ - if (!(self = [super init])) - return nil; - - m_object = axObject; - return self; -} - -- (void)detach -{ - m_object = 0; -} - -- (BOOL)updateObjectBackingStore -{ - // Calling updateBackingStore() can invalidate this element so self must be retained. - // If it does become invalidated, m_object will be nil. - [[self retain] autorelease]; - - if (!m_object) - return NO; - - m_object->updateBackingStore(); - if (!m_object) - return NO; - - return YES; -} - -- (id)attachmentView -{ - return nil; -} - -- (AccessibilityObject*)accessibilityObject -{ - return m_object; -} - -// FIXME: Different kinds of elements are putting the title tag to use in different -// AX fields. This should be rectified, but in the initial patch I want to achieve -// parity with existing behavior. -- (BOOL)titleTagShouldBeUsedInDescriptionField -{ - return (m_object->isLink() && !m_object->isImageMapLink()) || m_object->isImage(); -} - -// On iOS, we don't have to return the value in the title. We can return the actual title, given the API. -- (BOOL)fileUploadButtonReturnsValueInTitle -{ - return YES; -} - -// This should be the "visible" text that's actually on the screen if possible. -// If there's alternative text, that can override the title. -- (NSString *)accessibilityTitle -{ - // Static text objects should not have a title. Its content is communicated in its AXValue. - if (m_object->roleValue() == StaticTextRole) - return [NSString string]; - - // A file upload button presents a challenge because it has button text and a value, but the - // API doesn't support this paradigm. - // The compromise is to return the button type in the role description and the value of the file path in the title - if (m_object->isFileUploadButton() && [self fileUploadButtonReturnsValueInTitle]) - return m_object->stringValue(); - - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - // If we have alternative text, then we should not expose a title. - if (text.textSource == AlternativeText) - break; - - // Once we encounter visible text, or the text from our children that should be used foremost. - if (text.textSource == VisibleText || text.textSource == ChildrenText) - return text.text; - - // If there's an element that labels this object and it's not exposed, then we should use - // that text as our title. - if (text.textSource == LabelByElementText && !m_object->exposesTitleUIElement()) - return text.text; - - // FIXME: The title tag is used in certain cases for the title. This usage should - // probably be in the description field since it's not "visible". - if (text.textSource == TitleTagText && ![self titleTagShouldBeUsedInDescriptionField]) - return text.text; - } - - return [NSString string]; -} - -- (NSString *)accessibilityDescription -{ - // Static text objects should not have a description. Its content is communicated in its AXValue. - // One exception is the media control labels that have a value and a description. Those are set programatically. - if (m_object->roleValue() == StaticTextRole && !m_object->isMediaControlLabel()) - return [NSString string]; - - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - if (text.textSource == AlternativeText) - return text.text; - - if (text.textSource == TitleTagText && [self titleTagShouldBeUsedInDescriptionField]) - return text.text; - } - - return [NSString string]; -} - -- (NSString *)accessibilityHelpText -{ - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - bool descriptiveTextAvailable = false; - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - if (text.textSource == HelpText || text.textSource == SummaryText) - return text.text; - - // If an element does NOT have other descriptive text the title tag should be used as its descriptive text. - // But, if those ARE available, then the title tag should be used for help text instead. - switch (text.textSource) { - case AlternativeText: - case VisibleText: - case ChildrenText: - case LabelByElementText: - descriptiveTextAvailable = true; - default: - break; - } - - if (text.textSource == TitleTagText && descriptiveTextAvailable) - return text.text; - } - - return [NSString string]; -} - -struct PathConversionInfo { - WebAccessibilityObjectWrapperBase *wrapper; - CGMutablePathRef path; -}; - -static void ConvertPathToScreenSpaceFunction(void* info, const PathElement* element) -{ - PathConversionInfo* conversion = (PathConversionInfo*)info; - WebAccessibilityObjectWrapperBase *wrapper = conversion->wrapper; - CGMutablePathRef newPath = conversion->path; - switch (element->type) { - case PathElementMoveToPoint: - { - CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]]; - CGPathMoveToPoint(newPath, nil, newPoint.x, newPoint.y); - break; - } - case PathElementAddLineToPoint: - { - CGPoint newPoint = [wrapper convertPointToScreenSpace:element->points[0]]; - CGPathAddLineToPoint(newPath, nil, newPoint.x, newPoint.y); - break; - } - case PathElementAddQuadCurveToPoint: - { - CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]]; - CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]]; - CGPathAddQuadCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y); - break; - } - case PathElementAddCurveToPoint: - { - CGPoint newPoint1 = [wrapper convertPointToScreenSpace:element->points[0]]; - CGPoint newPoint2 = [wrapper convertPointToScreenSpace:element->points[1]]; - CGPoint newPoint3 = [wrapper convertPointToScreenSpace:element->points[2]]; - CGPathAddCurveToPoint(newPath, nil, newPoint1.x, newPoint1.y, newPoint2.x, newPoint2.y, newPoint3.x, newPoint3.y); - break; - } - case PathElementCloseSubpath: - { - CGPathCloseSubpath(newPath); - break; - } - } -} - -- (CGPathRef)convertPathToScreenSpace:(Path &)path -{ - PathConversionInfo conversion = { self, CGPathCreateMutable() }; - path.apply(&conversion, ConvertPathToScreenSpaceFunction); - return (CGPathRef)[(id)conversion.path autorelease]; -} - -- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point -{ - UNUSED_PARAM(point); - ASSERT_NOT_REACHED(); - return CGPointZero; -} - -- (NSString *)ariaLandmarkRoleDescription -{ - switch (m_object->roleValue()) { - case LandmarkApplicationRole: - return AXARIAContentGroupText(@"ARIALandmarkApplication"); - case LandmarkBannerRole: - return AXARIAContentGroupText(@"ARIALandmarkBanner"); - case LandmarkComplementaryRole: - return AXARIAContentGroupText(@"ARIALandmarkComplementary"); - case LandmarkContentInfoRole: - return AXARIAContentGroupText(@"ARIALandmarkContentInfo"); - case LandmarkMainRole: - return AXARIAContentGroupText(@"ARIALandmarkMain"); - case LandmarkNavigationRole: - return AXARIAContentGroupText(@"ARIALandmarkNavigation"); - case LandmarkSearchRole: - return AXARIAContentGroupText(@"ARIALandmarkSearch"); - case ApplicationAlertRole: - return AXARIAContentGroupText(@"ARIAApplicationAlert"); - case ApplicationAlertDialogRole: - return AXARIAContentGroupText(@"ARIAApplicationAlertDialog"); - case ApplicationDialogRole: - return AXARIAContentGroupText(@"ARIAApplicationDialog"); - case ApplicationLogRole: - return AXARIAContentGroupText(@"ARIAApplicationLog"); - case ApplicationMarqueeRole: - return AXARIAContentGroupText(@"ARIAApplicationMarquee"); - case ApplicationStatusRole: - return AXARIAContentGroupText(@"ARIAApplicationStatus"); - case ApplicationTimerRole: - return AXARIAContentGroupText(@"ARIAApplicationTimer"); - case DocumentRole: - return AXARIAContentGroupText(@"ARIADocument"); - case DocumentArticleRole: - return AXARIAContentGroupText(@"ARIADocumentArticle"); - case DocumentMathRole: - return AXARIAContentGroupText(@"ARIADocumentMath"); - case DocumentNoteRole: - return AXARIAContentGroupText(@"ARIADocumentNote"); - case DocumentRegionRole: - return AXARIAContentGroupText(@"ARIADocumentRegion"); - case UserInterfaceTooltipRole: - return AXARIAContentGroupText(@"ARIAUserInterfaceTooltip"); - case TabPanelRole: - return AXARIAContentGroupText(@"ARIATabPanel"); - default: - return nil; - } -} - -- (NSString *)accessibilityPlatformMathSubscriptKey -{ - ASSERT_NOT_REACHED(); - return nil; -} - -- (NSString *)accessibilityPlatformMathSuperscriptKey -{ - ASSERT_NOT_REACHED(); - return nil; -} - -- (NSArray *)accessibilityMathPostscriptPairs -{ - AccessibilityObject::AccessibilityMathMultiscriptPairs pairs; - m_object->mathPostscripts(pairs); - return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]); -} - -- (NSArray *)accessibilityMathPrescriptPairs -{ - AccessibilityObject::AccessibilityMathMultiscriptPairs pairs; - m_object->mathPrescripts(pairs); - return convertMathPairsToNSArray(pairs, [self accessibilityPlatformMathSubscriptKey], [self accessibilityPlatformMathSuperscriptKey]); -} - -// This is set by DRT when it wants to listen for notifications. -static BOOL accessibilityShouldRepostNotifications; -+ (void)accessibilitySetShouldRepostNotifications:(BOOL)repost -{ - accessibilityShouldRepostNotifications = repost; -} - -- (void)accessibilityPostedNotification:(NSString *)notificationName -{ - if (accessibilityShouldRepostNotifications) { - NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:notificationName, @"notificationName", nil]; - [[NSNotificationCenter defaultCenter] postNotificationName:@"AXDRTNotification" object:self userInfo:userInfo]; - } -} - -@end - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm deleted file mode 100644 index 6b98c3197..000000000 --- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm +++ /dev/null @@ -1,3697 +0,0 @@ -/* - * Copyright (C) 2008, 2009, 2010, 2011 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 Computer, 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. - */ - -#import "config.h" -#import "WebAccessibilityObjectWrapperMac.h" - -#if HAVE(ACCESSIBILITY) - -#import "AXObjectCache.h" -#import "AccessibilityARIAGridRow.h" -#import "AccessibilityList.h" -#import "AccessibilityListBox.h" -#import "AccessibilityRenderObject.h" -#import "AccessibilityScrollView.h" -#import "AccessibilitySpinButton.h" -#import "AccessibilityTable.h" -#import "AccessibilityTableCell.h" -#import "AccessibilityTableColumn.h" -#import "AccessibilityTableRow.h" -#import "Chrome.h" -#import "ChromeClient.h" -#import "ColorMac.h" -#import "ContextMenuController.h" -#import "Editor.h" -#import "Font.h" -#import "Frame.h" -#import "FrameLoaderClient.h" -#import "FrameSelection.h" -#import "HTMLAnchorElement.h" -#import "HTMLAreaElement.h" -#import "HTMLFrameOwnerElement.h" -#import "HTMLImageElement.h" -#import "HTMLInputElement.h" -#import "HTMLNames.h" -#import "HTMLTextAreaElement.h" -#import "LocalizedStrings.h" -#import "Page.h" -#import "RenderTextControl.h" -#import "RenderView.h" -#import "RenderWidget.h" -#import "ScrollView.h" -#import "SimpleFontData.h" -#import "TextCheckerClient.h" -#import "TextCheckingHelper.h" -#import "TextIterator.h" -#import "VisibleUnits.h" -#import "WebCoreFrameView.h" -#import "WebCoreObjCExtras.h" -#import "WebCoreSystemInterface.h" -#import "htmlediting.h" - -using namespace WebCore; -using namespace HTMLNames; -using namespace std; - -// Cell Tables -#ifndef NSAccessibilitySelectedCellsAttribute -#define NSAccessibilitySelectedCellsAttribute @"AXSelectedCells" -#endif - -#ifndef NSAccessibilityVisibleCellsAttribute -#define NSAccessibilityVisibleCellsAttribute @"AXVisibleCells" -#endif - -#ifndef NSAccessibilityRowHeaderUIElementsAttribute -#define NSAccessibilityRowHeaderUIElementsAttribute @"AXRowHeaderUIElements" -#endif - -#ifndef NSAccessibilityRowIndexRangeAttribute -#define NSAccessibilityRowIndexRangeAttribute @"AXRowIndexRange" -#endif - -#ifndef NSAccessibilityColumnIndexRangeAttribute -#define NSAccessibilityColumnIndexRangeAttribute @"AXColumnIndexRange" -#endif - -#ifndef NSAccessibilityCellForColumnAndRowParameterizedAttribute -#define NSAccessibilityCellForColumnAndRowParameterizedAttribute @"AXCellForColumnAndRow" -#endif - -#ifndef NSAccessibilityCellRole -#define NSAccessibilityCellRole @"AXCell" -#endif - -// Lists -#ifndef NSAccessibilityContentListSubrole -#define NSAccessibilityContentListSubrole @"AXContentList" -#endif - -#ifndef NSAccessibilityDefinitionListSubrole -#define NSAccessibilityDefinitionListSubrole @"AXDefinitionList" -#endif - -#ifndef NSAccessibilityDescriptionListSubrole -#define NSAccessibilityDescriptionListSubrole @"AXDescriptionList" -#endif - -// Miscellaneous -#ifndef NSAccessibilityBlockQuoteLevelAttribute -#define NSAccessibilityBlockQuoteLevelAttribute @"AXBlockQuoteLevel" -#endif - -#ifndef NSAccessibilityAccessKeyAttribute -#define NSAccessibilityAccessKeyAttribute @"AXAccessKey" -#endif - -#ifndef NSAccessibilityLanguageAttribute -#define NSAccessibilityLanguageAttribute @"AXLanguage" -#endif - -#ifndef NSAccessibilityRequiredAttribute -#define NSAccessibilityRequiredAttribute @"AXRequired" -#endif - -#ifndef NSAccessibilityInvalidAttribute -#define NSAccessibilityInvalidAttribute @"AXInvalid" -#endif - -#ifndef NSAccessibilityOwnsAttribute -#define NSAccessibilityOwnsAttribute @"AXOwns" -#endif - -#ifndef NSAccessibilityGrabbedAttribute -#define NSAccessibilityGrabbedAttribute @"AXGrabbed" -#endif - -#ifndef NSAccessibilityDropEffectsAttribute -#define NSAccessibilityDropEffectsAttribute @"AXDropEffects" -#endif - -#ifndef NSAccessibilityARIALiveAttribute -#define NSAccessibilityARIALiveAttribute @"AXARIALive" -#endif - -#ifndef NSAccessibilityARIAAtomicAttribute -#define NSAccessibilityARIAAtomicAttribute @"AXARIAAtomic" -#endif - -#ifndef NSAccessibilityARIARelevantAttribute -#define NSAccessibilityARIARelevantAttribute @"AXARIARelevant" -#endif - -#ifndef NSAccessibilityARIABusyAttribute -#define NSAccessibilityARIABusyAttribute @"AXARIABusy" -#endif - -#ifndef NSAccessibilityARIAPosInSetAttribute -#define NSAccessibilityARIAPosInSetAttribute @"AXARIAPosInSet" -#endif - -#ifndef NSAccessibilityARIASetSizeAttribute -#define NSAccessibilityARIASetSizeAttribute @"AXARIASetSize" -#endif - -#ifndef NSAccessibilityLoadingProgressAttribute -#define NSAccessibilityLoadingProgressAttribute @"AXLoadingProgress" -#endif - -#ifndef NSAccessibilityHasPopupAttribute -#define NSAccessibilityHasPopupAttribute @"AXHasPopup" -#endif - -#ifndef NSAccessibilityPlaceholderValueAttribute -#define NSAccessibilityPlaceholderValueAttribute @"AXPlaceholderValue" -#endif - -// Search -#ifndef NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute -#define NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute @"AXUIElementsForSearchPredicate" -#endif - -// Search Keys -#ifndef NSAccessibilityAnyTypeSearchKey -#define NSAccessibilityAnyTypeSearchKey @"AXAnyTypeSearchKey" -#endif - -#ifndef NSAccessibilityBlockquoteSameLevelSearchKey -#define NSAccessibilityBlockquoteSameLevelSearchKey @"AXBlockquoteSameLevelSearchKey" -#endif - -#ifndef NSAccessibilityBlockquoteSearchKey -#define NSAccessibilityBlockquoteSearchKey @"AXBlockquoteSearchKey" -#endif - -#ifndef NSAccessibilityBoldFontSearchKey -#define NSAccessibilityBoldFontSearchKey @"AXBoldFontSearchKey" -#endif - -#ifndef NSAccessibilityButtonSearchKey -#define NSAccessibilityButtonSearchKey @"AXButtonSearchKey" -#endif - -#ifndef NSAccessibilityCheckBoxSearchKey -#define NSAccessibilityCheckBoxSearchKey @"AXCheckBoxSearchKey" -#endif - -#ifndef NSAccessibilityControlSearchKey -#define NSAccessibilityControlSearchKey @"AXControlSearchKey" -#endif - -#ifndef NSAccessibilityDifferentTypeSearchKey -#define NSAccessibilityDifferentTypeSearchKey @"AXDifferentTypeSearchKey" -#endif - -#ifndef NSAccessibilityFontChangeSearchKey -#define NSAccessibilityFontChangeSearchKey @"AXFontChangeSearchKey" -#endif - -#ifndef NSAccessibilityFontColorChangeSearchKey -#define NSAccessibilityFontColorChangeSearchKey @"AXFontColorChangeSearchKey" -#endif - -#ifndef NSAccessibilityFrameSearchKey -#define NSAccessibilityFrameSearchKey @"AXFrameSearchKey" -#endif - -#ifndef NSAccessibilityGraphicSearchKey -#define NSAccessibilityGraphicSearchKey @"AXGraphicSearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel1SearchKey -#define NSAccessibilityHeadingLevel1SearchKey @"AXHeadingLevel1SearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel2SearchKey -#define NSAccessibilityHeadingLevel2SearchKey @"AXHeadingLevel2SearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel3SearchKey -#define NSAccessibilityHeadingLevel3SearchKey @"AXHeadingLevel3SearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel4SearchKey -#define NSAccessibilityHeadingLevel4SearchKey @"AXHeadingLevel4SearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel5SearchKey -#define NSAccessibilityHeadingLevel5SearchKey @"AXHeadingLevel5SearchKey" -#endif - -#ifndef NSAccessibilityHeadingLevel6SearchKey -#define NSAccessibilityHeadingLevel6SearchKey @"AXHeadingLevel6SearchKey" -#endif - -#ifndef NSAccessibilityHeadingSameLevelSearchKey -#define NSAccessibilityHeadingSameLevelSearchKey @"AXHeadingSameLevelSearchKey" -#endif - -#ifndef NSAccessibilityHeadingSearchKey -#define NSAccessibilityHeadingSearchKey @"AXHeadingSearchKey" -#endif - -#ifndef NSAccessibilityHighlightedSearchKey -#define NSAccessibilityHighlightedSearchKey @"AXHighlightedSearchKey" -#endif - -#ifndef NSAccessibilityItalicFontSearchKey -#define NSAccessibilityItalicFontSearchKey @"AXItalicFontSearchKey" -#endif - -#ifndef NSAccessibilityLandmarkSearchKey -#define NSAccessibilityLandmarkSearchKey @"AXLandmarkSearchKey" -#endif - -#ifndef NSAccessibilityLinkSearchKey -#define NSAccessibilityLinkSearchKey @"AXLinkSearchKey" -#endif - -#ifndef NSAccessibilityListSearchKey -#define NSAccessibilityListSearchKey @"AXListSearchKey" -#endif - -#ifndef NSAccessibilityLiveRegionSearchKey -#define NSAccessibilityLiveRegionSearchKey @"AXLiveRegionSearchKey" -#endif - -#ifndef NSAccessibilityMisspelledWordSearchKey -#define NSAccessibilityMisspelledWordSearchKey @"AXMisspelledWordSearchKey" -#endif - -#ifndef NSAccessibilityPlainTextSearchKey -#define NSAccessibilityPlainTextSearchKey @"AXPlainTextSearchKey" -#endif - -#ifndef NSAccessibilityRadioGroupSearchKey -#define NSAccessibilityRadioGroupSearchKey @"AXRadioGroupSearchKey" -#endif - -#ifndef NSAccessibilitySameTypeSearchKey -#define NSAccessibilitySameTypeSearchKey @"AXSameTypeSearchKey" -#endif - -#ifndef NSAccessibilityStaticTextSearchKey -#define NSAccessibilityStaticTextSearchKey @"AXStaticTextSearchKey" -#endif - -#ifndef NSAccessibilityStyleChangeSearchKey -#define NSAccessibilityStyleChangeSearchKey @"AXStyleChangeSearchKey" -#endif - -#ifndef NSAccessibilityTableSameLevelSearchKey -#define NSAccessibilityTableSameLevelSearchKey @"AXTableSameLevelSearchKey" -#endif - -#ifndef NSAccessibilityTableSearchKey -#define NSAccessibilityTableSearchKey @"AXTableSearchKey" -#endif - -#ifndef NSAccessibilityTextFieldSearchKey -#define NSAccessibilityTextFieldSearchKey @"AXTextFieldSearchKey" -#endif - -#ifndef NSAccessibilityUnderlineSearchKey -#define NSAccessibilityUnderlineSearchKey @"AXUnderlineSearchKey" -#endif - -#ifndef NSAccessibilityUnvisitedLinkSearchKey -#define NSAccessibilityUnvisitedLinkSearchKey @"AXUnvisitedLinkSearchKey" -#endif - -#ifndef NSAccessibilityVisitedLinkSearchKey -#define NSAccessibilityVisitedLinkSearchKey @"AXVisitedLinkSearchKey" -#endif - -#define NSAccessibilityTextMarkerIsValidParameterizedAttribute @"AXTextMarkerIsValid" -#define NSAccessibilityIndexForTextMarkerParameterizedAttribute @"AXIndexForTextMarker" -#define NSAccessibilityTextMarkerForIndexParameterizedAttribute @"AXTextMarkerForIndex" - -#ifndef NSAccessibilityScrollToVisibleAction -#define NSAccessibilityScrollToVisibleAction @"AXScrollToVisible" -#endif - -#ifndef NSAccessibilityPathAttribute -#define NSAccessibilityPathAttribute @"AXPath" -#endif - -// Math attributes -#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand" -#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex" -#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator" -#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator" -#define NSAccessibilityMathBaseAttribute @"AXMathBase" -#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript" -#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript" -#define NSAccessibilityMathUnderAttribute @"AXMathUnder" -#define NSAccessibilityMathOverAttribute @"AXMathOver" -#define NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen" -#define NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose" -#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness" -#define NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts" -#define NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts" - -@implementation WebAccessibilityObjectWrapper - -- (void)unregisterUniqueIdForUIElement -{ - wkUnregisterUniqueIdForElement(self); -} - -- (void)detach -{ - // Send unregisterUniqueIdForUIElement unconditionally because if it is - // ever accidentally not done (via other bugs in our AX implementation) you - // end up with a crash like <rdar://problem/4273149>. It is safe and not - // expensive to send even if the object is not registered. - [self unregisterUniqueIdForUIElement]; - [super detach]; -} - -- (id)attachmentView -{ - ASSERT(m_object->isAttachment()); - Widget* widget = m_object->widgetForAttachmentView(); - if (!widget) - return nil; - return NSAccessibilityUnignoredDescendant(widget->platformWidget()); -} - -#pragma mark SystemInterface wrappers - -static inline id CFAutoreleaseHelper(CFTypeRef obj) -{ - if (obj) - CFMakeCollectable(obj); - [(id)obj autorelease]; - return (id)obj; -} - -static inline BOOL AXObjectIsTextMarker(id obj) -{ - return obj != nil && CFGetTypeID(obj) == wkGetAXTextMarkerTypeID(); -} - -static inline BOOL AXObjectIsTextMarkerRange(id obj) -{ - return obj != nil && CFGetTypeID(obj) == wkGetAXTextMarkerRangeTypeID(); -} - -static id AXTextMarkerRange(id startMarker, id endMarker) -{ - ASSERT(startMarker != nil); - ASSERT(endMarker != nil); - ASSERT(CFGetTypeID(startMarker) == wkGetAXTextMarkerTypeID()); - ASSERT(CFGetTypeID(endMarker) == wkGetAXTextMarkerTypeID()); - return CFAutoreleaseHelper(wkCreateAXTextMarkerRange((CFTypeRef)startMarker, (CFTypeRef)endMarker)); -} - -static id AXTextMarkerRangeStart(id range) -{ - ASSERT(range != nil); - ASSERT(CFGetTypeID(range) == wkGetAXTextMarkerRangeTypeID()); - return CFAutoreleaseHelper(wkCopyAXTextMarkerRangeStart(range)); -} - -static id AXTextMarkerRangeEnd(id range) -{ - ASSERT(range != nil); - ASSERT(CFGetTypeID(range) == wkGetAXTextMarkerRangeTypeID()); - return CFAutoreleaseHelper(wkCopyAXTextMarkerRangeEnd(range)); -} - -#pragma mark Search helpers - -typedef HashMap<String, AccessibilitySearchKey> AccessibilitySearchKeyMap; - -struct SearchKeyEntry { - String key; - AccessibilitySearchKey value; -}; - -static AccessibilitySearchKeyMap* createAccessibilitySearchKeyMap() -{ - const SearchKeyEntry searchKeys[] = { - { NSAccessibilityAnyTypeSearchKey, AnyTypeSearchKey }, - { NSAccessibilityBlockquoteSameLevelSearchKey, BlockquoteSameLevelSearchKey }, - { NSAccessibilityBlockquoteSearchKey, BlockquoteSearchKey }, - { NSAccessibilityBoldFontSearchKey, BoldFontSearchKey }, - { NSAccessibilityButtonSearchKey, ButtonSearchKey }, - { NSAccessibilityCheckBoxSearchKey, CheckBoxSearchKey }, - { NSAccessibilityControlSearchKey, ControlSearchKey }, - { NSAccessibilityDifferentTypeSearchKey, DifferentTypeSearchKey }, - { NSAccessibilityFontChangeSearchKey, FontChangeSearchKey }, - { NSAccessibilityFontColorChangeSearchKey, FontColorChangeSearchKey }, - { NSAccessibilityFrameSearchKey, FrameSearchKey }, - { NSAccessibilityGraphicSearchKey, GraphicSearchKey }, - { NSAccessibilityHeadingLevel1SearchKey, HeadingLevel1SearchKey }, - { NSAccessibilityHeadingLevel2SearchKey, HeadingLevel2SearchKey }, - { NSAccessibilityHeadingLevel3SearchKey, HeadingLevel3SearchKey }, - { NSAccessibilityHeadingLevel4SearchKey, HeadingLevel4SearchKey }, - { NSAccessibilityHeadingLevel5SearchKey, HeadingLevel5SearchKey }, - { NSAccessibilityHeadingLevel6SearchKey, HeadingLevel6SearchKey }, - { NSAccessibilityHeadingSameLevelSearchKey, HeadingSameLevelSearchKey }, - { NSAccessibilityHeadingSearchKey, HeadingSearchKey }, - { NSAccessibilityHighlightedSearchKey, HighlightedSearchKey }, - { NSAccessibilityItalicFontSearchKey, ItalicFontSearchKey }, - { NSAccessibilityLandmarkSearchKey, LandmarkSearchKey }, - { NSAccessibilityLinkSearchKey, LinkSearchKey }, - { NSAccessibilityListSearchKey, ListSearchKey }, - { NSAccessibilityLiveRegionSearchKey, LiveRegionSearchKey }, - { NSAccessibilityMisspelledWordSearchKey, MisspelledWordSearchKey }, - { NSAccessibilityPlainTextSearchKey, PlainTextSearchKey }, - { NSAccessibilityRadioGroupSearchKey, RadioGroupSearchKey }, - { NSAccessibilitySameTypeSearchKey, SameTypeSearchKey }, - { NSAccessibilityStaticTextSearchKey, StaticTextSearchKey }, - { NSAccessibilityStyleChangeSearchKey, StyleChangeSearchKey }, - { NSAccessibilityTableSameLevelSearchKey, TableSameLevelSearchKey }, - { NSAccessibilityTableSearchKey, TableSearchKey }, - { NSAccessibilityTextFieldSearchKey, TextFieldSearchKey }, - { NSAccessibilityUnderlineSearchKey, UnderlineSearchKey }, - { NSAccessibilityUnvisitedLinkSearchKey, UnvisitedLinkSearchKey }, - { NSAccessibilityVisitedLinkSearchKey, VisitedLinkSearchKey } - }; - - AccessibilitySearchKeyMap* searchKeyMap = new AccessibilitySearchKeyMap; - for (size_t i = 0; i < WTF_ARRAY_LENGTH(searchKeys); i++) - searchKeyMap->set(searchKeys[i].key, searchKeys[i].value); - - return searchKeyMap; -} - -static AccessibilitySearchKey accessibilitySearchKeyForString(const String& value) -{ - if (value.isEmpty()) - return AnyTypeSearchKey; - - static const AccessibilitySearchKeyMap* searchKeyMap = createAccessibilitySearchKeyMap(); - - AccessibilitySearchKey searchKey = searchKeyMap->get(value); - - return searchKey ? searchKey : AnyTypeSearchKey; -} - -#pragma mark Text Marker helpers - -static id textMarkerForVisiblePosition(AXObjectCache* cache, const VisiblePosition& visiblePos) -{ - ASSERT(cache); - - TextMarkerData textMarkerData; - cache->textMarkerDataForVisiblePosition(textMarkerData, visiblePos); - if (!textMarkerData.axID) - return nil; - - return CFAutoreleaseHelper(wkCreateAXTextMarker(&textMarkerData, sizeof(textMarkerData))); -} - -- (id)textMarkerForVisiblePosition:(const VisiblePosition &)visiblePos -{ - return textMarkerForVisiblePosition(m_object->axObjectCache(), visiblePos); -} - -static VisiblePosition visiblePositionForTextMarker(AXObjectCache* cache, CFTypeRef textMarker) -{ - ASSERT(cache); - - if (!textMarker) - return VisiblePosition(); - TextMarkerData textMarkerData; - if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData))) - return VisiblePosition(); - - return cache->visiblePositionForTextMarkerData(textMarkerData); -} - -- (VisiblePosition)visiblePositionForTextMarker:(id)textMarker -{ - return visiblePositionForTextMarker(m_object->axObjectCache(), textMarker); -} - -static VisiblePosition visiblePositionForStartOfTextMarkerRange(AXObjectCache *cache, id textMarkerRange) -{ - return visiblePositionForTextMarker(cache, AXTextMarkerRangeStart(textMarkerRange)); -} - -static VisiblePosition visiblePositionForEndOfTextMarkerRange(AXObjectCache *cache, id textMarkerRange) -{ - return visiblePositionForTextMarker(cache, AXTextMarkerRangeEnd(textMarkerRange)); -} - -static id textMarkerRangeFromMarkers(id textMarker1, id textMarker2) -{ - if (!textMarker1 || !textMarker2) - return nil; - - return AXTextMarkerRange(textMarker1, textMarker2); -} - -// When modifying attributed strings, the range can come from a source which may provide faulty information (e.g. the spell checker). -// To protect against such cases the range should be validated before adding or removing attributes. -static BOOL AXAttributedStringRangeIsValid(NSAttributedString* attrString, NSRange range) -{ - return (range.location < [attrString length] && NSMaxRange(range) <= [attrString length]); -} - -static void AXAttributeStringSetFont(NSMutableAttributedString* attrString, NSString* attribute, NSFont* font, NSRange range) -{ - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - if (font) { - NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: - [font fontName] , NSAccessibilityFontNameKey, - [font familyName] , NSAccessibilityFontFamilyKey, - [font displayName] , NSAccessibilityVisibleNameKey, - [NSNumber numberWithFloat:[font pointSize]] , NSAccessibilityFontSizeKey, - nil]; - - [attrString addAttribute:attribute value:dict range:range]; - } else - [attrString removeAttribute:attribute range:range]; - -} - -static CGColorRef CreateCGColorIfDifferent(NSColor* nsColor, CGColorRef existingColor) -{ - // get color information assuming NSDeviceRGBColorSpace - NSColor* rgbColor = [nsColor colorUsingColorSpaceName:NSDeviceRGBColorSpace]; - if (rgbColor == nil) - rgbColor = [NSColor blackColor]; - CGFloat components[4]; - [rgbColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; - - // create a new CGColorRef to return - CGColorSpaceRef cgColorSpace = CGColorSpaceCreateDeviceRGB(); - CGColorRef cgColor = CGColorCreate(cgColorSpace, components); - CGColorSpaceRelease(cgColorSpace); - - // check for match with existing color - if (existingColor && CGColorEqualToColor(cgColor, existingColor)) { - CGColorRelease(cgColor); - cgColor = 0; - } - - return cgColor; -} - -static void AXAttributeStringSetColor(NSMutableAttributedString* attrString, NSString* attribute, NSColor* color, NSRange range) -{ - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - if (color) { - CGColorRef existingColor = (CGColorRef) [attrString attribute:attribute atIndex:range.location effectiveRange:nil]; - CGColorRef cgColor = CreateCGColorIfDifferent(color, existingColor); - if (cgColor) { - [attrString addAttribute:attribute value:(id)cgColor range:range]; - CGColorRelease(cgColor); - } - } else - [attrString removeAttribute:attribute range:range]; -} - -static void AXAttributeStringSetNumber(NSMutableAttributedString* attrString, NSString* attribute, NSNumber* number, NSRange range) -{ - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - if (number) - [attrString addAttribute:attribute value:number range:range]; - else - [attrString removeAttribute:attribute range:range]; -} - -static void AXAttributeStringSetStyle(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - RenderStyle* style = renderer->style(); - - // set basic font info - AXAttributeStringSetFont(attrString, NSAccessibilityFontTextAttribute, style->font().primaryFont()->getNSFont(), range); - - // set basic colors - AXAttributeStringSetColor(attrString, NSAccessibilityForegroundColorTextAttribute, nsColor(style->visitedDependentColor(CSSPropertyColor)), range); - AXAttributeStringSetColor(attrString, NSAccessibilityBackgroundColorTextAttribute, nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)), range); - - // set super/sub scripting - EVerticalAlign alignment = style->verticalAlign(); - if (alignment == SUB) - AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:(-1)], range); - else if (alignment == SUPER) - AXAttributeStringSetNumber(attrString, NSAccessibilitySuperscriptTextAttribute, [NSNumber numberWithInt:1], range); - else - [attrString removeAttribute:NSAccessibilitySuperscriptTextAttribute range:range]; - - // set shadow - if (style->textShadow()) - AXAttributeStringSetNumber(attrString, NSAccessibilityShadowTextAttribute, [NSNumber numberWithBool:YES], range); - else - [attrString removeAttribute:NSAccessibilityShadowTextAttribute range:range]; - - // set underline and strikethrough - int decor = style->textDecorationsInEffect(); - if ((decor & TextDecorationUnderline) == 0) { - [attrString removeAttribute:NSAccessibilityUnderlineTextAttribute range:range]; - [attrString removeAttribute:NSAccessibilityUnderlineColorTextAttribute range:range]; - } - - if ((decor & TextDecorationLineThrough) == 0) { - [attrString removeAttribute:NSAccessibilityStrikethroughTextAttribute range:range]; - [attrString removeAttribute:NSAccessibilityStrikethroughColorTextAttribute range:range]; - } - - if ((decor & (TextDecorationUnderline | TextDecorationLineThrough)) != 0) { - // find colors using quirk mode approach (strict mode would use current - // color for all but the root line box, which would use getTextDecorationColors) - Color underline, overline, linethrough; - renderer->getTextDecorationColors(decor, underline, overline, linethrough); - - if ((decor & TextDecorationUnderline) != 0) { - AXAttributeStringSetNumber(attrString, NSAccessibilityUnderlineTextAttribute, [NSNumber numberWithBool:YES], range); - AXAttributeStringSetColor(attrString, NSAccessibilityUnderlineColorTextAttribute, nsColor(underline), range); - } - - if ((decor & TextDecorationLineThrough) != 0) { - AXAttributeStringSetNumber(attrString, NSAccessibilityStrikethroughTextAttribute, [NSNumber numberWithBool:YES], range); - AXAttributeStringSetColor(attrString, NSAccessibilityStrikethroughColorTextAttribute, nsColor(linethrough), range); - } - } - - // Indicate background highlighting. - for (Node* node = renderer->node(); node; node = node->parentNode()) { - if (node->hasTagName(markTag)) - AXAttributeStringSetNumber(attrString, @"AXHighlight", [NSNumber numberWithBool:YES], range); - } -} - -static void AXAttributeStringSetBlockquoteLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - AccessibilityObject* obj = renderer->document()->axObjectCache()->getOrCreate(renderer); - int quoteLevel = obj->blockquoteLevel(); - - if (quoteLevel) - [attrString addAttribute:NSAccessibilityBlockQuoteLevelAttribute value:[NSNumber numberWithInt:quoteLevel] range:range]; - else - [attrString removeAttribute:NSAccessibilityBlockQuoteLevelAttribute range:range]; -} - -static void AXAttributeStringSetSpelling(NSMutableAttributedString* attrString, Node* node, const UChar* chars, int charLength, NSRange range) -{ - if (unifiedTextCheckerEnabled(node->document()->frame())) { - // Check the spelling directly since document->markersForNode() does not store the misspelled marking when the cursor is in a word. - TextCheckerClient* checker = node->document()->frame()->editor().textChecker(); - - // checkTextOfParagraph is the only spelling/grammar checker implemented in WK1 and WK2 - Vector<TextCheckingResult> results; - checkTextOfParagraph(checker, chars, charLength, TextCheckingTypeSpelling, results); - - size_t size = results.size(); - NSNumber* trueValue = [NSNumber numberWithBool:YES]; - for (unsigned i = 0; i < size; i++) { - const TextCheckingResult& result = results[i]; - AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, trueValue, NSMakeRange(result.location + range.location, result.length)); -#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 - AXAttributeStringSetNumber(attrString, NSAccessibilityMarkedMisspelledTextAttribute, trueValue, NSMakeRange(result.location + range.location, result.length)); -#endif - } - return; - } - - int currentPosition = 0; - while (charLength > 0) { - const UChar* charData = chars + currentPosition; - TextCheckerClient* checker = node->document()->frame()->editor().textChecker(); - - int misspellingLocation = -1; - int misspellingLength = 0; - checker->checkSpellingOfString(charData, charLength, &misspellingLocation, &misspellingLength); - if (misspellingLocation == -1 || !misspellingLength) - break; - - NSRange spellRange = NSMakeRange(range.location + currentPosition + misspellingLocation, misspellingLength); - AXAttributeStringSetNumber(attrString, NSAccessibilityMisspelledTextAttribute, [NSNumber numberWithBool:YES], spellRange); -#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 - AXAttributeStringSetNumber(attrString, NSAccessibilityMarkedMisspelledTextAttribute, [NSNumber numberWithBool:YES], spellRange); -#endif - charLength -= (misspellingLocation + misspellingLength); - currentPosition += (misspellingLocation + misspellingLength); - } -} - -static void AXAttributeStringSetHeadingLevel(NSMutableAttributedString* attrString, RenderObject* renderer, NSRange range) -{ - if (!renderer) - return; - - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - // Sometimes there are objects between the text and the heading. - // In those cases the parent hierarchy should be queried to see if there is a heading level. - int parentHeadingLevel = 0; - AccessibilityObject* parentObject = renderer->document()->axObjectCache()->getOrCreate(renderer->parent()); - for (; parentObject; parentObject = parentObject->parentObject()) { - parentHeadingLevel = parentObject->headingLevel(); - if (parentHeadingLevel) - break; - } - - if (parentHeadingLevel) - [attrString addAttribute:@"AXHeadingLevel" value:[NSNumber numberWithInt:parentHeadingLevel] range:range]; - else - [attrString removeAttribute:@"AXHeadingLevel" range:range]; -} - -static void AXAttributeStringSetElement(NSMutableAttributedString* attrString, NSString* attribute, AccessibilityObject* object, NSRange range) -{ - if (!AXAttributedStringRangeIsValid(attrString, range)) - return; - - if (object && object->isAccessibilityRenderObject()) { - // make a serializable AX object - - RenderObject* renderer = static_cast<AccessibilityRenderObject*>(object)->renderer(); - if (!renderer) - return; - - Document* doc = renderer->document(); - if (!doc) - return; - - AXObjectCache* cache = doc->axObjectCache(); - if (!cache) - return; - - AXUIElementRef axElement = wkCreateAXUIElementRef(object->wrapper()); - if (axElement) { - [attrString addAttribute:attribute value:(id)axElement range:range]; - CFRelease(axElement); - } - } else - [attrString removeAttribute:attribute range:range]; -} - -static void AXAttributedStringAppendText(NSMutableAttributedString* attrString, Node* node, const UChar* chars, int length) -{ - // skip invisible text - if (!node->renderer()) - return; - - // easier to calculate the range before appending the string - NSRange attrStringRange = NSMakeRange([attrString length], length); - - // append the string from this node - [[attrString mutableString] appendString:[NSString stringWithCharacters:chars length:length]]; - - // add new attributes and remove irrelevant inherited ones - // NOTE: color attributes are handled specially because -[NSMutableAttributedString addAttribute: value: range:] does not merge - // identical colors. Workaround is to not replace an existing color attribute if it matches what we are adding. This also means - // we cannot just pre-remove all inherited attributes on the appended string, so we have to remove the irrelevant ones individually. - - // remove inherited attachment from prior AXAttributedStringAppendReplaced - [attrString removeAttribute:NSAccessibilityAttachmentTextAttribute range:attrStringRange]; -#if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 - [attrString removeAttribute:NSAccessibilityMarkedMisspelledTextAttribute range:attrStringRange]; -#endif - [attrString removeAttribute:NSAccessibilityMisspelledTextAttribute range:attrStringRange]; - - // set new attributes - AXAttributeStringSetStyle(attrString, node->renderer(), attrStringRange); - AXAttributeStringSetHeadingLevel(attrString, node->renderer(), attrStringRange); - AXAttributeStringSetBlockquoteLevel(attrString, node->renderer(), attrStringRange); - AXAttributeStringSetElement(attrString, NSAccessibilityLinkTextAttribute, AccessibilityObject::anchorElementForNode(node), attrStringRange); - - // do spelling last because it tends to break up the range - AXAttributeStringSetSpelling(attrString, node, chars, length, attrStringRange); -} - -static NSString* nsStringForReplacedNode(Node* replacedNode) -{ - // we should always be given a rendered node and a replaced node, but be safe - // replaced nodes are either attachments (widgets) or images - if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode()) { - ASSERT_NOT_REACHED(); - return nil; - } - - // create an AX object, but skip it if it is not supposed to be seen - RefPtr<AccessibilityObject> obj = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); - if (obj->accessibilityIsIgnored()) - return nil; - - // use the attachmentCharacter to represent the replaced node - const UniChar attachmentChar = NSAttachmentCharacter; - return [NSString stringWithCharacters:&attachmentChar length:1]; -} - -- (NSAttributedString*)doAXAttributedStringForTextMarkerRange:(id)textMarkerRange -{ - if (!m_object) - return nil; - - // extract the start and end VisiblePosition - VisiblePosition startVisiblePosition = visiblePositionForStartOfTextMarkerRange(m_object->axObjectCache(), textMarkerRange); - if (startVisiblePosition.isNull()) - return nil; - - VisiblePosition endVisiblePosition = visiblePositionForEndOfTextMarkerRange(m_object->axObjectCache(), textMarkerRange); - if (endVisiblePosition.isNull()) - return nil; - - VisiblePositionRange visiblePositionRange(startVisiblePosition, endVisiblePosition); - // iterate over the range to build the AX attributed string - NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] init]; - TextIterator it(makeRange(startVisiblePosition, endVisiblePosition).get()); - while (!it.atEnd()) { - // locate the node and starting offset for this range - int exception = 0; - Node* node = it.range()->startContainer(exception); - ASSERT(node == it.range()->endContainer(exception)); - int offset = it.range()->startOffset(exception); - - // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX) - if (it.length() != 0) { - // Add the text of the list marker item if necessary. - String listMarkerText = m_object->listMarkerTextForNodeAndPosition(node, VisiblePosition(it.range()->startPosition())); - if (!listMarkerText.isEmpty()) - AXAttributedStringAppendText(attrString, node, listMarkerText.characters(), listMarkerText.length()); - - AXAttributedStringAppendText(attrString, node, it.characters(), it.length()); - } else { - Node* replacedNode = node->childNode(offset); - NSString *attachmentString = nsStringForReplacedNode(replacedNode); - if (attachmentString) { - NSRange attrStringRange = NSMakeRange([attrString length], [attachmentString length]); - - // append the placeholder string - [[attrString mutableString] appendString:attachmentString]; - - // remove all inherited attributes - [attrString setAttributes:nil range:attrStringRange]; - - // add the attachment attribute - AccessibilityObject* obj = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer()); - AXAttributeStringSetElement(attrString, NSAccessibilityAttachmentTextAttribute, obj, attrStringRange); - } - } - it.advance(); - } - - return [attrString autorelease]; -} - -static id textMarkerRangeFromVisiblePositions(AXObjectCache *cache, VisiblePosition startPosition, VisiblePosition endPosition) -{ - id startTextMarker = textMarkerForVisiblePosition(cache, startPosition); - id endTextMarker = textMarkerForVisiblePosition(cache, endPosition); - return textMarkerRangeFromMarkers(startTextMarker, endTextMarker); -} - -- (id)textMarkerRangeFromVisiblePositions:(VisiblePosition)startPosition endPosition:(VisiblePosition)endPosition -{ - return textMarkerRangeFromVisiblePositions(m_object->axObjectCache(), startPosition, endPosition); -} - -- (NSArray*)accessibilityActionNames -{ - if (![self updateObjectBackingStore]) - return nil; - - // All elements should get ShowMenu and ScrollToVisible. - // But certain earlier VoiceOver versions do not support scroll to visible, and it confuses them to see it in the list. -#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090 - static NSArray *defaultElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityShowMenuAction, nil]; -#else - static NSArray *defaultElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction, nil]; -#endif - - // Action elements allow Press. - // The order is important to VoiceOver, which expects the 'default' action to be the first action. In this case the default action should be press. -#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090 - static NSArray *actionElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction, NSAccessibilityShowMenuAction, nil]; -#else - static NSArray *actionElementActions = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction, NSAccessibilityShowMenuAction, NSAccessibilityScrollToVisibleAction, nil]; -#endif - - // Menu elements allow Press and Cancel. - static NSArray *menuElementActions = [[actionElementActions arrayByAddingObject:NSAccessibilityCancelAction] retain]; - - // Slider elements allow Increment/Decrement. - static NSArray *sliderActions = [[defaultElementActions arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:NSAccessibilityIncrementAction, NSAccessibilityDecrementAction, nil]] retain]; - - NSArray *actions; - if (m_object->actionElement() || m_object->isButton()) - actions = actionElementActions; - else if (m_object->isMenuRelated()) - actions = menuElementActions; - else if (m_object->isSlider()) - actions = sliderActions; - else if (m_object->isAttachment()) - actions = [[self attachmentView] accessibilityActionNames]; - else - actions = defaultElementActions; - - return actions; -} - -- (NSArray*)additionalAccessibilityAttributeNames -{ - if (!m_object) - return nil; - - NSMutableArray *additional = [NSMutableArray array]; - if (m_object->supportsARIAOwns()) - [additional addObject:NSAccessibilityOwnsAttribute]; - - if (m_object->supportsARIAExpanded()) - [additional addObject:NSAccessibilityExpandedAttribute]; - - if (m_object->isScrollbar()) - [additional addObject:NSAccessibilityOrientationAttribute]; - - if (m_object->supportsARIADragging()) - [additional addObject:NSAccessibilityGrabbedAttribute]; - - if (m_object->supportsARIADropping()) - [additional addObject:NSAccessibilityDropEffectsAttribute]; - - if (m_object->isAccessibilityTable() && static_cast<AccessibilityTable*>(m_object)->supportsSelectedRows()) - [additional addObject:NSAccessibilitySelectedRowsAttribute]; - - if (m_object->supportsARIALiveRegion()) { - [additional addObject:NSAccessibilityARIALiveAttribute]; - [additional addObject:NSAccessibilityARIARelevantAttribute]; - } - - if (m_object->supportsARIASetSize()) - [additional addObject:NSAccessibilityARIASetSizeAttribute]; - if (m_object->supportsARIAPosInSet()) - [additional addObject:NSAccessibilityARIAPosInSetAttribute]; - - if (m_object->sortDirection() != SortDirectionNone) - [additional addObject:NSAccessibilitySortDirectionAttribute]; - - // If an object is a child of a live region, then add these - if (m_object->isInsideARIALiveRegion()) - [additional addObject:NSAccessibilityARIAAtomicAttribute]; - // All objects should expose the ARIA busy attribute (ARIA 1.1 with ISSUE-538). - [additional addObject:NSAccessibilityARIABusyAttribute]; - - // Popup buttons on the Mac expose the value attribute. - if (m_object->isPopUpButton()) { - [additional addObject:NSAccessibilityValueAttribute]; - } - - if (m_object->supportsRequiredAttribute()) { - [additional addObject:NSAccessibilityRequiredAttribute]; - } - - if (m_object->ariaHasPopup()) - [additional addObject:NSAccessibilityHasPopupAttribute]; - - if (m_object->isMathRoot()) { - // The index of a square root is always known, so there's no object associated with it. - if (!m_object->isMathSquareRoot()) - [additional addObject:NSAccessibilityMathRootIndexAttribute]; - [additional addObject:NSAccessibilityMathRootRadicandAttribute]; - } else if (m_object->isMathFraction()) { - [additional addObject:NSAccessibilityMathFractionNumeratorAttribute]; - [additional addObject:NSAccessibilityMathFractionDenominatorAttribute]; - [additional addObject:NSAccessibilityMathLineThicknessAttribute]; - } else if (m_object->isMathSubscriptSuperscript()) { - [additional addObject:NSAccessibilityMathBaseAttribute]; - [additional addObject:NSAccessibilityMathSubscriptAttribute]; - [additional addObject:NSAccessibilityMathSuperscriptAttribute]; - } else if (m_object->isMathUnderOver()) { - [additional addObject:NSAccessibilityMathBaseAttribute]; - [additional addObject:NSAccessibilityMathUnderAttribute]; - [additional addObject:NSAccessibilityMathOverAttribute]; - } else if (m_object->isMathFenced()) { - [additional addObject:NSAccessibilityMathFencedOpenAttribute]; - [additional addObject:NSAccessibilityMathFencedCloseAttribute]; - } else if (m_object->isMathMultiscript()) { - [additional addObject:NSAccessibilityMathBaseAttribute]; - [additional addObject:NSAccessibilityMathPrescriptsAttribute]; - [additional addObject:NSAccessibilityMathPostscriptsAttribute]; - } - - if (m_object->supportsPath()) - [additional addObject:NSAccessibilityPathAttribute]; - - return additional; -} - -- (NSArray*)accessibilityAttributeNames -{ - if (![self updateObjectBackingStore]) - return nil; - - if (m_object->isAttachment()) - return [[self attachmentView] accessibilityAttributeNames]; - - static NSArray* attributes = nil; - static NSArray* anchorAttrs = nil; - static NSArray* webAreaAttrs = nil; - static NSArray* textAttrs = nil; - static NSArray* listAttrs = nil; - static NSArray* listBoxAttrs = nil; - static NSArray* rangeAttrs = nil; - static NSArray* commonMenuAttrs = nil; - static NSArray* menuAttrs = nil; - static NSArray* menuBarAttrs = nil; - static NSArray* menuItemAttrs = nil; - static NSArray* menuButtonAttrs = nil; - static NSArray* controlAttrs = nil; - static NSArray* tableAttrs = nil; - static NSArray* tableRowAttrs = nil; - static NSArray* tableColAttrs = nil; - static NSArray* tableCellAttrs = nil; - static NSArray* groupAttrs = nil; - static NSArray* inputImageAttrs = nil; - static NSArray* passwordFieldAttrs = nil; - static NSArray* tabListAttrs = nil; - static NSArray* comboBoxAttrs = nil; - static NSArray* outlineAttrs = nil; - static NSArray* outlineRowAttrs = nil; - static NSArray* buttonAttrs = nil; - static NSArray* scrollViewAttrs = nil; - static NSArray* incrementorAttrs = nil; - NSMutableArray* tempArray; - if (attributes == nil) { - attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, - NSAccessibilitySubroleAttribute, - NSAccessibilityRoleDescriptionAttribute, - NSAccessibilityChildrenAttribute, - NSAccessibilityHelpAttribute, - NSAccessibilityParentAttribute, - NSAccessibilityPositionAttribute, - NSAccessibilitySizeAttribute, - NSAccessibilityTitleAttribute, - NSAccessibilityDescriptionAttribute, - NSAccessibilityValueAttribute, - NSAccessibilityFocusedAttribute, - NSAccessibilityEnabledAttribute, - NSAccessibilityWindowAttribute, - @"AXSelectedTextMarkerRange", - @"AXStartTextMarker", - @"AXEndTextMarker", - @"AXVisited", - NSAccessibilityLinkedUIElementsAttribute, - NSAccessibilitySelectedAttribute, - NSAccessibilityBlockQuoteLevelAttribute, - NSAccessibilityTopLevelUIElementAttribute, - nil]; - } - if (commonMenuAttrs == nil) { - commonMenuAttrs = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, - NSAccessibilityRoleDescriptionAttribute, - NSAccessibilityChildrenAttribute, - NSAccessibilityParentAttribute, - NSAccessibilityEnabledAttribute, - NSAccessibilityPositionAttribute, - NSAccessibilitySizeAttribute, - nil]; - } - if (anchorAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityURLAttribute]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; - anchorAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (webAreaAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:@"AXLinkUIElements"]; - [tempArray addObject:@"AXLoaded"]; - [tempArray addObject:@"AXLayoutCount"]; - [tempArray addObject:NSAccessibilityLoadingProgressAttribute]; - [tempArray addObject:NSAccessibilityURLAttribute]; - webAreaAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (textAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityNumberOfCharactersAttribute]; - [tempArray addObject:NSAccessibilitySelectedTextAttribute]; - [tempArray addObject:NSAccessibilitySelectedTextRangeAttribute]; - [tempArray addObject:NSAccessibilityVisibleCharacterRangeAttribute]; - [tempArray addObject:NSAccessibilityInsertionPointLineNumberAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; - [tempArray addObject:NSAccessibilityRequiredAttribute]; - [tempArray addObject:NSAccessibilityInvalidAttribute]; - [tempArray addObject:NSAccessibilityPlaceholderValueAttribute]; - textAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (listAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; - [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; - [tempArray addObject:NSAccessibilityOrientationAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - listAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (listBoxAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:listAttrs]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; - [tempArray addObject:NSAccessibilityRequiredAttribute]; - [tempArray addObject:NSAccessibilityInvalidAttribute]; - listBoxAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (rangeAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityMinValueAttribute]; - [tempArray addObject:NSAccessibilityMaxValueAttribute]; - [tempArray addObject:NSAccessibilityOrientationAttribute]; - [tempArray addObject:NSAccessibilityValueDescriptionAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - rangeAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (menuBarAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; - [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; - [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - menuBarAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (menuAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; - [tempArray addObject:NSAccessibilitySelectedChildrenAttribute]; - [tempArray addObject:NSAccessibilityVisibleChildrenAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - menuAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (menuItemAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:commonMenuAttrs]; - [tempArray addObject:NSAccessibilityTitleAttribute]; - [tempArray addObject:NSAccessibilityHelpAttribute]; - [tempArray addObject:NSAccessibilitySelectedAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemCmdCharAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemCmdVirtualKeyAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemCmdGlyphAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemCmdModifiersAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemMarkCharAttribute]; - [tempArray addObject:(NSString*)kAXMenuItemPrimaryUIElementAttribute]; - [tempArray addObject:NSAccessibilityServesAsTitleForUIElementsAttribute]; - menuItemAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (menuButtonAttrs == nil) { - menuButtonAttrs = [[NSArray alloc] initWithObjects:NSAccessibilityRoleAttribute, - NSAccessibilityRoleDescriptionAttribute, - NSAccessibilityParentAttribute, - NSAccessibilityPositionAttribute, - NSAccessibilitySizeAttribute, - NSAccessibilityWindowAttribute, - NSAccessibilityEnabledAttribute, - NSAccessibilityFocusedAttribute, - NSAccessibilityTitleAttribute, - NSAccessibilityChildrenAttribute, nil]; - } - if (controlAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; - [tempArray addObject:NSAccessibilityRequiredAttribute]; - [tempArray addObject:NSAccessibilityInvalidAttribute]; - controlAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (incrementorAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityIncrementButtonAttribute]; - [tempArray addObject:NSAccessibilityDecrementButtonAttribute]; - incrementorAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (buttonAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - // Buttons should not expose AXValue. - [tempArray removeObject:NSAccessibilityValueAttribute]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - [tempArray addObject:NSAccessibilityAccessKeyAttribute]; - buttonAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (comboBoxAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:controlAttrs]; - [tempArray addObject:NSAccessibilityExpandedAttribute]; - comboBoxAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tableAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityRowsAttribute]; - [tempArray addObject:NSAccessibilityVisibleRowsAttribute]; - [tempArray addObject:NSAccessibilityColumnsAttribute]; - [tempArray addObject:NSAccessibilityVisibleColumnsAttribute]; - [tempArray addObject:NSAccessibilityVisibleCellsAttribute]; - [tempArray addObject:(NSString *)kAXColumnHeaderUIElementsAttribute]; - [tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute]; - [tempArray addObject:NSAccessibilityHeaderAttribute]; - tableAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tableRowAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityIndexAttribute]; - tableRowAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tableColAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityIndexAttribute]; - [tempArray addObject:NSAccessibilityHeaderAttribute]; - [tempArray addObject:NSAccessibilityRowsAttribute]; - [tempArray addObject:NSAccessibilityVisibleRowsAttribute]; - tableColAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tableCellAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityRowIndexRangeAttribute]; - [tempArray addObject:NSAccessibilityColumnIndexRangeAttribute]; - tableCellAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (groupAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - groupAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (inputImageAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:buttonAttrs]; - [tempArray addObject:NSAccessibilityURLAttribute]; - inputImageAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (passwordFieldAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityTitleUIElementAttribute]; - [tempArray addObject:NSAccessibilityRequiredAttribute]; - [tempArray addObject:NSAccessibilityInvalidAttribute]; - [tempArray addObject:NSAccessibilityPlaceholderValueAttribute]; - passwordFieldAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tabListAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityTabsAttribute]; - [tempArray addObject:NSAccessibilityContentsAttribute]; - tabListAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (outlineAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilitySelectedRowsAttribute]; - [tempArray addObject:NSAccessibilityRowsAttribute]; - [tempArray addObject:NSAccessibilityColumnsAttribute]; - outlineAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (outlineRowAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:tableRowAttrs]; - [tempArray addObject:NSAccessibilityDisclosingAttribute]; - [tempArray addObject:NSAccessibilityDisclosedByRowAttribute]; - [tempArray addObject:NSAccessibilityDisclosureLevelAttribute]; - [tempArray addObject:NSAccessibilityDisclosedRowsAttribute]; - outlineRowAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (scrollViewAttrs == nil) { - tempArray = [[NSMutableArray alloc] initWithArray:attributes]; - [tempArray addObject:NSAccessibilityContentsAttribute]; - [tempArray addObject:NSAccessibilityHorizontalScrollBarAttribute]; - [tempArray addObject:NSAccessibilityVerticalScrollBarAttribute]; - scrollViewAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - - NSArray *objectAttributes = attributes; - - if (m_object->isPasswordField()) - objectAttributes = passwordFieldAttrs; - - else if (m_object->isWebArea()) - objectAttributes = webAreaAttrs; - - else if (m_object->isTextControl()) - objectAttributes = textAttrs; - - else if (m_object->isAnchor() || m_object->isImage() || m_object->isLink()) - objectAttributes = anchorAttrs; - - else if (m_object->isAccessibilityTable()) - objectAttributes = tableAttrs; - else if (m_object->isTableColumn()) - objectAttributes = tableColAttrs; - else if (m_object->isTableCell()) - objectAttributes = tableCellAttrs; - else if (m_object->isTableRow()) { - // An ARIA table row can be collapsed and expanded, so it needs the extra attributes. - if (m_object->isARIATreeGridRow()) - objectAttributes = outlineRowAttrs; - else - objectAttributes = tableRowAttrs; - } - - else if (m_object->isTree()) - objectAttributes = outlineAttrs; - else if (m_object->isTreeItem()) - objectAttributes = outlineRowAttrs; - - else if (m_object->isListBox()) - objectAttributes = listBoxAttrs; - else if (m_object->isList()) - objectAttributes = listAttrs; - - else if (m_object->isComboBox()) - objectAttributes = comboBoxAttrs; - - else if (m_object->isProgressIndicator() || m_object->isSlider()) - objectAttributes = rangeAttrs; - - // These are processed in order because an input image is a button, and a button is a control. - else if (m_object->isInputImage()) - objectAttributes = inputImageAttrs; - else if (m_object->isButton()) - objectAttributes = buttonAttrs; - else if (m_object->isControl()) - objectAttributes = controlAttrs; - - else if (m_object->isGroup() || m_object->isListItem()) - objectAttributes = groupAttrs; - else if (m_object->isTabList()) - objectAttributes = tabListAttrs; - else if (m_object->isScrollView()) - objectAttributes = scrollViewAttrs; - else if (m_object->isSpinButton()) - objectAttributes = incrementorAttrs; - - else if (m_object->isMenu()) - objectAttributes = menuAttrs; - else if (m_object->isMenuBar()) - objectAttributes = menuBarAttrs; - else if (m_object->isMenuButton()) - objectAttributes = menuButtonAttrs; - else if (m_object->isMenuItem()) - objectAttributes = menuItemAttrs; - - NSArray *additionalAttributes = [self additionalAccessibilityAttributeNames]; - if ([additionalAttributes count]) - objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes]; - - return objectAttributes; -} - -- (VisiblePositionRange)visiblePositionRangeForTextMarkerRange:(id)textMarkerRange -{ - if (!textMarkerRange) - return VisiblePositionRange(); - AXObjectCache* cache = m_object->axObjectCache(); - return VisiblePositionRange(visiblePositionForStartOfTextMarkerRange(cache, textMarkerRange), visiblePositionForEndOfTextMarkerRange(cache, textMarkerRange)); -} - -- (NSArray*)renderWidgetChildren -{ - Widget* widget = m_object->widget(); - if (!widget) - return nil; - return [(widget->platformWidget()) accessibilityAttributeValue: NSAccessibilityChildrenAttribute]; -} - -- (id)remoteAccessibilityParentObject -{ - if (!m_object) - return nil; - - Document* document = m_object->document(); - if (!document) - return nil; - - Frame* frame = document->frame(); - if (!frame) - return nil; - - return frame->loader()->client()->accessibilityRemoteObject(); -} - -static void convertToVector(NSArray* array, AccessibilityObject::AccessibilityChildrenVector& vector) -{ - unsigned length = [array count]; - vector.reserveInitialCapacity(length); - for (unsigned i = 0; i < length; ++i) { - AccessibilityObject* obj = [[array objectAtIndex:i] accessibilityObject]; - if (obj) - vector.append(obj); - } -} - -static NSMutableArray* convertToNSArray(const AccessibilityObject::AccessibilityChildrenVector& vector) -{ - unsigned length = vector.size(); - NSMutableArray* array = [NSMutableArray arrayWithCapacity: length]; - for (unsigned i = 0; i < length; ++i) { - WebAccessibilityObjectWrapper* wrapper = vector[i]->wrapper(); - ASSERT(wrapper); - if (wrapper) { - // we want to return the attachment view instead of the object representing the attachment. - // otherwise, we get palindrome errors in the AX hierarchy - if (vector[i]->isAttachment() && [wrapper attachmentView]) - [array addObject:[wrapper attachmentView]]; - else - [array addObject:wrapper]; - } - } - return array; -} - -- (id)textMarkerRangeForSelection -{ - VisibleSelection selection = m_object->selection(); - if (selection.isNone()) - return nil; - return [self textMarkerRangeFromVisiblePositions:selection.visibleStart() endPosition:selection.visibleEnd()]; -} - -- (CGPoint)convertPointToScreenSpace:(FloatPoint &)point -{ - FrameView* frameView = m_object->documentFrameView(); - - // WebKit1 code path... platformWidget() exists. - if (frameView && frameView->platformWidget()) { - - NSPoint nsPoint = (NSPoint)point; - NSView* view = frameView->documentView(); - nsPoint = [[view window] convertBaseToScreen:[view convertPoint:nsPoint toView:nil]]; - return CGPointMake(nsPoint.x, nsPoint.y); - - } else { - - // Find the appropriate scroll view to use to convert the contents to the window. - ScrollView* scrollView = 0; - AccessibilityObject* parent = 0; - for (parent = m_object->parentObject(); parent; parent = parent->parentObject()) { - if (parent->isAccessibilityScrollView()) { - scrollView = toAccessibilityScrollView(parent)->scrollView(); - break; - } - } - - IntPoint intPoint = flooredIntPoint(point); - if (scrollView) - intPoint = scrollView->contentsToRootView(intPoint); - - Page* page = m_object->page(); - - // If we have an empty chrome client (like SVG) then we should use the page - // of the scroll view parent to help us get to the screen rect. - if (parent && page && page->chrome().client()->isEmptyChromeClient()) - page = parent->page(); - - if (page) { - IntRect rect = IntRect(intPoint, IntSize(0, 0)); - intPoint = page->chrome().rootViewToScreen(rect).location(); - } - - return intPoint; - } -} - -static void WebTransformCGPathToNSBezierPath(void *info, const CGPathElement *element) -{ - NSBezierPath *bezierPath = (NSBezierPath *)info; - switch (element->type) { - case kCGPathElementMoveToPoint: - [bezierPath moveToPoint:NSPointFromCGPoint(element->points[0])]; - break; - case kCGPathElementAddLineToPoint: - [bezierPath lineToPoint:NSPointFromCGPoint(element->points[0])]; - break; - case kCGPathElementAddCurveToPoint: - [bezierPath curveToPoint:NSPointFromCGPoint(element->points[0]) controlPoint1:NSPointFromCGPoint(element->points[1]) controlPoint2:NSPointFromCGPoint(element->points[2])]; - break; - case kCGPathElementCloseSubpath: - [bezierPath closePath]; - break; - default: - break; - } -} - -- (NSBezierPath *)bezierPathFromPath:(CGPathRef)path -{ - NSBezierPath *bezierPath = [NSBezierPath bezierPath]; - CGPathApply(path, bezierPath, WebTransformCGPathToNSBezierPath); - return bezierPath; -} - -- (NSBezierPath *)path -{ - Path path = m_object->elementPath(); - if (path.isEmpty()) - return NULL; - - CGPathRef transformedPath = [self convertPathToScreenSpace:path]; - return [self bezierPathFromPath:transformedPath]; -} - -- (NSValue *)position -{ - IntRect rect = pixelSnappedIntRect(m_object->elementRect()); - - // The Cocoa accessibility API wants the lower-left corner. - FloatPoint floatPoint = FloatPoint(rect.x(), rect.maxY()); - - CGPoint cgPoint = [self convertPointToScreenSpace:floatPoint]; - - return [NSValue valueWithPoint:NSMakePoint(cgPoint.x, cgPoint.y)]; -} - -typedef HashMap<int, NSString*> AccessibilityRoleMap; - -static const AccessibilityRoleMap& createAccessibilityRoleMap() -{ - struct RoleEntry { - AccessibilityRole value; - NSString* string; - }; - - static const RoleEntry roles[] = { - { UnknownRole, NSAccessibilityUnknownRole }, - { ButtonRole, NSAccessibilityButtonRole }, - { RadioButtonRole, NSAccessibilityRadioButtonRole }, - { CheckBoxRole, NSAccessibilityCheckBoxRole }, - { SliderRole, NSAccessibilitySliderRole }, - { TabGroupRole, NSAccessibilityTabGroupRole }, - { TextFieldRole, NSAccessibilityTextFieldRole }, - { StaticTextRole, NSAccessibilityStaticTextRole }, - { TextAreaRole, NSAccessibilityTextAreaRole }, - { ScrollAreaRole, NSAccessibilityScrollAreaRole }, - { PopUpButtonRole, NSAccessibilityPopUpButtonRole }, - { MenuButtonRole, NSAccessibilityMenuButtonRole }, - { TableRole, NSAccessibilityTableRole }, - { ApplicationRole, NSAccessibilityApplicationRole }, - { GroupRole, NSAccessibilityGroupRole }, - { RadioGroupRole, NSAccessibilityRadioGroupRole }, - { ListRole, NSAccessibilityListRole }, - { DirectoryRole, NSAccessibilityListRole }, - { ScrollBarRole, NSAccessibilityScrollBarRole }, - { ValueIndicatorRole, NSAccessibilityValueIndicatorRole }, - { ImageRole, NSAccessibilityImageRole }, - { MenuBarRole, NSAccessibilityMenuBarRole }, - { MenuRole, NSAccessibilityMenuRole }, - { MenuItemRole, NSAccessibilityMenuItemRole }, - { ColumnRole, NSAccessibilityColumnRole }, - { RowRole, NSAccessibilityRowRole }, - { ToolbarRole, NSAccessibilityToolbarRole }, - { BusyIndicatorRole, NSAccessibilityBusyIndicatorRole }, - { ProgressIndicatorRole, NSAccessibilityProgressIndicatorRole }, - { WindowRole, NSAccessibilityWindowRole }, - { DrawerRole, NSAccessibilityDrawerRole }, - { SystemWideRole, NSAccessibilitySystemWideRole }, - { OutlineRole, NSAccessibilityOutlineRole }, - { IncrementorRole, NSAccessibilityIncrementorRole }, - { BrowserRole, NSAccessibilityBrowserRole }, - { ComboBoxRole, NSAccessibilityComboBoxRole }, - { SplitGroupRole, NSAccessibilitySplitGroupRole }, - { SplitterRole, NSAccessibilitySplitterRole }, - { ColorWellRole, NSAccessibilityColorWellRole }, - { GrowAreaRole, NSAccessibilityGrowAreaRole }, - { SheetRole, NSAccessibilitySheetRole }, - { HelpTagRole, NSAccessibilityHelpTagRole }, - { MatteRole, NSAccessibilityMatteRole }, - { RulerRole, NSAccessibilityRulerRole }, - { RulerMarkerRole, NSAccessibilityRulerMarkerRole }, - { LinkRole, NSAccessibilityLinkRole }, - { DisclosureTriangleRole, NSAccessibilityDisclosureTriangleRole }, - { GridRole, NSAccessibilityGridRole }, - { WebCoreLinkRole, NSAccessibilityLinkRole }, - { ImageMapLinkRole, NSAccessibilityLinkRole }, - { ImageMapRole, @"AXImageMap" }, - { ListMarkerRole, @"AXListMarker" }, - { WebAreaRole, @"AXWebArea" }, - { SeamlessWebAreaRole, NSAccessibilityGroupRole }, - { HeadingRole, @"AXHeading" }, - { ListBoxRole, NSAccessibilityListRole }, - { ListBoxOptionRole, NSAccessibilityStaticTextRole }, - { CellRole, NSAccessibilityCellRole }, - { TableHeaderContainerRole, NSAccessibilityGroupRole }, - { RowHeaderRole, NSAccessibilityGroupRole }, - { DefinitionRole, NSAccessibilityGroupRole }, - { DescriptionListDetailRole, NSAccessibilityGroupRole }, - { DescriptionListTermRole, NSAccessibilityGroupRole }, - { DescriptionListRole, NSAccessibilityListRole }, - { SliderThumbRole, NSAccessibilityValueIndicatorRole }, - { LandmarkApplicationRole, NSAccessibilityGroupRole }, - { LandmarkBannerRole, NSAccessibilityGroupRole }, - { LandmarkComplementaryRole, NSAccessibilityGroupRole }, - { LandmarkContentInfoRole, NSAccessibilityGroupRole }, - { LandmarkMainRole, NSAccessibilityGroupRole }, - { LandmarkNavigationRole, NSAccessibilityGroupRole }, - { LandmarkSearchRole, NSAccessibilityGroupRole }, - { ApplicationAlertRole, NSAccessibilityGroupRole }, - { ApplicationAlertDialogRole, NSAccessibilityGroupRole }, - { ApplicationDialogRole, NSAccessibilityGroupRole }, - { ApplicationLogRole, NSAccessibilityGroupRole }, - { ApplicationMarqueeRole, NSAccessibilityGroupRole }, - { ApplicationStatusRole, NSAccessibilityGroupRole }, - { ApplicationTimerRole, NSAccessibilityGroupRole }, - { DocumentRole, NSAccessibilityGroupRole }, - { DocumentArticleRole, NSAccessibilityGroupRole }, - { DocumentMathRole, NSAccessibilityGroupRole }, - { DocumentNoteRole, NSAccessibilityGroupRole }, - { DocumentRegionRole, NSAccessibilityGroupRole }, - { UserInterfaceTooltipRole, NSAccessibilityGroupRole }, - { TabRole, NSAccessibilityRadioButtonRole }, - { TabListRole, NSAccessibilityTabGroupRole }, - { TabPanelRole, NSAccessibilityGroupRole }, - { TreeRole, NSAccessibilityOutlineRole }, - { TreeItemRole, NSAccessibilityRowRole }, - { ListItemRole, NSAccessibilityGroupRole }, - { ParagraphRole, NSAccessibilityGroupRole }, - { LabelRole, NSAccessibilityGroupRole }, - { DivRole, NSAccessibilityGroupRole }, - { FormRole, NSAccessibilityGroupRole }, - { SpinButtonRole, NSAccessibilityIncrementorRole }, - { FooterRole, NSAccessibilityGroupRole }, - { ToggleButtonRole, NSAccessibilityButtonRole }, - { CanvasRole, NSAccessibilityImageRole }, - { SVGRootRole, NSAccessibilityGroupRole }, - { LegendRole, NSAccessibilityGroupRole }, - { MathElementRole, NSAccessibilityGroupRole } - }; - AccessibilityRoleMap& roleMap = *new AccessibilityRoleMap; - - const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); - for (unsigned i = 0; i < numRoles; ++i) - roleMap.set(roles[i].value, roles[i].string); - return roleMap; -} - -static NSString* roleValueToNSString(AccessibilityRole value) -{ - ASSERT(value); - static const AccessibilityRoleMap& roleMap = createAccessibilityRoleMap(); - return roleMap.get(value); -} - -- (NSString*)role -{ - if (m_object->isAttachment()) - return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleAttribute]; - AccessibilityRole role = m_object->roleValue(); - if (role == CanvasRole && m_object->canvasHasFallbackContent()) - role = GroupRole; - NSString* string = roleValueToNSString(role); - if (string != nil) - return string; - return NSAccessibilityUnknownRole; -} - -- (NSString*)subrole -{ - if (m_object->isPasswordField()) - return NSAccessibilitySecureTextFieldSubrole; - if (m_object->isSearchField()) - return NSAccessibilitySearchFieldSubrole; - - if (m_object->isAttachment()) { - NSView* attachView = [self attachmentView]; - if ([[attachView accessibilityAttributeNames] containsObject:NSAccessibilitySubroleAttribute]) { - return [attachView accessibilityAttributeValue:NSAccessibilitySubroleAttribute]; - } - } - - if (m_object->isSpinButtonPart()) { - if (toAccessibilitySpinButtonPart(m_object)->isIncrementor()) - return NSAccessibilityIncrementArrowSubrole; - - return NSAccessibilityDecrementArrowSubrole; - } - - if (m_object->isFileUploadButton()) - return @"AXFileUploadButton"; - - if (m_object->isTreeItem()) - return NSAccessibilityOutlineRowSubrole; - - if (m_object->isList()) { - AccessibilityList* listObject = toAccessibilityList(m_object); - if (listObject->isUnorderedList() || listObject->isOrderedList()) - return NSAccessibilityContentListSubrole; - if (listObject->isDescriptionList()) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1090 - return NSAccessibilityDefinitionListSubrole; -#else - return NSAccessibilityDescriptionListSubrole; -#endif - } - } - - // ARIA content subroles. - switch (m_object->roleValue()) { - case LandmarkApplicationRole: - return @"AXLandmarkApplication"; - case LandmarkBannerRole: - return @"AXLandmarkBanner"; - case LandmarkComplementaryRole: - return @"AXLandmarkComplementary"; - // Footer roles should appear as content info types. - case FooterRole: - case LandmarkContentInfoRole: - return @"AXLandmarkContentInfo"; - case LandmarkMainRole: - return @"AXLandmarkMain"; - case LandmarkNavigationRole: - return @"AXLandmarkNavigation"; - case LandmarkSearchRole: - return @"AXLandmarkSearch"; - case ApplicationAlertRole: - return @"AXApplicationAlert"; - case ApplicationAlertDialogRole: - return @"AXApplicationAlertDialog"; - case ApplicationDialogRole: - return @"AXApplicationDialog"; - case ApplicationLogRole: - return @"AXApplicationLog"; - case ApplicationMarqueeRole: - return @"AXApplicationMarquee"; - case ApplicationStatusRole: - return @"AXApplicationStatus"; - case ApplicationTimerRole: - return @"AXApplicationTimer"; - case DocumentRole: - return @"AXDocument"; - case DocumentArticleRole: - return @"AXDocumentArticle"; - case DocumentMathRole: - return @"AXDocumentMath"; - case DocumentNoteRole: - return @"AXDocumentNote"; - case DocumentRegionRole: - return @"AXDocumentRegion"; - case UserInterfaceTooltipRole: - return @"AXUserInterfaceTooltip"; - case TabPanelRole: - return @"AXTabPanel"; - case DefinitionRole: - return @"AXDefinition"; - case DescriptionListTermRole: - return @"AXTerm"; - case DescriptionListDetailRole: - return @"AXDescription"; - // Default doesn't return anything, so roles defined below can be chosen. - default: - break; - } - - if (m_object->roleValue() == MathElementRole) { - if (m_object->isMathFraction()) - return @"AXMathFraction"; - if (m_object->isMathFenced()) - return @"AXMathFenced"; - if (m_object->isMathSubscriptSuperscript()) - return @"AXMathSubscriptSuperscript"; - if (m_object->isMathRow()) - return @"AXMathRow"; - if (m_object->isMathUnderOver()) - return @"AXMathUnderOver"; - if (m_object->isMathSquareRoot()) - return @"AXMathSquareRoot"; - if (m_object->isMathRoot()) - return @"AXMathRoot"; - if (m_object->isMathText()) - return @"AXMathText"; - if (m_object->isMathNumber()) - return @"AXMathNumber"; - if (m_object->isMathIdentifier()) - return @"AXMathIdentifier"; - if (m_object->isMathTable()) - return @"AXMathTable"; - if (m_object->isMathTableRow()) - return @"AXMathTableRow"; - if (m_object->isMathTableCell()) - return @"AXMathTableCell"; - if (m_object->isMathFenceOperator()) - return @"AXMathFenceOperator"; - if (m_object->isMathSeparatorOperator()) - return @"AXMathSeparatorOperator"; - if (m_object->isMathOperator()) - return @"AXMathOperator"; - if (m_object->isMathMultiscript()) - return @"AXMathMultiscript"; - } - - if (m_object->isMediaTimeline()) - return NSAccessibilityTimelineSubrole; - - return nil; -} - -- (NSString*)roleDescription -{ - if (!m_object) - return nil; - - // attachments have the AXImage role, but a different subrole - if (m_object->isAttachment()) - return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityRoleDescriptionAttribute]; - - NSString* axRole = [self role]; - - if ([axRole isEqualToString:NSAccessibilityGroupRole]) { - - NSString *ariaLandmarkRoleDescription = [self ariaLandmarkRoleDescription]; - if (ariaLandmarkRoleDescription) - return ariaLandmarkRoleDescription; - - switch (m_object->roleValue()) { - case DefinitionRole: - return AXDefinitionText(); - case DescriptionListTermRole: - return AXDescriptionListTermText(); - case DescriptionListDetailRole: - return AXDescriptionListDetailText(); - case FooterRole: - return AXFooterRoleDescriptionText(); - default: - return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, [self subrole]); - } - } - - if ([axRole isEqualToString:@"AXWebArea"]) - return AXWebAreaText(); - - if ([axRole isEqualToString:@"AXLink"]) - return AXLinkText(); - - if ([axRole isEqualToString:@"AXListMarker"]) - return AXListMarkerText(); - - if ([axRole isEqualToString:@"AXImageMap"]) - return AXImageMapText(); - - if ([axRole isEqualToString:@"AXHeading"]) - return AXHeadingText(); - - if (m_object->isFileUploadButton()) - return AXFileUploadButtonText(); - - // Only returning for DL (not UL or OL) because description changed with HTML5 from 'definition list' to - // superset 'description list' and does not return the same values in AX API on some OS versions. - if (m_object->isList()) { - AccessibilityList* listObject = toAccessibilityList(m_object); - if (listObject->isDescriptionList()) - return AXDescriptionListText(); - } - - // AppKit also returns AXTab for the role description for a tab item. - if (m_object->isTabItem()) - return NSAccessibilityRoleDescription(@"AXTab", nil); - - // We should try the system default role description for all other roles. - // If we get the same string back, then as a last resort, return unknown. - NSString* defaultRoleDescription = NSAccessibilityRoleDescription(axRole, [self subrole]); - - // On earlier Mac versions (Lion), using a non-standard subrole would result in a role description - // being returned that looked like AXRole:AXSubrole. To make all platforms have the same role descriptions - // we should fallback on a role description ignoring the subrole in these cases. - if ([defaultRoleDescription isEqualToString:[NSString stringWithFormat:@"%@:%@", axRole, [self subrole]]]) - defaultRoleDescription = NSAccessibilityRoleDescription(axRole, nil); - - if (![defaultRoleDescription isEqualToString:axRole]) - return defaultRoleDescription; - - return NSAccessibilityRoleDescription(NSAccessibilityUnknownRole, nil); -} - -- (id)scrollViewParent -{ - if (!m_object || !m_object->isAccessibilityScrollView()) - return nil; - - // If this scroll view provides it's parent object (because it's a sub-frame), then - // we should not find the remoteAccessibilityParent. - if (m_object->parentObject()) - return nil; - - AccessibilityScrollView* scrollView = toAccessibilityScrollView(m_object); - ScrollView* scroll = scrollView->scrollView(); - if (!scroll) - return nil; - - if (scroll->platformWidget()) - return NSAccessibilityUnignoredAncestor(scroll->platformWidget()); - - return [self remoteAccessibilityParentObject]; -} - -// FIXME: Different kinds of elements are putting the title tag to use in different -// AX fields. This should be rectified, but in the initial patch I want to achieve -// parity with existing behavior. -- (BOOL)titleTagShouldBeUsedInDescriptionField -{ - return (m_object->isLink() && !m_object->isImageMapLink()) || m_object->isImage(); -} - -// This should be the "visible" text that's actually on the screen if possible. -// If there's alternative text, that can override the title. -- (NSString *)accessibilityTitle -{ - // Static text objects should not have a title. Its content is communicated in its AXValue. - if (m_object->roleValue() == StaticTextRole) - return [NSString string]; - - // A file upload button presents a challenge because it has button text and a value, but the - // API doesn't support this paradigm. - // The compromise is to return the button type in the role description and the value of the file path in the title - if (m_object->isFileUploadButton()) - return m_object->stringValue(); - - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - // If we have alternative text, then we should not expose a title. - if (text.textSource == AlternativeText) - break; - - // Once we encounter visible text, or the text from our children that should be used foremost. - if (text.textSource == VisibleText || text.textSource == ChildrenText) - return text.text; - - // If there's an element that labels this object and it's not exposed, then we should use - // that text as our title. - if (text.textSource == LabelByElementText && !m_object->exposesTitleUIElement()) - return text.text; - - // FIXME: The title tag is used in certain cases for the title. This usage should - // probably be in the description field since it's not "visible". - if (text.textSource == TitleTagText && ![self titleTagShouldBeUsedInDescriptionField]) - return text.text; - } - - return [NSString string]; -} - -- (NSString *)accessibilityDescription -{ - // Static text objects should not have a description. Its content is communicated in its AXValue. - // One exception is the media control labels that have a value and a description. Those are set programatically. - if (m_object->roleValue() == StaticTextRole && !m_object->isMediaControlLabel()) - return [NSString string]; - - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - if (text.textSource == AlternativeText) - return text.text; - - if (text.textSource == TitleTagText && [self titleTagShouldBeUsedInDescriptionField]) - return text.text; - } - - return [NSString string]; -} - -- (NSString *)accessibilityHelpText -{ - Vector<AccessibilityText> textOrder; - m_object->accessibilityText(textOrder); - - unsigned length = textOrder.size(); - bool descriptiveTextAvailable = false; - for (unsigned k = 0; k < length; k++) { - const AccessibilityText& text = textOrder[k]; - - if (text.textSource == HelpText || text.textSource == SummaryText) - return text.text; - - // If an element does NOT have other descriptive text the title tag should be used as its descriptive text. - // But, if those ARE available, then the title tag should be used for help text instead. - switch (text.textSource) { - case AlternativeText: - case VisibleText: - case ChildrenText: - case LabelByElementText: - descriptiveTextAvailable = true; - default: - break; - } - - if (text.textSource == TitleTagText && descriptiveTextAvailable) - return text.text; - } - - return [NSString string]; -} - -// FIXME: split up this function in a better way. -// suggestions: Use a hash table that maps attribute names to function calls, -// or maybe pointers to member functions -- (id)accessibilityAttributeValue:(NSString*)attributeName -{ - if (![self updateObjectBackingStore]) - return nil; - - if ([attributeName isEqualToString: NSAccessibilityRoleAttribute]) - return [self role]; - - if ([attributeName isEqualToString: NSAccessibilitySubroleAttribute]) - return [self subrole]; - - if ([attributeName isEqualToString: NSAccessibilityRoleDescriptionAttribute]) - return [self roleDescription]; - - if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) { - - // This will return the parent of the AXWebArea, if this is a web area. - id scrollViewParent = [self scrollViewParent]; - if (scrollViewParent) - return scrollViewParent; - - // Tree item (changed to AXRows) can only report the tree (AXOutline) as its parent. - if (m_object->isTreeItem()) { - AccessibilityObject* parent = m_object->parentObjectUnignored(); - while (parent) { - if (parent->isTree()) - return parent->wrapper(); - parent = parent->parentObjectUnignored(); - } - } - - AccessibilityObject* parent = m_object->parentObjectUnignored(); - if (!parent) - return nil; - - // In WebKit1, the scroll view is provided by the system (the attachment view), so the parent - // should be reported directly as such. - if (m_object->isWebArea() && parent->isAttachment()) - return [parent->wrapper() attachmentView]; - - return parent->wrapper(); - } - - if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { - if (m_object->children().isEmpty()) { - NSArray* children = [self renderWidgetChildren]; - if (children != nil) - return children; - } - - // The tree's (AXOutline) children are supposed to be its rows and columns. - // The ARIA spec doesn't have columns, so we just need rows. - if (m_object->isTree()) - return [self accessibilityAttributeValue:NSAccessibilityRowsAttribute]; - - // A tree item should only expose its content as its children (not its rows) - if (m_object->isTreeItem()) { - AccessibilityObject::AccessibilityChildrenVector contentCopy; - m_object->ariaTreeItemContent(contentCopy); - return convertToNSArray(contentCopy); - } - - return convertToNSArray(m_object->children()); - } - - if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) { - if (m_object->isListBox()) { - AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; - m_object->selectedChildren(selectedChildrenCopy); - return convertToNSArray(selectedChildrenCopy); - } - return nil; - } - - if ([attributeName isEqualToString: NSAccessibilityVisibleChildrenAttribute]) { - if (m_object->isListBox()) { - AccessibilityObject::AccessibilityChildrenVector visibleChildrenCopy; - m_object->visibleChildren(visibleChildrenCopy); - return convertToNSArray(visibleChildrenCopy); - } - else if (m_object->isList()) - return [self accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; - - return nil; - } - - - if (m_object->isWebArea()) { - if ([attributeName isEqualToString:@"AXLinkUIElements"]) { - AccessibilityObject::AccessibilityChildrenVector links; - static_cast<AccessibilityRenderObject*>(m_object)->getDocumentLinks(links); - return convertToNSArray(links); - } - if ([attributeName isEqualToString:@"AXLoaded"]) - return [NSNumber numberWithBool:m_object->isLoaded()]; - if ([attributeName isEqualToString:@"AXLayoutCount"]) - return [NSNumber numberWithInt:m_object->layoutCount()]; - if ([attributeName isEqualToString:NSAccessibilityLoadingProgressAttribute]) - return [NSNumber numberWithDouble:m_object->estimatedLoadingProgress()]; - } - - if (m_object->isTextControl()) { - if ([attributeName isEqualToString: NSAccessibilityNumberOfCharactersAttribute]) { - int length = m_object->textLength(); - if (length < 0) - return nil; - return [NSNumber numberWithUnsignedInt:length]; - } - if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { - String selectedText = m_object->selectedText(); - if (selectedText.isNull()) - return nil; - return (NSString*)selectedText; - } - if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) { - PlainTextRange textRange = m_object->selectedTextRange(); - if (textRange.isNull()) - return [NSValue valueWithRange:NSMakeRange(0, 0)]; - return [NSValue valueWithRange:NSMakeRange(textRange.start, textRange.length)]; - } - // TODO: Get actual visible range. <rdar://problem/4712101> - if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) - return m_object->isPasswordField() ? nil : [NSValue valueWithRange: NSMakeRange(0, m_object->textLength())]; - if ([attributeName isEqualToString: NSAccessibilityInsertionPointLineNumberAttribute]) { - // if selectionEnd > 0, then there is selected text and this question should not be answered - if (m_object->isPasswordField() || m_object->selectionEnd() > 0) - return nil; - - AccessibilityObject* focusedObject = m_object->focusedUIElement(); - if (focusedObject != m_object) - return nil; - - VisiblePosition focusedPosition = focusedObject->visiblePositionForIndex(focusedObject->selectionStart(), true); - int lineNumber = m_object->lineForPosition(focusedPosition); - if (lineNumber < 0) - return nil; - - return [NSNumber numberWithInt:lineNumber]; - } - } - - if ([attributeName isEqualToString: NSAccessibilityURLAttribute]) { - KURL url = m_object->url(); - if (url.isNull()) - return nil; - return (NSURL*)url; - } - - // Only native spin buttons have increment and decrement buttons. - if (m_object->isNativeSpinButton()) { - if ([attributeName isEqualToString:NSAccessibilityIncrementButtonAttribute]) - return toAccessibilitySpinButton(m_object)->incrementButton()->wrapper(); - if ([attributeName isEqualToString:NSAccessibilityDecrementButtonAttribute]) - return toAccessibilitySpinButton(m_object)->decrementButton()->wrapper(); - } - - if ([attributeName isEqualToString: @"AXVisited"]) - return [NSNumber numberWithBool: m_object->isVisited()]; - - if ([attributeName isEqualToString: NSAccessibilityTitleAttribute]) { - if (m_object->isAttachment()) { - if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityTitleAttribute]) - return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityTitleAttribute]; - } - - return [self accessibilityTitle]; - } - - if ([attributeName isEqualToString: NSAccessibilityDescriptionAttribute]) { - if (m_object->isAttachment()) { - if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityDescriptionAttribute]) - return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]; - } - return [self accessibilityDescription]; - } - - if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) { - if (m_object->isAttachment()) { - if ([[[self attachmentView] accessibilityAttributeNames] containsObject:NSAccessibilityValueAttribute]) - return [[self attachmentView] accessibilityAttributeValue:NSAccessibilityValueAttribute]; - } - if (m_object->supportsRangeValue()) - return [NSNumber numberWithFloat:m_object->valueForRange()]; - if (m_object->roleValue() == SliderThumbRole) - return [NSNumber numberWithFloat:m_object->parentObject()->valueForRange()]; - if (m_object->isHeading()) - return [NSNumber numberWithInt:m_object->headingLevel()]; - - if (m_object->isCheckboxOrRadio()) { - switch (m_object->checkboxOrRadioValue()) { - case ButtonStateOff: - return [NSNumber numberWithInt:0]; - case ButtonStateOn: - return [NSNumber numberWithInt:1]; - case ButtonStateMixed: - return [NSNumber numberWithInt:2]; - } - } - - // radio groups return the selected radio button as the AXValue - if (m_object->isRadioGroup()) { - AccessibilityObject* radioButton = m_object->selectedRadioButton(); - if (!radioButton) - return nil; - return radioButton->wrapper(); - } - - if (m_object->isTabList()) { - AccessibilityObject* tabItem = m_object->selectedTabItem(); - if (!tabItem) - return nil; - return tabItem->wrapper(); - } - - if (m_object->isTabItem()) - return [NSNumber numberWithInt:m_object->isSelected()]; - - if (m_object->isColorWell()) { - int r, g, b; - m_object->colorValue(r, g, b); - return [NSString stringWithFormat:@"rgb %7.5f %7.5f %7.5f 1", r / 255., g / 255., b / 255.]; - } - - return m_object->stringValue(); - } - - if ([attributeName isEqualToString:(NSString *)kAXMenuItemMarkCharAttribute]) { - const unichar ch = 0x2713; // ✓ used on Mac for selected menu items. - return (m_object->isChecked()) ? [NSString stringWithCharacters:&ch length:1] : nil; - } - - if ([attributeName isEqualToString: NSAccessibilityMinValueAttribute]) - return [NSNumber numberWithFloat:m_object->minValueForRange()]; - - if ([attributeName isEqualToString: NSAccessibilityMaxValueAttribute]) - return [NSNumber numberWithFloat:m_object->maxValueForRange()]; - - if ([attributeName isEqualToString: NSAccessibilityHelpAttribute]) - return [self accessibilityHelpText]; - - if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) - return [NSNumber numberWithBool: m_object->isFocused()]; - - if ([attributeName isEqualToString: NSAccessibilityEnabledAttribute]) - return [NSNumber numberWithBool: m_object->isEnabled()]; - - if ([attributeName isEqualToString: NSAccessibilitySizeAttribute]) { - IntSize s = m_object->pixelSnappedSize(); - return [NSValue valueWithSize: NSMakeSize(s.width(), s.height())]; - } - - if ([attributeName isEqualToString: NSAccessibilityPositionAttribute]) - return [self position]; - if ([attributeName isEqualToString:NSAccessibilityPathAttribute]) - return [self path]; - - if ([attributeName isEqualToString: NSAccessibilityWindowAttribute] || - [attributeName isEqualToString: NSAccessibilityTopLevelUIElementAttribute]) { - - id remoteParent = [self remoteAccessibilityParentObject]; - if (remoteParent) - return [remoteParent accessibilityAttributeValue:attributeName]; - - FrameView* fv = m_object->documentFrameView(); - if (fv) - return [fv->platformWidget() window]; - return nil; - } - - if ([attributeName isEqualToString:NSAccessibilityAccessKeyAttribute]) { - AtomicString accessKey = m_object->accessKey(); - if (accessKey.isNull()) - return nil; - return accessKey; - } - - if ([attributeName isEqualToString:NSAccessibilityTabsAttribute]) { - if (m_object->isTabList()) { - AccessibilityObject::AccessibilityChildrenVector tabsChildren; - m_object->tabChildren(tabsChildren); - return convertToNSArray(tabsChildren); - } - } - - if ([attributeName isEqualToString:NSAccessibilityContentsAttribute]) { - // The contents of a tab list are all the children except the tabs. - if (m_object->isTabList()) { - AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); - AccessibilityObject::AccessibilityChildrenVector tabsChildren; - m_object->tabChildren(tabsChildren); - - AccessibilityObject::AccessibilityChildrenVector contents; - unsigned childrenSize = children.size(); - for (unsigned k = 0; k < childrenSize; ++k) { - if (tabsChildren.find(children[k]) == WTF::notFound) - contents.append(children[k]); - } - return convertToNSArray(contents); - } else if (m_object->isScrollView()) { - AccessibilityObject::AccessibilityChildrenVector children = m_object->children(); - - // A scrollView's contents are everything except the scroll bars. - AccessibilityObject::AccessibilityChildrenVector contents; - unsigned childrenSize = children.size(); - for (unsigned k = 0; k < childrenSize; ++k) { - if (!children[k]->isScrollbar()) - contents.append(children[k]); - } - return convertToNSArray(contents); - } - } - - if (m_object->isAccessibilityTable()) { - // TODO: distinguish between visible and non-visible rows - if ([attributeName isEqualToString:NSAccessibilityRowsAttribute] || - [attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute]) { - return convertToNSArray(static_cast<AccessibilityTable*>(m_object)->rows()); - } - // TODO: distinguish between visible and non-visible columns - if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute] || - [attributeName isEqualToString:NSAccessibilityVisibleColumnsAttribute]) { - return convertToNSArray(static_cast<AccessibilityTable*>(m_object)->columns()); - } - - if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; - m_object->selectedChildren(selectedChildrenCopy); - return convertToNSArray(selectedChildrenCopy); - } - - // HTML tables don't support these - if ([attributeName isEqualToString:NSAccessibilitySelectedColumnsAttribute] || - [attributeName isEqualToString:NSAccessibilitySelectedCellsAttribute]) - return nil; - - if ([attributeName isEqualToString:(NSString *)kAXColumnHeaderUIElementsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector columnHeaders; - static_cast<AccessibilityTable*>(m_object)->columnHeaders(columnHeaders); - return convertToNSArray(columnHeaders); - } - - if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) { - AccessibilityObject* headerContainer = static_cast<AccessibilityTable*>(m_object)->headerContainer(); - if (headerContainer) - return headerContainer->wrapper(); - return nil; - } - - if ([attributeName isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector rowHeaders; - static_cast<AccessibilityTable*>(m_object)->rowHeaders(rowHeaders); - return convertToNSArray(rowHeaders); - } - - if ([attributeName isEqualToString:NSAccessibilityVisibleCellsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector cells; - static_cast<AccessibilityTable*>(m_object)->cells(cells); - return convertToNSArray(cells); - } - } - - if (m_object->isTableColumn()) { - if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) - return [NSNumber numberWithInt:static_cast<AccessibilityTableColumn*>(m_object)->columnIndex()]; - - // rows attribute for a column is the list of all the elements in that column at each row - if ([attributeName isEqualToString:NSAccessibilityRowsAttribute] || - [attributeName isEqualToString:NSAccessibilityVisibleRowsAttribute]) { - return convertToNSArray(static_cast<AccessibilityTableColumn*>(m_object)->children()); - } - if ([attributeName isEqualToString:NSAccessibilityHeaderAttribute]) { - AccessibilityObject* header = static_cast<AccessibilityTableColumn*>(m_object)->headerObject(); - if (!header) - return nil; - return header->wrapper(); - } - } - - if (m_object->isTableCell()) { - if ([attributeName isEqualToString:NSAccessibilityRowIndexRangeAttribute]) { - pair<unsigned, unsigned> rowRange; - static_cast<AccessibilityTableCell*>(m_object)->rowIndexRange(rowRange); - return [NSValue valueWithRange:NSMakeRange(rowRange.first, rowRange.second)]; - } - if ([attributeName isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) { - pair<unsigned, unsigned> columnRange; - static_cast<AccessibilityTableCell*>(m_object)->columnIndexRange(columnRange); - return [NSValue valueWithRange:NSMakeRange(columnRange.first, columnRange.second)]; - } - } - - if (m_object->isTree()) { - if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector selectedChildrenCopy; - m_object->selectedChildren(selectedChildrenCopy); - return convertToNSArray(selectedChildrenCopy); - } - if ([attributeName isEqualToString:NSAccessibilityRowsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector rowsCopy; - m_object->ariaTreeRows(rowsCopy); - return convertToNSArray(rowsCopy); - } - - // TreeRoles do not support columns, but Mac AX expects to be able to ask about columns at the least. - if ([attributeName isEqualToString:NSAccessibilityColumnsAttribute]) - return [NSArray array]; - } - - if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) { - if (m_object->isTreeItem()) { - AccessibilityObject* parent = m_object->parentObject(); - for (; parent && !parent->isTree(); parent = parent->parentObject()) - { } - - if (!parent) - return nil; - - // Find the index of this item by iterating the parents. - AccessibilityObject::AccessibilityChildrenVector rowsCopy; - parent->ariaTreeRows(rowsCopy); - size_t count = rowsCopy.size(); - for (size_t k = 0; k < count; ++k) - if (rowsCopy[k]->wrapper() == self) - return [NSNumber numberWithUnsignedInt:k]; - - return nil; - } - if (m_object->isTableRow()) { - if ([attributeName isEqualToString:NSAccessibilityIndexAttribute]) - return [NSNumber numberWithInt:static_cast<AccessibilityTableRow*>(m_object)->rowIndex()]; - } - } - - // The rows that are considered inside this row. - if ([attributeName isEqualToString:NSAccessibilityDisclosedRowsAttribute]) { - if (m_object->isTreeItem()) { - AccessibilityObject::AccessibilityChildrenVector rowsCopy; - m_object->ariaTreeItemDisclosedRows(rowsCopy); - return convertToNSArray(rowsCopy); - } else if (m_object->isARIATreeGridRow()) { - AccessibilityObject::AccessibilityChildrenVector rowsCopy; - static_cast<AccessibilityARIAGridRow*>(m_object)->disclosedRows(rowsCopy); - return convertToNSArray(rowsCopy); - } - } - - // The row that contains this row. It should be the same as the first parent that is a treeitem. - if ([attributeName isEqualToString:NSAccessibilityDisclosedByRowAttribute]) { - if (m_object->isTreeItem()) { - AccessibilityObject* parent = m_object->parentObject(); - while (parent) { - if (parent->isTreeItem()) - return parent->wrapper(); - // If the parent is the tree itself, then this value == nil. - if (parent->isTree()) - return nil; - parent = parent->parentObject(); - } - return nil; - } else if (m_object->isARIATreeGridRow()) { - AccessibilityObject* row = static_cast<AccessibilityARIAGridRow*>(m_object)->disclosedByRow(); - if (!row) - return nil; - return row->wrapper(); - } - } - - if ([attributeName isEqualToString:NSAccessibilityDisclosureLevelAttribute]) { - // Convert from 1-based level (from aria-level spec) to 0-based level (Mac) - int level = m_object->hierarchicalLevel(); - if (level > 0) - level -= 1; - return [NSNumber numberWithInt:level]; - } - if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) - return [NSNumber numberWithBool:m_object->isExpanded()]; - - if ((m_object->isListBox() || m_object->isList()) && [attributeName isEqualToString:NSAccessibilityOrientationAttribute]) - return NSAccessibilityVerticalOrientationValue; - - if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) - return [self textMarkerRangeForSelection]; - - if (m_object->renderer()) { - if ([attributeName isEqualToString: @"AXStartTextMarker"]) - return [self textMarkerForVisiblePosition:startOfDocument(m_object->renderer()->document())]; - if ([attributeName isEqualToString: @"AXEndTextMarker"]) - return [self textMarkerForVisiblePosition:endOfDocument(m_object->renderer()->document())]; - } - - if ([attributeName isEqualToString:NSAccessibilityBlockQuoteLevelAttribute]) - return [NSNumber numberWithInt:m_object->blockquoteLevel()]; - if ([attributeName isEqualToString:@"AXTableLevel"]) - return [NSNumber numberWithInt:m_object->tableLevel()]; - - if ([attributeName isEqualToString: NSAccessibilityLinkedUIElementsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector linkedUIElements; - m_object->linkedUIElements(linkedUIElements); - if (linkedUIElements.size() == 0) - return nil; - return convertToNSArray(linkedUIElements); - } - - if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) - return [NSNumber numberWithBool:m_object->isSelected()]; - - if ([attributeName isEqualToString: NSAccessibilityServesAsTitleForUIElementsAttribute] && m_object->isMenuButton()) { - AccessibilityObject* uiElement = static_cast<AccessibilityRenderObject*>(m_object)->menuForMenuButton(); - if (uiElement) - return [NSArray arrayWithObject:uiElement->wrapper()]; - } - - if ([attributeName isEqualToString:NSAccessibilityTitleUIElementAttribute]) { - if (!m_object->exposesTitleUIElement()) - return nil; - - AccessibilityObject* obj = m_object->titleUIElement(); - if (obj) - return obj->wrapper(); - return nil; - } - - if ([attributeName isEqualToString:NSAccessibilityValueDescriptionAttribute]) - return m_object->valueDescription(); - - if ([attributeName isEqualToString:NSAccessibilityOrientationAttribute]) { - AccessibilityOrientation elementOrientation = m_object->orientation(); - if (elementOrientation == AccessibilityOrientationVertical) - return NSAccessibilityVerticalOrientationValue; - if (elementOrientation == AccessibilityOrientationHorizontal) - return NSAccessibilityHorizontalOrientationValue; - return nil; - } - - if ([attributeName isEqualToString:NSAccessibilityHorizontalScrollBarAttribute]) { - AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientationHorizontal); - if (scrollBar) - return scrollBar->wrapper(); - return nil; - } - if ([attributeName isEqualToString:NSAccessibilityVerticalScrollBarAttribute]) { - AccessibilityObject* scrollBar = m_object->scrollBar(AccessibilityOrientationVertical); - if (scrollBar) - return scrollBar->wrapper(); - return nil; - } - - if ([attributeName isEqualToString:NSAccessibilitySortDirectionAttribute]) { - switch (m_object->sortDirection()) { - case SortDirectionAscending: - return NSAccessibilityAscendingSortDirectionValue; - case SortDirectionDescending: - return NSAccessibilityDescendingSortDirectionValue; - default: - return NSAccessibilityUnknownSortDirectionValue; - } - } - - if ([attributeName isEqualToString:NSAccessibilityLanguageAttribute]) - return m_object->language(); - - if ([attributeName isEqualToString:NSAccessibilityExpandedAttribute]) - return [NSNumber numberWithBool:m_object->isExpanded()]; - - if ([attributeName isEqualToString:NSAccessibilityRequiredAttribute]) - return [NSNumber numberWithBool:m_object->isRequired()]; - - if ([attributeName isEqualToString:NSAccessibilityInvalidAttribute]) - return m_object->invalidStatus(); - - if ([attributeName isEqualToString:NSAccessibilityOwnsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector ariaOwns; - m_object->ariaOwnsElements(ariaOwns); - return convertToNSArray(ariaOwns); - } - - if ([attributeName isEqualToString:NSAccessibilityARIAPosInSetAttribute]) - return [NSNumber numberWithInt:m_object->ariaPosInSet()]; - if ([attributeName isEqualToString:NSAccessibilityARIASetSizeAttribute]) - return [NSNumber numberWithInt:m_object->ariaSetSize()]; - - if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) - return [NSNumber numberWithBool:m_object->isARIAGrabbed()]; - - if ([attributeName isEqualToString:NSAccessibilityDropEffectsAttribute]) { - Vector<String> dropEffects; - m_object->determineARIADropEffects(dropEffects); - size_t length = dropEffects.size(); - - NSMutableArray* dropEffectsArray = [NSMutableArray arrayWithCapacity:length]; - for (size_t i = 0; i < length; ++i) - [dropEffectsArray addObject:dropEffects[i]]; - return dropEffectsArray; - } - - if ([attributeName isEqualToString:NSAccessibilityPlaceholderValueAttribute]) - return m_object->placeholderValue(); - - if ([attributeName isEqualToString:NSAccessibilityHasPopupAttribute]) - return [NSNumber numberWithBool:m_object->ariaHasPopup()]; - - // ARIA Live region attributes. - if ([attributeName isEqualToString:NSAccessibilityARIALiveAttribute]) - return m_object->ariaLiveRegionStatus(); - if ([attributeName isEqualToString:NSAccessibilityARIARelevantAttribute]) - return m_object->ariaLiveRegionRelevant(); - if ([attributeName isEqualToString:NSAccessibilityARIAAtomicAttribute]) - return [NSNumber numberWithBool:m_object->ariaLiveRegionAtomic()]; - if ([attributeName isEqualToString:NSAccessibilityARIABusyAttribute]) - return [NSNumber numberWithBool:m_object->ariaLiveRegionBusy()]; - - // MathML Attributes. - if (m_object->isMathElement()) { - if ([attributeName isEqualToString:NSAccessibilityMathRootIndexAttribute]) - return (m_object->mathRootIndexObject()) ? m_object->mathRootIndexObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathRootRadicandAttribute]) - return (m_object->mathRadicandObject()) ? m_object->mathRadicandObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathFractionNumeratorAttribute]) - return (m_object->mathNumeratorObject()) ? m_object->mathNumeratorObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathFractionDenominatorAttribute]) - return (m_object->mathDenominatorObject()) ? m_object->mathDenominatorObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathBaseAttribute]) - return (m_object->mathBaseObject()) ? m_object->mathBaseObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathSubscriptAttribute]) - return (m_object->mathSubscriptObject()) ? m_object->mathSubscriptObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathSuperscriptAttribute]) - return (m_object->mathSuperscriptObject()) ? m_object->mathSuperscriptObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathUnderAttribute]) - return (m_object->mathUnderObject()) ? m_object->mathUnderObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathOverAttribute]) - return (m_object->mathOverObject()) ? m_object->mathOverObject()->wrapper() : 0; - if ([attributeName isEqualToString:NSAccessibilityMathFencedOpenAttribute]) - return m_object->mathFencedOpenString(); - if ([attributeName isEqualToString:NSAccessibilityMathFencedCloseAttribute]) - return m_object->mathFencedCloseString(); - if ([attributeName isEqualToString:NSAccessibilityMathLineThicknessAttribute]) - return [NSNumber numberWithInteger:m_object->mathLineThickness()]; - if ([attributeName isEqualToString:NSAccessibilityMathPostscriptsAttribute]) - return [self accessibilityMathPostscriptPairs]; - if ([attributeName isEqualToString:NSAccessibilityMathPrescriptsAttribute]) - return [self accessibilityMathPrescriptPairs]; - } - - // this is used only by DumpRenderTree for testing - if ([attributeName isEqualToString:@"AXClickPoint"]) - return [NSValue valueWithPoint:m_object->clickPoint()]; - - // This is used by DRT to verify CSS3 speech works. - if ([attributeName isEqualToString:@"AXDRTSpeechAttribute"]) { - ESpeak speakProperty = m_object->speakProperty(); - switch (speakProperty) { - case SpeakNone: - return @"none"; - case SpeakSpellOut: - return @"spell-out"; - case SpeakDigits: - return @"digits"; - case SpeakLiteralPunctuation: - return @"literal-punctuation"; - case SpeakNoPunctuation: - return @"no-punctuation"; - default: - case SpeakNormal: - return @"normal"; - } - } - - // Used by DRT to find an accessible node by its element id. - if ([attributeName isEqualToString:@"AXDRTElementIdAttribute"]) - return m_object->getAttribute(idAttr); - - return nil; -} - -- (NSString *)accessibilityPlatformMathSubscriptKey -{ - return NSAccessibilityMathSubscriptAttribute; -} - -- (NSString *)accessibilityPlatformMathSuperscriptKey -{ - return NSAccessibilityMathSuperscriptAttribute; -} - -- (id)accessibilityFocusedUIElement -{ - if (![self updateObjectBackingStore]) - return nil; - - RefPtr<AccessibilityObject> focusedObj = m_object->focusedUIElement(); - - if (!focusedObj) - return nil; - - return focusedObj->wrapper(); -} - -- (id)accessibilityHitTest:(NSPoint)point -{ - if (![self updateObjectBackingStore]) - return nil; - - m_object->updateChildrenIfNecessary(); - RefPtr<AccessibilityObject> axObject = m_object->accessibilityHitTest(IntPoint(point)); - if (axObject) - return NSAccessibilityUnignoredAncestor(axObject->wrapper()); - return NSAccessibilityUnignoredAncestor(self); -} - -- (BOOL)accessibilityIsAttributeSettable:(NSString*)attributeName -{ - if (![self updateObjectBackingStore]) - return nil; - - if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) - return YES; - - if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) - return m_object->canSetFocusAttribute(); - - if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) - return m_object->canSetValueAttribute(); - - if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) - return m_object->canSetSelectedAttribute(); - - if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) - return m_object->canSetSelectedChildrenAttribute(); - - if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) - return m_object->canSetExpandedAttribute(); - - if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) - return YES; - - if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute] || - [attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute] || - [attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) - return m_object->canSetTextRangeAttributes(); - - if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) - return YES; - - return NO; -} - -// accessibilityShouldUseUniqueId is an AppKit method we override so that -// objects will be given a unique ID, and therefore allow AppKit to know when they -// become obsolete (e.g. when the user navigates to a new web page, making this one -// unrendered but not deallocated because it is in the back/forward cache). -// It is important to call NSAccessibilityUnregisterUniqueIdForUIElement in the -// appropriate place (e.g. dealloc) to remove these non-retained references from -// AppKit's id mapping tables. We do this in detach by calling unregisterUniqueIdForUIElement. -// -// Registering an object is also required for observing notifications. Only registered objects can be observed. -- (BOOL)accessibilityIsIgnored -{ - if (![self updateObjectBackingStore]) - return YES; - - if (m_object->isAttachment()) - return [[self attachmentView] accessibilityIsIgnored]; - return m_object->accessibilityIsIgnored(); -} - -- (NSArray* )accessibilityParameterizedAttributeNames -{ - if (![self updateObjectBackingStore]) - return nil; - - if (m_object->isAttachment()) - return nil; - - static NSArray* paramAttrs = nil; - static NSArray* textParamAttrs = nil; - static NSArray* tableParamAttrs = nil; - static NSArray* webAreaParamAttrs = nil; - if (paramAttrs == nil) { - paramAttrs = [[NSArray alloc] initWithObjects: - @"AXUIElementForTextMarker", - @"AXTextMarkerRangeForUIElement", - @"AXLineForTextMarker", - @"AXTextMarkerRangeForLine", - @"AXStringForTextMarkerRange", - @"AXTextMarkerForPosition", - @"AXBoundsForTextMarkerRange", - @"AXAttributedStringForTextMarkerRange", - @"AXTextMarkerRangeForUnorderedTextMarkers", - @"AXNextTextMarkerForTextMarker", - @"AXPreviousTextMarkerForTextMarker", - @"AXLeftWordTextMarkerRangeForTextMarker", - @"AXRightWordTextMarkerRangeForTextMarker", - @"AXLeftLineTextMarkerRangeForTextMarker", - @"AXRightLineTextMarkerRangeForTextMarker", - @"AXSentenceTextMarkerRangeForTextMarker", - @"AXParagraphTextMarkerRangeForTextMarker", - @"AXNextWordEndTextMarkerForTextMarker", - @"AXPreviousWordStartTextMarkerForTextMarker", - @"AXNextLineEndTextMarkerForTextMarker", - @"AXPreviousLineStartTextMarkerForTextMarker", - @"AXNextSentenceEndTextMarkerForTextMarker", - @"AXPreviousSentenceStartTextMarkerForTextMarker", - @"AXNextParagraphEndTextMarkerForTextMarker", - @"AXPreviousParagraphStartTextMarkerForTextMarker", - @"AXStyleTextMarkerRangeForTextMarker", - @"AXLengthForTextMarkerRange", - NSAccessibilityBoundsForRangeParameterizedAttribute, - NSAccessibilityStringForRangeParameterizedAttribute, - NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute, - nil]; - } - - if (textParamAttrs == nil) { - NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; - [tempArray addObject:(NSString*)kAXLineForIndexParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXRangeForLineParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXStringForRangeParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXRangeForPositionParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXRangeForIndexParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXBoundsForRangeParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXRTFForRangeParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXAttributedStringForRangeParameterizedAttribute]; - [tempArray addObject:(NSString*)kAXStyleRangeForIndexParameterizedAttribute]; - textParamAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (tableParamAttrs == nil) { - NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; - [tempArray addObject:NSAccessibilityCellForColumnAndRowParameterizedAttribute]; - tableParamAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - if (!webAreaParamAttrs) { - NSMutableArray* tempArray = [[NSMutableArray alloc] initWithArray:paramAttrs]; - [tempArray addObject:NSAccessibilityTextMarkerForIndexParameterizedAttribute]; - [tempArray addObject:NSAccessibilityTextMarkerIsValidParameterizedAttribute]; - [tempArray addObject:NSAccessibilityIndexForTextMarkerParameterizedAttribute]; - webAreaParamAttrs = [[NSArray alloc] initWithArray:tempArray]; - [tempArray release]; - } - - if (m_object->isPasswordField()) - return [NSArray array]; - - if (!m_object->isAccessibilityRenderObject()) - return paramAttrs; - - if (m_object->isTextControl()) - return textParamAttrs; - - if (m_object->isAccessibilityTable()) - return tableParamAttrs; - - if (m_object->isMenuRelated()) - return nil; - - if (m_object->isWebArea()) - return webAreaParamAttrs; - - return paramAttrs; -} - -- (void)accessibilityPerformPressAction -{ - if (![self updateObjectBackingStore]) - return; - - if (m_object->isAttachment()) - [[self attachmentView] accessibilityPerformAction:NSAccessibilityPressAction]; - else - m_object->press(); -} - -- (void)accessibilityPerformIncrementAction -{ - if (![self updateObjectBackingStore]) - return; - - if (m_object->isAttachment()) - [[self attachmentView] accessibilityPerformAction:NSAccessibilityIncrementAction]; - else - m_object->increment(); -} - -- (void)accessibilityPerformDecrementAction -{ - if (![self updateObjectBackingStore]) - return; - - if (m_object->isAttachment()) - [[self attachmentView] accessibilityPerformAction:NSAccessibilityDecrementAction]; - else - m_object->decrement(); -} - -- (void)accessibilityPerformShowMenuAction -{ - if (m_object->roleValue() == ComboBoxRole) - m_object->setIsExpanded(true); - else { - // This needs to be performed in an iteration of the run loop that did not start from an AX call. - // If it's the same run loop iteration, the menu open notification won't be sent - [self performSelector:@selector(accessibilityShowContextMenu) withObject:nil afterDelay:0.0]; - } -} - -- (void)accessibilityShowContextMenu -{ - Page* page = m_object->page(); - if (!page) - return; - - IntRect rect = pixelSnappedIntRect(m_object->elementRect()); - FrameView* frameView = m_object->documentFrameView(); - - // On WK2, we need to account for the scroll position. - // On WK1, this isn't necessary, it's taken care of by the attachment views. - if (frameView && !frameView->platformWidget()) { - // Find the appropriate scroll view to use to convert the contents to the window. - for (AccessibilityObject* parent = m_object->parentObject(); parent; parent = parent->parentObject()) { - if (parent->isAccessibilityScrollView()) { - ScrollView* scrollView = toAccessibilityScrollView(parent)->scrollView(); - rect = scrollView->contentsToRootView(rect); - break; - } - } - } - - page->contextMenuController()->showContextMenuAt(page->mainFrame(), rect.center()); -} - -- (void)accessibilityScrollToVisible -{ - m_object->scrollToMakeVisible(); -} - -- (void)accessibilityPerformAction:(NSString*)action -{ - if (![self updateObjectBackingStore]) - return; - - if ([action isEqualToString:NSAccessibilityPressAction]) - [self accessibilityPerformPressAction]; - - else if ([action isEqualToString:NSAccessibilityShowMenuAction]) - [self accessibilityPerformShowMenuAction]; - - else if ([action isEqualToString:NSAccessibilityIncrementAction]) - [self accessibilityPerformIncrementAction]; - - else if ([action isEqualToString:NSAccessibilityDecrementAction]) - [self accessibilityPerformDecrementAction]; - - else if ([action isEqualToString:NSAccessibilityScrollToVisibleAction]) - [self accessibilityScrollToVisible]; -} - -- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attributeName -{ - if (![self updateObjectBackingStore]) - return; - - id textMarkerRange = nil; - NSNumber* number = nil; - NSString* string = nil; - NSRange range = {0, 0}; - NSArray* array = nil; - - // decode the parameter - if (AXObjectIsTextMarkerRange(value)) - textMarkerRange = value; - - else if ([value isKindOfClass:[NSNumber self]]) - number = value; - - else if ([value isKindOfClass:[NSString self]]) - string = value; - - else if ([value isKindOfClass:[NSValue self]]) - range = [value rangeValue]; - - else if ([value isKindOfClass:[NSArray self]]) - array = value; - - // handle the command - if ([attributeName isEqualToString: @"AXSelectedTextMarkerRange"]) { - ASSERT(textMarkerRange); - m_object->setSelectedVisiblePositionRange([self visiblePositionRangeForTextMarkerRange:textMarkerRange]); - } else if ([attributeName isEqualToString: NSAccessibilityFocusedAttribute]) { - ASSERT(number); - - bool focus = [number boolValue]; - - // If focus is just set without making the view the first responder, then keyboard focus won't move to the right place. - if (focus && m_object->isWebArea() && !m_object->document()->frame()->selection()->isFocusedAndActive()) { - FrameView* frameView = m_object->documentFrameView(); - Page* page = m_object->page(); - if (page && frameView) { - ChromeClient* client = page->chrome().client(); - client->focus(); - if (frameView->platformWidget()) - client->makeFirstResponder(frameView->platformWidget()); - else - client->makeFirstResponder(); - } - } - - m_object->setFocused(focus); - } else if ([attributeName isEqualToString: NSAccessibilityValueAttribute]) { - if (number && m_object->canSetNumericValue()) - m_object->setValue([number floatValue]); - else if (string) - m_object->setValue(string); - } else if ([attributeName isEqualToString: NSAccessibilitySelectedAttribute]) { - if (!number) - return; - m_object->setSelected([number boolValue]); - } else if ([attributeName isEqualToString: NSAccessibilitySelectedChildrenAttribute]) { - if (!array || m_object->roleValue() != ListBoxRole) - return; - AccessibilityObject::AccessibilityChildrenVector selectedChildren; - convertToVector(array, selectedChildren); - static_cast<AccessibilityListBox*>(m_object)->setSelectedChildren(selectedChildren); - } else if (m_object->isTextControl()) { - if ([attributeName isEqualToString: NSAccessibilitySelectedTextAttribute]) { - m_object->setSelectedText(string); - } else if ([attributeName isEqualToString: NSAccessibilitySelectedTextRangeAttribute]) { - m_object->setSelectedTextRange(PlainTextRange(range.location, range.length)); - } else if ([attributeName isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute]) { - m_object->makeRangeVisible(PlainTextRange(range.location, range.length)); - } - } else if ([attributeName isEqualToString:NSAccessibilityDisclosingAttribute]) - m_object->setIsExpanded([number boolValue]); - else if ([attributeName isEqualToString:NSAccessibilitySelectedRowsAttribute]) { - AccessibilityObject::AccessibilityChildrenVector selectedRows; - convertToVector(array, selectedRows); - if (m_object->isTree() || m_object->isAccessibilityTable()) - m_object->setSelectedRows(selectedRows); - } else if ([attributeName isEqualToString:NSAccessibilityGrabbedAttribute]) - m_object->setARIAGrabbed([number boolValue]); -} - -static RenderObject* rendererForView(NSView* view) -{ - if (![view conformsToProtocol:@protocol(WebCoreFrameView)]) - return 0; - - NSView<WebCoreFrameView>* frameView = (NSView<WebCoreFrameView>*)view; - Frame* frame = [frameView _web_frame]; - if (!frame) - return 0; - - Node* node = frame->document()->ownerElement(); - if (!node) - return 0; - - return node->renderer(); -} - -- (id)_accessibilityParentForSubview:(NSView*)subview -{ - RenderObject* renderer = rendererForView(subview); - if (!renderer) - return nil; - - AccessibilityObject* obj = renderer->document()->axObjectCache()->getOrCreate(renderer); - if (obj) - return obj->parentObjectUnignored()->wrapper(); - return nil; -} - -- (NSString*)accessibilityActionDescription:(NSString*)action -{ - // we have no custom actions - return NSAccessibilityActionDescription(action); -} - -// The CFAttributedStringType representation of the text associated with this accessibility -// object that is specified by the given range. -- (NSAttributedString*)doAXAttributedStringForRange:(NSRange)range -{ - PlainTextRange textRange = PlainTextRange(range.location, range.length); - VisiblePositionRange visiblePosRange = m_object->visiblePositionRangeForRange(textRange); - return [self doAXAttributedStringForTextMarkerRange:[self textMarkerRangeFromVisiblePositions:visiblePosRange.start endPosition:visiblePosRange.end]]; -} - -- (NSRange)_convertToNSRange:(Range*)range -{ - NSRange result = NSMakeRange(NSNotFound, 0); - if (!range || !range->startContainer()) - return result; - - Document* document = m_object->document(); - if (!document) - return result; - - size_t location; - size_t length; - TextIterator::getLocationAndLengthFromRange(document->documentElement(), range, location, length); - result.location = location; - result.length = length; - - return result; -} - -- (NSInteger)_indexForTextMarker:(id)marker -{ - if (!marker) - return NSNotFound; - - VisibleSelection selection([self visiblePositionForTextMarker:marker]); - return [self _convertToNSRange:selection.toNormalizedRange().get()].location; -} - -- (id)_textMarkerForIndex:(NSInteger)textIndex -{ - Document* document = m_object->document(); - if (!document) - return nil; - - PassRefPtr<Range> textRange = TextIterator::rangeFromLocationAndLength(document->documentElement(), textIndex, 0); - if (!textRange || !textRange->boundaryPointsValid()) - return nil; - - VisiblePosition position(textRange->startPosition()); - return [self textMarkerForVisiblePosition:position]; -} - -// The RTF representation of the text associated with this accessibility object that is -// specified by the given range. -- (NSData*)doAXRTFForRange:(NSRange)range -{ - NSAttributedString* attrString = [self doAXAttributedStringForRange:range]; - return [attrString RTFFromRange: NSMakeRange(0, [attrString length]) documentAttributes: nil]; -} - -- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter -{ - id textMarker = nil; - id textMarkerRange = nil; - NSNumber* number = nil; - NSArray* array = nil; - NSDictionary* dictionary = nil; - RefPtr<AccessibilityObject> uiElement = 0; - NSPoint point = NSZeroPoint; - bool pointSet = false; - NSRange range = {0, 0}; - bool rangeSet = false; - - // basic parameter validation - if (!m_object || !attribute || !parameter) - return nil; - - if (![self updateObjectBackingStore]) - return nil; - - // common parameter type check/casting. Nil checks in handlers catch wrong type case. - // NOTE: This assumes nil is not a valid parameter, because it is indistinguishable from - // a parameter of the wrong type. - if (AXObjectIsTextMarker(parameter)) - textMarker = parameter; - - else if (AXObjectIsTextMarkerRange(parameter)) - textMarkerRange = parameter; - - else if ([parameter isKindOfClass:[WebAccessibilityObjectWrapper self]]) - uiElement = [(WebAccessibilityObjectWrapper*)parameter accessibilityObject]; - - else if ([parameter isKindOfClass:[NSNumber self]]) - number = parameter; - - else if ([parameter isKindOfClass:[NSArray self]]) - array = parameter; - - else if ([parameter isKindOfClass:[NSDictionary self]]) - dictionary = parameter; - - else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSPoint)) == 0) { - pointSet = true; - point = [(NSValue*)parameter pointValue]; - - } else if ([parameter isKindOfClass:[NSValue self]] && strcmp([(NSValue*)parameter objCType], @encode(NSRange)) == 0) { - rangeSet = true; - range = [(NSValue*)parameter rangeValue]; - } else { - // Attribute type is not supported. Allow super to handle. - return [super accessibilityAttributeValue:attribute forParameter:parameter]; - } - - // dispatch - if ([attribute isEqualToString:NSAccessibilityUIElementsForSearchPredicateParameterizedAttribute]) { - AccessibilityObject* startObject = 0; - if ([[dictionary objectForKey:@"AXStartElement"] isKindOfClass:[WebAccessibilityObjectWrapper self]]) - startObject = [(WebAccessibilityObjectWrapper*)[dictionary objectForKey:@"AXStartElement"] accessibilityObject]; - - AccessibilitySearchDirection searchDirection = SearchDirectionNext; - if ([[dictionary objectForKey:@"AXDirection"] isKindOfClass:[NSString self]]) - searchDirection = ([(NSString*)[dictionary objectForKey:@"AXDirection"] isEqualToString:@"AXDirectionNext"]) ? SearchDirectionNext : SearchDirectionPrevious; - - String searchText; - if ([[dictionary objectForKey:@"AXSearchText"] isKindOfClass:[NSString self]]) - searchText = (CFStringRef)[dictionary objectForKey:@"AXSearchText"]; - - unsigned resultsLimit = 0; - if ([[dictionary objectForKey:@"AXResultsLimit"] isKindOfClass:[NSNumber self]]) - resultsLimit = [(NSNumber*)[dictionary objectForKey:@"AXResultsLimit"] unsignedIntValue]; - - BOOL visibleOnly = NO; - if ([[dictionary objectForKey:@"AXVisibleOnly"] isKindOfClass:[NSNumber self]]) - visibleOnly = [(NSNumber*)[dictionary objectForKey:@"AXVisibleOnly"] boolValue]; - - AccessibilitySearchCriteria criteria = AccessibilitySearchCriteria(startObject, searchDirection, &searchText, resultsLimit, visibleOnly); - - id searchKeyEntry = [dictionary objectForKey:@"AXSearchKey"]; - if ([searchKeyEntry isKindOfClass:[NSString class]]) - criteria.searchKeys.append(accessibilitySearchKeyForString((CFStringRef)searchKeyEntry)); - else if ([searchKeyEntry isKindOfClass:[NSArray class]]) { - size_t length = static_cast<size_t>([(NSArray *)searchKeyEntry count]); - criteria.searchKeys.reserveInitialCapacity(length); - for (size_t i = 0; i < length; ++i) { - id searchKey = [(NSArray *)searchKeyEntry objectAtIndex:i]; - if ([searchKey isKindOfClass:[NSString class]]) - criteria.searchKeys.append(accessibilitySearchKeyForString((CFStringRef)searchKey)); - } - } - - AccessibilityObject::AccessibilityChildrenVector results; - m_object->findMatchingObjects(&criteria, results); - - return convertToNSArray(results); - } - - if ([attribute isEqualToString:NSAccessibilityTextMarkerIsValidParameterizedAttribute]) { - VisiblePosition pos = [self visiblePositionForTextMarker:textMarker]; - return [NSNumber numberWithBool:!pos.isNull()]; - } - if ([attribute isEqualToString:NSAccessibilityIndexForTextMarkerParameterizedAttribute]) { - return [NSNumber numberWithInteger:[self _indexForTextMarker:textMarker]]; - } - if ([attribute isEqualToString:NSAccessibilityTextMarkerForIndexParameterizedAttribute]) { - return [self _textMarkerForIndex:[number integerValue]]; - } - - if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - AccessibilityObject* axObject = m_object->accessibilityObjectForPosition(visiblePos); - if (!axObject) - return nil; - return axObject->wrapper(); - } - - if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) { - VisiblePositionRange vpRange = uiElement.get()->visiblePositionRange(); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXLineForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [NSNumber numberWithUnsignedInt:m_object->lineForPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXTextMarkerRangeForLine"]) { - VisiblePositionRange vpRange = m_object->visiblePositionRangeForLine([number intValue]); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) { - VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; - return m_object->stringForVisiblePositionRange(visiblePosRange); - } - - if ([attribute isEqualToString:@"AXTextMarkerForPosition"]) { - IntPoint webCorePoint = IntPoint(point); - return pointSet ? [self textMarkerForVisiblePosition:m_object->visiblePositionForPoint(webCorePoint)] : nil; - } - - if ([attribute isEqualToString:@"AXBoundsForTextMarkerRange"]) { - VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; - NSRect rect = m_object->boundsForVisiblePositionRange(visiblePosRange); - return [NSValue valueWithRect:rect]; - } - - if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) { - VisiblePosition start = m_object->visiblePositionForIndex(range.location); - VisiblePosition end = m_object->visiblePositionForIndex(range.location+range.length); - if (start.isNull() || end.isNull()) - return nil; - NSRect rect = m_object->boundsForVisiblePositionRange(VisiblePositionRange(start, end)); - return [NSValue valueWithRect:rect]; - } - - if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) { - VisiblePosition start = m_object->visiblePositionForIndex(range.location); - VisiblePosition end = m_object->visiblePositionForIndex(range.location+range.length); - if (start.isNull() || end.isNull()) - return nil; - return m_object->stringForVisiblePositionRange(VisiblePositionRange(start, end)); - } - - if ([attribute isEqualToString:@"AXAttributedStringForTextMarkerRange"]) - return [self doAXAttributedStringForTextMarkerRange:textMarkerRange]; - - if ([attribute isEqualToString:@"AXTextMarkerRangeForUnorderedTextMarkers"]) { - if ([array count] < 2) - return nil; - - id textMarker1 = [array objectAtIndex:0]; - id textMarker2 = [array objectAtIndex:1]; - if (!AXObjectIsTextMarker(textMarker1) || !AXObjectIsTextMarker(textMarker2)) - return nil; - - VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:(textMarker1)]; - VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:(textMarker2)]; - VisiblePositionRange vpRange = m_object->visiblePositionRangeForUnorderedPositions(visiblePos1, visiblePos2); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->nextVisiblePosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->previousVisiblePosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXLeftWordTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->positionOfLeftWord(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXRightWordTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->positionOfRightWord(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXLeftLineTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->leftLineVisiblePositionRange(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXRightLineTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->rightLineVisiblePositionRange(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXSentenceTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->sentenceForPosition(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXParagraphTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->paragraphForPosition(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXNextWordEndTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->nextWordEnd(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXPreviousWordStartTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->previousWordStart(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXNextLineEndTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->nextLineEndPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXPreviousLineStartTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->previousLineStartPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXNextSentenceEndTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->nextSentenceEndPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXPreviousSentenceStartTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->previousSentenceStartPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXNextParagraphEndTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->nextParagraphEndPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXPreviousParagraphStartTextMarkerForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - return [self textMarkerForVisiblePosition:m_object->previousParagraphStartPosition(visiblePos)]; - } - - if ([attribute isEqualToString:@"AXStyleTextMarkerRangeForTextMarker"]) { - VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)]; - VisiblePositionRange vpRange = m_object->styleRangeForPosition(visiblePos); - return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end]; - } - - if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) { - VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; - int length = m_object->lengthForVisiblePositionRange(visiblePosRange); - if (length < 0) - return nil; - return [NSNumber numberWithInt:length]; - } - - // Used only by DumpRenderTree (so far). - if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) { - VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; - return [self textMarkerForVisiblePosition:visiblePosRange.start]; - } - - if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) { - VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange]; - return [self textMarkerForVisiblePosition:visiblePosRange.end]; - } - - if (m_object->isAccessibilityTable()) { - if ([attribute isEqualToString:NSAccessibilityCellForColumnAndRowParameterizedAttribute]) { - if (array == nil || [array count] != 2) - return nil; - AccessibilityTableCell* cell = static_cast<AccessibilityTable*>(m_object)->cellForColumnAndRow([[array objectAtIndex:0] unsignedIntValue], [[array objectAtIndex:1] unsignedIntValue]); - if (!cell) - return nil; - - return cell->wrapper(); - } - } - - if (m_object->isTextControl()) { - if ([attribute isEqualToString: (NSString *)kAXLineForIndexParameterizedAttribute]) { - int lineNumber = m_object->doAXLineForIndex([number intValue]); - if (lineNumber < 0) - return nil; - return [NSNumber numberWithUnsignedInt:lineNumber]; - } - - if ([attribute isEqualToString: (NSString *)kAXRangeForLineParameterizedAttribute]) { - PlainTextRange textRange = m_object->doAXRangeForLine([number intValue]); - return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; - } - - if ([attribute isEqualToString: (NSString*)kAXStringForRangeParameterizedAttribute]) { - PlainTextRange plainTextRange = PlainTextRange(range.location, range.length); - return rangeSet ? (id)(m_object->doAXStringForRange(plainTextRange)) : nil; - } - - if ([attribute isEqualToString: (NSString*)kAXRangeForPositionParameterizedAttribute]) { - if (!pointSet) - return nil; - IntPoint webCorePoint = IntPoint(point); - PlainTextRange textRange = m_object->doAXRangeForPosition(webCorePoint); - return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; - } - - if ([attribute isEqualToString: (NSString*)kAXRangeForIndexParameterizedAttribute]) { - PlainTextRange textRange = m_object->doAXRangeForIndex([number intValue]); - return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; - } - - if ([attribute isEqualToString: (NSString*)kAXBoundsForRangeParameterizedAttribute]) { - if (!rangeSet) - return nil; - PlainTextRange plainTextRange = PlainTextRange(range.location, range.length); - NSRect rect = m_object->doAXBoundsForRange(plainTextRange); - return [NSValue valueWithRect:rect]; - } - - if ([attribute isEqualToString: (NSString*)kAXRTFForRangeParameterizedAttribute]) - return rangeSet ? [self doAXRTFForRange:range] : nil; - - if ([attribute isEqualToString: (NSString*)kAXAttributedStringForRangeParameterizedAttribute]) - return rangeSet ? [self doAXAttributedStringForRange:range] : nil; - - if ([attribute isEqualToString: (NSString*)kAXStyleRangeForIndexParameterizedAttribute]) { - PlainTextRange textRange = m_object->doAXStyleRangeForIndex([number intValue]); - return [NSValue valueWithRange: NSMakeRange(textRange.start, textRange.length)]; - } - } - - // There are some parameters that super handles that are not explicitly returned by the list of the element's attributes. - // In that case it must be passed to super. - return [super accessibilityAttributeValue:attribute forParameter:parameter]; -} - -- (BOOL)accessibilitySupportsOverriddenAttributes -{ - return YES; -} - -- (BOOL)accessibilityShouldUseUniqueId -{ - // All AX object wrappers should use unique ID's because it's faster within AppKit to look them up. - return YES; -} - -// API that AppKit uses for faster access -- (NSUInteger)accessibilityIndexOfChild:(id)child -{ - if (![self updateObjectBackingStore]) - return NSNotFound; - - // Tree objects return their rows as their children. We can use the original method - // here, because we won't gain any speed up. - if (m_object->isTree()) - return [super accessibilityIndexOfChild:child]; - - const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); - - if (children.isEmpty()) - return [[self renderWidgetChildren] indexOfObject:child]; - - unsigned count = children.size(); - for (unsigned k = 0; k < count; ++k) { - WebAccessibilityObjectWrapper* wrapper = children[k]->wrapper(); - if (wrapper == child || (children[k]->isAttachment() && [wrapper attachmentView] == child)) - return k; - } - - return NSNotFound; -} - -- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute -{ - if (![self updateObjectBackingStore]) - return 0; - - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - // Tree items object returns a different set of children than those that are in children() - // because an AXOutline (the mac role is becomes) has some odd stipulations. - if (m_object->isTree() || m_object->isTreeItem()) - return [[self accessibilityAttributeValue:NSAccessibilityChildrenAttribute] count]; - - const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); - if (children.isEmpty()) - return [[self renderWidgetChildren] count]; - - return children.size(); - } - - return [super accessibilityArrayAttributeCount:attribute]; -} - -- (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount -{ - if (![self updateObjectBackingStore]) - return nil; - - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - if (m_object->children().isEmpty()) { - NSArray *children = [self renderWidgetChildren]; - if (!children) - return nil; - - NSUInteger childCount = [children count]; - if (index >= childCount) - return nil; - - NSUInteger arrayLength = min(childCount - index, maxCount); - return [children subarrayWithRange:NSMakeRange(index, arrayLength)]; - } else if (m_object->isTree()) { - // Tree objects return their rows as their children. We can use the original method in this case. - return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; - } - - const AccessibilityObject::AccessibilityChildrenVector& children = m_object->children(); - unsigned childCount = children.size(); - if (index >= childCount) - return nil; - - unsigned available = min(childCount - index, maxCount); - - NSMutableArray *subarray = [NSMutableArray arrayWithCapacity:available]; - for (unsigned added = 0; added < available; ++index, ++added) { - WebAccessibilityObjectWrapper* wrapper = children[index]->wrapper(); - if (wrapper) { - // The attachment view should be returned, otherwise AX palindrome errors occur. - if (children[index]->isAttachment() && [wrapper attachmentView]) - [subarray addObject:[wrapper attachmentView]]; - else - [subarray addObject:wrapper]; - } - } - - return subarray; - } - - return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; -} - -@end - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp b/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp deleted file mode 100644 index 54fe4a76a..000000000 --- a/Source/WebCore/accessibility/win/AXObjectCacheWin.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2008, 2009, 2010, 2013 Apple Inc. All Rights Reserved. - * Copyright (C) 2012 Serotek Corporation. 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. - */ - - -#include "config.h" -#include "AXObjectCache.h" - -#include "AccessibilityObject.h" -#include "Chrome.h" -#include "ChromeClient.h" -#include "Document.h" -#include "Page.h" -#include "RenderObject.h" - -// Provided by IAccessibleEventID.idl -#define IA2_EVENT_DOCUMENT_LOAD_COMPLETE 261 - -using namespace std; - -namespace WebCore { - -void AXObjectCache::detachWrapper(AccessibilityObject* obj) -{ - // On Windows, AccessibilityObjects are created when get_accChildCount is - // called, but they are not wrapped until get_accChild is called, so this - // object may not have a wrapper. - if (AccessibilityObjectWrapper* wrapper = obj->wrapper()) - wrapper->detach(); -} - -void AXObjectCache::attachWrapper(AccessibilityObject*) -{ - // On Windows, AccessibilityObjects are wrapped when the accessibility - // software requests them via get_accChild. -} - -void AXObjectCache::handleScrolledToAnchor(const Node* anchorNode) -{ - // The anchor node may not be accessible. Post the notification for the - // first accessible object. - postPlatformNotification(AccessibilityObject::firstAccessibleObjectFromNode(anchorNode), AXScrolledToAnchor); -} - -void AXObjectCache::postPlatformNotification(AccessibilityObject* obj, AXNotification notification) -{ - if (!obj) - return; - - Document* document = obj->document(); - if (!document) - return; - - Page* page = document->page(); - if (!page || !page->chrome().platformPageClient()) - return; - - DWORD msaaEvent; - switch (notification) { - case AXCheckedStateChanged: - msaaEvent = EVENT_OBJECT_STATECHANGE; - break; - - case AXFocusedUIElementChanged: - case AXActiveDescendantChanged: - msaaEvent = EVENT_OBJECT_FOCUS; - break; - - case AXScrolledToAnchor: - msaaEvent = EVENT_SYSTEM_SCROLLINGSTART; - break; - - case AXLayoutComplete: - msaaEvent = EVENT_OBJECT_REORDER; - break; - - case AXLoadComplete: - msaaEvent = IA2_EVENT_DOCUMENT_LOAD_COMPLETE; - break; - - case AXValueChanged: - case AXMenuListValueChanged: - msaaEvent = EVENT_OBJECT_VALUECHANGE; - break; - - case AXMenuListItemSelected: - msaaEvent = EVENT_OBJECT_SELECTION; - break; - - default: - return; - } - - // Windows will end up calling get_accChild() on the root accessible - // object for the WebView, passing the child ID that we specify below. We - // negate the AXID so we know that the caller is passing the ID of an - // element, not the index of a child element. - - ASSERT(obj->axObjectID() >= 1); - ASSERT(obj->axObjectID() <= numeric_limits<LONG>::max()); - - NotifyWinEvent(msaaEvent, page->chrome().platformPageClient(), OBJID_CLIENT, -static_cast<LONG>(obj->axObjectID())); -} - -void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject*, AXTextChange, unsigned, const String&) -{ -} - -void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* obj, AXLoadingEvent notification) -{ - if (!obj) - return; - - Document* document = obj->document(); - if (!document) - return; - - Page* page = document->page(); - if (!page) - return; - - if (notification == AXLoadingStarted) - page->chrome().client()->AXStartFrameLoad(); - else if (notification == AXLoadingFinished) - page->chrome().client()->AXFinishFrameLoad(); -} - -AXID AXObjectCache::platformGenerateAXID() const -{ - static AXID lastUsedID = 0; - - // Generate a new ID. Windows accessibility relies on a positive AXID, - // ranging from 1 to LONG_MAX. - AXID objID = lastUsedID; - do { - ++objID; - objID %= std::numeric_limits<LONG>::max(); - } while (objID == 0 || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); - - ASSERT(objID >= 1 && objID <= std::numeric_limits<LONG>::max()); - - lastUsedID = objID; - - return objID; -} - -void AXObjectCache::handleFocusedUIElementChanged(Node*, Node* newFocusedNode) -{ - if (!newFocusedNode) - return; - - Page* page = newFocusedNode->document()->page(); - if (!page || !page->chrome().platformPageClient()) - return; - - AccessibilityObject* focusedObject = focusedUIElementForPage(page); - if (!focusedObject) - return; - - ASSERT(!focusedObject->accessibilityIsIgnored()); - - postPlatformNotification(focusedObject, AXFocusedUIElementChanged); -} - -} // namespace WebCore diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp b/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp deleted file mode 100644 index 44122ef27..000000000 --- a/Source/WebCore/accessibility/win/AccessibilityObjectWin.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -#include "config.h" -#include "AccessibilityObject.h" - -#if HAVE(ACCESSIBILITY) - -namespace WebCore { - -bool AccessibilityObject::accessibilityIgnoreAttachment() const -{ - return false; -} - -AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const -{ - if (isMenuListPopup() || isMenuListOption()) - return IncludeObject; - - return DefaultBehavior; -} - -} // namespace WebCore - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp deleted file mode 100644 index ba3d53ba4..000000000 --- a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#include "config.h" -#include "AccessibilityObjectWrapperWin.h" - -#if HAVE(ACCESSIBILITY) - -#include "AXObjectCache.h" -#include "AccessibilityObject.h" -#include "BString.h" -#include "HTMLNames.h" -#include "QualifiedName.h" - -namespace WebCore { - -void AccessibilityObjectWrapper::accessibilityAttributeValue(const AtomicString& attributeName, VARIANT* result) -{ - // FIXME: This should be fleshed out to match the Mac version - - // Not a real concept on Windows, but used heavily in WebKit accessibility testing. - if (attributeName == "AXTitleUIElementAttribute") { - if (!m_object->exposesTitleUIElement()) - return; - - AccessibilityObject* obj = m_object->titleUIElement(); - if (obj) { - ASSERT(V_VT(result) == VT_EMPTY); - V_VT(result) = VT_UNKNOWN; - AccessibilityObjectWrapper* wrapper = obj->wrapper(); - V_UNKNOWN(result) = wrapper; - if (wrapper) - wrapper->AddRef(); - } - return; - } - - // Used by DRT to find an accessible node by its element id. - if (attributeName == "AXDRTElementIdAttribute") { - ASSERT(V_VT(result) == VT_EMPTY); - - V_VT(result) = VT_BSTR; - V_BSTR(result) = WebCore::BString(m_object->getAttribute(WebCore::HTMLNames::idAttr)).release(); - return; - } -} - - -} // namespace WebCore - -#endif // HAVE(ACCESSIBILITY) diff --git a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h b/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h deleted file mode 100644 index f32159117..000000000 --- a/Source/WebCore/accessibility/win/AccessibilityObjectWrapperWin.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - - -#ifndef AccessibilityObjectWrapperWin_h -#define AccessibilityObjectWrapperWin_h - -#include <wtf/text/AtomicString.h> - -namespace WebCore { - - class AccessibilityObject; - - class AccessibilityObjectWrapper : public IUnknown { - public: - // IUnknown - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) = 0; - virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; - virtual ULONG STDMETHODCALLTYPE Release(void) = 0; - - virtual void detach() = 0; - bool attached() const { return m_object; } - AccessibilityObject* accessibilityObject() const { return m_object; } - - void accessibilityAttributeValue(const AtomicString&, VARIANT*); - - protected: - AccessibilityObjectWrapper(AccessibilityObject* obj) : m_object(obj) { } - AccessibilityObjectWrapper() : m_object(0) { } - - AccessibilityObject* m_object; - }; - -} // namespace WebCore - -#endif // AccessibilityObjectWrapperWin_h |
