Changeset 195240 in webkit


Ignore:
Timestamp:
Jan 18, 2016 4:56:13 PM (8 years ago)
Author:
n_wang@apple.com
Message:

AX: [Mac] Implement next/previous text marker functions using TextIterator
https://bugs.webkit.org/show_bug.cgi?id=152728

Reviewed by Chris Fleizach.

Source/WebCore:

The existing AXTextMarker based calls are implemented using visible position, and that introduced
some bugs which make VoiceOver working incorrectly on Mac sometimes. Since TextIterator uses rendering
position, we tried to use it to refactor those AXTextMarker based calls.
In this patch, I implemented functions to navigate to previous/next text marker using Range and TextIterator.
Also added a conversion between visible position and character offset to make sure unconverted text marker
related functions are still working correctly.

Tests: accessibility/mac/previous-next-text-marker.html

accessibility/mac/text-marker-with-user-select-none.html

  • accessibility/AXObjectCache.cpp:

(WebCore::AXObjectCache::visiblePositionForTextMarkerData):
(WebCore::AXObjectCache::traverseToOffsetInRange):
(WebCore::AXObjectCache::lengthForRange):
(WebCore::AXObjectCache::rangeForNodeContents):
(WebCore::characterOffsetsInOrder):
(WebCore::AXObjectCache::rangeForUnorderedCharacterOffsets):
(WebCore::AXObjectCache::setTextMarkerDataWithCharacterOffset):
(WebCore::AXObjectCache::startOrEndTextMarkerDataForRange):
(WebCore::AXObjectCache::textMarkerDataForCharacterOffset):
(WebCore::AXObjectCache::nextNode):
(WebCore::AXObjectCache::previousNode):
(WebCore::AXObjectCache::visiblePositionFromCharacterOffset):
(WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
(WebCore::AXObjectCache::accessibilityObjectForTextMarkerData):
(WebCore::AXObjectCache::textMarkerDataForVisiblePosition):

  • accessibility/AXObjectCache.h:

(WebCore::CharacterOffset::CharacterOffset):
(WebCore::CharacterOffset::remaining):
(WebCore::CharacterOffset::isNull):
(WebCore::AXObjectCache::setNodeInUse):
(WebCore::AXObjectCache::removeNodeForUse):
(WebCore::AXObjectCache::isNodeInUse):

  • accessibility/AccessibilityObject.cpp:

(WebCore::AccessibilityObject::selectionRange):
(WebCore::AccessibilityObject::elementRange):
(WebCore::AccessibilityObject::selectText):
(WebCore::AccessibilityObject::lineRangeForPosition):
(WebCore::AccessibilityObject::replacedNodeNeedsCharacter):
(WebCore::renderListItemContainerForNode):
(WebCore::listMarkerTextForNode):
(WebCore::AccessibilityObject::listMarkerTextForNodeAndPosition):
(WebCore::AccessibilityObject::stringForRange):
(WebCore::AccessibilityObject::stringForVisiblePositionRange):
(WebCore::replacedNodeNeedsCharacter): Deleted.

  • accessibility/AccessibilityObject.h:

(WebCore::AccessibilityObject::visiblePositionRange):
(WebCore::AccessibilityObject::visiblePositionRangeForLine):
(WebCore::AccessibilityObject::boundsForVisiblePositionRange):
(WebCore::AccessibilityObject::setSelectedVisiblePositionRange):

  • accessibility/mac/WebAccessibilityObjectWrapperMac.mm:

(isTextMarkerIgnored):
(-[WebAccessibilityObjectWrapper accessibilityObjectForTextMarker:]):
(accessibilityObjectForTextMarker):
(-[WebAccessibilityObjectWrapper textMarkerRangeFromRange:]):
(textMarkerRangeFromRange):
(-[WebAccessibilityObjectWrapper startOrEndTextMarkerForRange:isStart:]):
(startOrEndTextmarkerForRange):
(-[WebAccessibilityObjectWrapper nextTextMarkerForNode:offset:]):
(-[WebAccessibilityObjectWrapper previousTextMarkerForNode:offset:]):
(-[WebAccessibilityObjectWrapper textMarkerForNode:offset:]):
(textMarkerForCharacterOffset):
(-[WebAccessibilityObjectWrapper rangeForTextMarkerRange:]):
(-[WebAccessibilityObjectWrapper characterOffsetForTextMarker:]):
(textMarkerForVisiblePosition):
(-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):

Tools:

  • WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:

(WTR::AccessibilityUIElement::accessibilityElementForTextMarker):

LayoutTests:

  • accessibility/mac/previous-next-text-marker-expected.txt: Added.
  • accessibility/mac/previous-next-text-marker.html: Added.
  • accessibility/mac/text-marker-with-user-select-none-expected.txt: Added.
  • accessibility/mac/text-marker-with-user-select-none.html: Added.
Location:
trunk
Files:
4 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r195185 r195240  
     12016-01-18  Nan Wang  <n_wang@apple.com>
     2
     3        AX: [Mac] Implement next/previous text marker functions using TextIterator
     4        https://bugs.webkit.org/show_bug.cgi?id=152728
     5
     6        Reviewed by Chris Fleizach.
     7
     8        * accessibility/mac/previous-next-text-marker-expected.txt: Added.
     9        * accessibility/mac/previous-next-text-marker.html: Added.
     10        * accessibility/mac/text-marker-with-user-select-none-expected.txt: Added.
     11        * accessibility/mac/text-marker-with-user-select-none.html: Added.
     12
    1132016-01-17  Simon Fraser  <simon.fraser@apple.com>
    214
  • trunk/Source/WebCore/ChangeLog

    r195239 r195240  
     12016-01-18  Nan Wang  <n_wang@apple.com>
     2
     3        AX: [Mac] Implement next/previous text marker functions using TextIterator
     4        https://bugs.webkit.org/show_bug.cgi?id=152728
     5
     6        Reviewed by Chris Fleizach.
     7
     8        The existing AXTextMarker based calls are implemented using visible position, and that introduced
     9        some bugs which make VoiceOver working incorrectly on Mac sometimes. Since TextIterator uses rendering
     10        position, we tried to use it to refactor those AXTextMarker based calls.
     11        In this patch, I implemented functions to navigate to previous/next text marker using Range and TextIterator.
     12        Also added a conversion between visible position and character offset to make sure unconverted text marker
     13        related functions are still working correctly.
     14
     15        Tests: accessibility/mac/previous-next-text-marker.html
     16               accessibility/mac/text-marker-with-user-select-none.html
     17
     18        * accessibility/AXObjectCache.cpp:
     19        (WebCore::AXObjectCache::visiblePositionForTextMarkerData):
     20        (WebCore::AXObjectCache::traverseToOffsetInRange):
     21        (WebCore::AXObjectCache::lengthForRange):
     22        (WebCore::AXObjectCache::rangeForNodeContents):
     23        (WebCore::characterOffsetsInOrder):
     24        (WebCore::AXObjectCache::rangeForUnorderedCharacterOffsets):
     25        (WebCore::AXObjectCache::setTextMarkerDataWithCharacterOffset):
     26        (WebCore::AXObjectCache::startOrEndTextMarkerDataForRange):
     27        (WebCore::AXObjectCache::textMarkerDataForCharacterOffset):
     28        (WebCore::AXObjectCache::nextNode):
     29        (WebCore::AXObjectCache::previousNode):
     30        (WebCore::AXObjectCache::visiblePositionFromCharacterOffset):
     31        (WebCore::AXObjectCache::characterOffsetFromVisiblePosition):
     32        (WebCore::AXObjectCache::accessibilityObjectForTextMarkerData):
     33        (WebCore::AXObjectCache::textMarkerDataForVisiblePosition):
     34        * accessibility/AXObjectCache.h:
     35        (WebCore::CharacterOffset::CharacterOffset):
     36        (WebCore::CharacterOffset::remaining):
     37        (WebCore::CharacterOffset::isNull):
     38        (WebCore::AXObjectCache::setNodeInUse):
     39        (WebCore::AXObjectCache::removeNodeForUse):
     40        (WebCore::AXObjectCache::isNodeInUse):
     41        * accessibility/AccessibilityObject.cpp:
     42        (WebCore::AccessibilityObject::selectionRange):
     43        (WebCore::AccessibilityObject::elementRange):
     44        (WebCore::AccessibilityObject::selectText):
     45        (WebCore::AccessibilityObject::lineRangeForPosition):
     46        (WebCore::AccessibilityObject::replacedNodeNeedsCharacter):
     47        (WebCore::renderListItemContainerForNode):
     48        (WebCore::listMarkerTextForNode):
     49        (WebCore::AccessibilityObject::listMarkerTextForNodeAndPosition):
     50        (WebCore::AccessibilityObject::stringForRange):
     51        (WebCore::AccessibilityObject::stringForVisiblePositionRange):
     52        (WebCore::replacedNodeNeedsCharacter): Deleted.
     53        * accessibility/AccessibilityObject.h:
     54        (WebCore::AccessibilityObject::visiblePositionRange):
     55        (WebCore::AccessibilityObject::visiblePositionRangeForLine):
     56        (WebCore::AccessibilityObject::boundsForVisiblePositionRange):
     57        (WebCore::AccessibilityObject::setSelectedVisiblePositionRange):
     58        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
     59        (isTextMarkerIgnored):
     60        (-[WebAccessibilityObjectWrapper accessibilityObjectForTextMarker:]):
     61        (accessibilityObjectForTextMarker):
     62        (-[WebAccessibilityObjectWrapper textMarkerRangeFromRange:]):
     63        (textMarkerRangeFromRange):
     64        (-[WebAccessibilityObjectWrapper startOrEndTextMarkerForRange:isStart:]):
     65        (startOrEndTextmarkerForRange):
     66        (-[WebAccessibilityObjectWrapper nextTextMarkerForNode:offset:]):
     67        (-[WebAccessibilityObjectWrapper previousTextMarkerForNode:offset:]):
     68        (-[WebAccessibilityObjectWrapper textMarkerForNode:offset:]):
     69        (textMarkerForCharacterOffset):
     70        (-[WebAccessibilityObjectWrapper rangeForTextMarkerRange:]):
     71        (-[WebAccessibilityObjectWrapper characterOffsetForTextMarker:]):
     72        (textMarkerForVisiblePosition):
     73        (-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
     74
    1752016-01-18  Olivier Blin  <olivier.blin@softathome.com>
    276
  • trunk/Source/WebCore/accessibility/AXObjectCache.cpp

    r194496 r195240  
    8282#include "RenderView.h"
    8383#include "ScrollView.h"
     84#include "TextIterator.h"
    8485#include <wtf/DataLog.h>
    8586
     
    14191420}
    14201421
     1422CharacterOffset AXObjectCache::traverseToOffsetInRange(RefPtr<Range>range, int offset, bool toNodeEnd, bool stayWithinRange)
     1423{
     1424    if (!range)
     1425        return CharacterOffset();
     1426   
     1427    int offsetInCharacter = 0;
     1428    int offsetSoFar = 0;
     1429    int remaining = 0;
     1430    int lastLength = 0;
     1431    Node* currentNode = nullptr;
     1432    bool finished = false;
     1433    int lastStartOffset = 0;
     1434   
     1435    TextIterator iterator(range.get());
     1436   
     1437    // When the range has zero length, there might be replaced node or brTag that we need to increment the characterOffset.
     1438    if (iterator.atEnd()) {
     1439        currentNode = &range->startContainer();
     1440        lastStartOffset = range->startOffset();
     1441        if (offset > 0 || toNodeEnd) {
     1442            if (AccessibilityObject::replacedNodeNeedsCharacter(currentNode) || (currentNode->renderer() && currentNode->renderer()->isBR()))
     1443                offsetSoFar++;
     1444            lastLength = offsetSoFar;
     1445           
     1446            // When going backwards, stayWithinRange is false.
     1447            // Here when we don't have any character to move and we are going backwards, we traverse to the previous node.
     1448            if (!lastLength && toNodeEnd && !stayWithinRange) {
     1449                if (Node* preNode = previousNode(currentNode))
     1450                    return traverseToOffsetInRange(rangeForNodeContents(preNode), offset, toNodeEnd);
     1451                return CharacterOffset();
     1452            }
     1453        }
     1454    }
     1455   
     1456    for (; !iterator.atEnd(); iterator.advance()) {
     1457        int currentLength = iterator.text().length();
     1458       
     1459        Node& node = iterator.range()->startContainer();
     1460        currentNode = &node;
     1461        // When currentLength == 0, we check if there's any replaced node.
     1462        // If not, we skip the node with no length.
     1463        if (!currentLength) {
     1464            int subOffset = iterator.range()->startOffset();
     1465            Node* childNode = node.traverseToChildAt(subOffset);
     1466            if (AccessibilityObject::replacedNodeNeedsCharacter(childNode)) {
     1467                offsetSoFar++;
     1468                currentLength++;
     1469                currentNode = childNode;
     1470            } else
     1471                continue;
     1472        } else {
     1473            // Ignore space, new line, tag node.
     1474            if (currentLength == 1 && isSpaceOrNewline(iterator.text()[0]))
     1475                continue;
     1476            offsetSoFar += currentLength;
     1477        }
     1478
     1479        lastLength = currentLength;
     1480        lastStartOffset = iterator.range()->startOffset();
     1481       
     1482        // Break early if we have advanced enough characters.
     1483        if (!toNodeEnd && offsetSoFar >= offset) {
     1484            offsetInCharacter = offset - (offsetSoFar - currentLength);
     1485            finished = true;
     1486            break;
     1487        }
     1488    }
     1489   
     1490    if (!finished) {
     1491        offsetInCharacter = lastLength;
     1492        if (!toNodeEnd)
     1493            remaining = offset - offsetSoFar;
     1494    }
     1495   
     1496    return CharacterOffset(currentNode, lastStartOffset, offsetInCharacter, remaining);
     1497}
     1498
     1499int AXObjectCache::lengthForRange(Range* range)
     1500{
     1501    if (!range)
     1502        return -1;
     1503   
     1504    int length = 0;
     1505    for (TextIterator it(range); !it.atEnd(); it.advance()) {
     1506        // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
     1507        if (it.text().length())
     1508            length += it.text().length();
     1509        else {
     1510            // locate the node and starting offset for this replaced range
     1511            Node& node = it.range()->startContainer();
     1512            int offset = it.range()->startOffset();
     1513            if (AccessibilityObject::replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
     1514                ++length;
     1515        }
     1516    }
     1517       
     1518    return length;
     1519}
     1520
     1521RefPtr<Range> AXObjectCache::rangeForNodeContents(Node* node)
     1522{
     1523    if (!node)
     1524        return nullptr;
     1525   
     1526    Document* document = &node->document();
     1527    if (!document)
     1528        return nullptr;
     1529    RefPtr<Range> range = Range::create(*document);
     1530    ExceptionCode ec = 0;
     1531    range->selectNodeContents(node, ec);
     1532    return ec ? nullptr : range;
     1533}
     1534
     1535static bool characterOffsetsInOrder(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
     1536{
     1537    if (characterOffset1.isNull() || characterOffset2.isNull())
     1538        return false;
     1539   
     1540    if (characterOffset1.node == characterOffset2.node)
     1541        return characterOffset1.offset <= characterOffset2.offset;
     1542   
     1543    RefPtr<Range> range1 = AXObjectCache::rangeForNodeContents(characterOffset1.node);
     1544    RefPtr<Range> range2 = AXObjectCache::rangeForNodeContents(characterOffset2.node);
     1545    return range1->compareBoundaryPoints(Range::START_TO_START, range2.get(), IGNORE_EXCEPTION) <= 0;
     1546}
     1547
     1548RefPtr<Range> AXObjectCache::rangeForUnorderedCharacterOffsets(const CharacterOffset& characterOffset1, const CharacterOffset& characterOffset2)
     1549{
     1550    if (characterOffset1.isNull() || characterOffset2.isNull())
     1551        return nullptr;
     1552   
     1553    bool alreadyInOrder = characterOffsetsInOrder(characterOffset1, characterOffset2);
     1554    CharacterOffset startCharacterOffset = alreadyInOrder ? characterOffset1 : characterOffset2;
     1555    CharacterOffset endCharacterOffset = alreadyInOrder ? characterOffset2 : characterOffset1;
     1556   
     1557    int endOffset = endCharacterOffset.offset;
     1558   
     1559    // endOffset can be out of bounds sometimes if the node is a replaced node or has brTag.
     1560    if (startCharacterOffset.node == endCharacterOffset.node) {
     1561        RefPtr<Range> nodeRange = AXObjectCache::rangeForNodeContents(startCharacterOffset.node);
     1562        int nodeLength = TextIterator::rangeLength(nodeRange.get());
     1563        if (endOffset > nodeLength)
     1564            endOffset = nodeLength;
     1565    }
     1566   
     1567    int startOffset = startCharacterOffset.startIndex + startCharacterOffset.offset;
     1568    endOffset = endCharacterOffset.startIndex + endOffset;
     1569   
     1570    // If start node is a replaced node and it has children, we want to include the replaced node itself in the range.
     1571    Node* startNode = startCharacterOffset.node;
     1572    if (AccessibilityObject::replacedNodeNeedsCharacter(startNode) && (startNode->hasChildNodes() || startNode != endCharacterOffset.node)) {
     1573        startOffset = startNode->computeNodeIndex();
     1574        startNode = startNode->parentNode();
     1575    }
     1576   
     1577    RefPtr<Range> result = Range::create(m_document);
     1578    ExceptionCode ecStart = 0, ecEnd = 0;
     1579    result->setStart(startNode, startOffset, ecStart);
     1580    result->setEnd(endCharacterOffset.node, endOffset, ecEnd);
     1581    if (ecStart || ecEnd)
     1582        return nullptr;
     1583   
     1584    return result;
     1585}
     1586
     1587void AXObjectCache::setTextMarkerDataWithCharacterOffset(TextMarkerData& textMarkerData, const CharacterOffset& characterOffset)
     1588{
     1589    if (characterOffset.isNull())
     1590        return;
     1591   
     1592    Node* domNode = characterOffset.node;
     1593    if (is<HTMLInputElement>(*domNode) && downcast<HTMLInputElement>(*domNode).isPasswordField()) {
     1594        textMarkerData.ignored = true;
     1595        return;
     1596    }
     1597   
     1598    RefPtr<AccessibilityObject> obj = this->getOrCreate(domNode);
     1599   
     1600    // Convert to visible position.
     1601    VisiblePosition visiblePosition = visiblePositionFromCharacterOffset(obj.get(), characterOffset);
     1602    int vpOffset = 0;
     1603    if (!visiblePosition.isNull()) {
     1604        Position deepPos = visiblePosition.deepEquivalent();
     1605        vpOffset = deepPos.deprecatedEditingOffset();
     1606    }
     1607   
     1608    textMarkerData.axID = obj.get()->axObjectID();
     1609    textMarkerData.node = domNode;
     1610    textMarkerData.characterOffset = characterOffset.offset;
     1611    textMarkerData.characterStartIndex = characterOffset.startIndex;
     1612    textMarkerData.offset = vpOffset;
     1613    textMarkerData.affinity = visiblePosition.affinity();
     1614   
     1615    this->setNodeInUse(domNode);
     1616}
     1617
     1618void AXObjectCache::startOrEndTextMarkerDataForRange(TextMarkerData& textMarkerData, RefPtr<Range> range, bool isStart)
     1619{
     1620    memset(&textMarkerData, 0, sizeof(TextMarkerData));
     1621   
     1622    if (!range)
     1623        return;
     1624   
     1625    // If it's end text marker, we want to go to the end of the range, and stay within the range.
     1626    bool stayWithinRange = !isStart;
     1627   
     1628    // Change the start of the range, so the character offset starts from node beginning.
     1629    int offset = 0;
     1630    Node* node = &range->startContainer();
     1631    if (node->offsetInCharacters()) {
     1632        CharacterOffset nodeStartOffset = traverseToOffsetInRange(rangeForNodeContents(node), 0, false);
     1633        offset = std::max(range->startOffset() - nodeStartOffset.startIndex, 0);
     1634        range->setStart(node, nodeStartOffset.startIndex);
     1635    }
     1636   
     1637    CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, !isStart, stayWithinRange);
     1638    setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
     1639}
     1640
     1641void AXObjectCache::textMarkerDataForCharacterOffset(TextMarkerData& textMarkerData, Node& node, int offset, bool toNodeEnd)
     1642{
     1643    memset(&textMarkerData, 0, sizeof(TextMarkerData));
     1644   
     1645    Node* domNode = &node;
     1646    if (!domNode)
     1647        return;
     1648   
     1649    // If offset <= 0, means we want to go to the previous node.
     1650    if (offset <= 0 && !toNodeEnd) {
     1651        // Set the offset to the amount of characters we need to go backwards.
     1652        offset = - offset + 1;
     1653        while (offset > 0 && textMarkerData.characterOffset <= offset) {
     1654            offset -= textMarkerData.characterOffset;
     1655            domNode = previousNode(domNode);
     1656            if (domNode) {
     1657                textMarkerDataForCharacterOffset(textMarkerData, *domNode, 0, true);
     1658                offset--;
     1659            } else
     1660                return;
     1661        }
     1662        if (offset > 0)
     1663            textMarkerDataForCharacterOffset(textMarkerData, *domNode, offset, false);
     1664        return;
     1665    }
     1666   
     1667    RefPtr<Range> range = rangeForNodeContents(domNode);
     1668
     1669    // Traverse the offset amount of characters forward and see if there's remaining offsets.
     1670    // Keep traversing to the next node when there's remaining offsets.
     1671    CharacterOffset characterOffset = traverseToOffsetInRange(range, offset, toNodeEnd);
     1672    while (!characterOffset.isNull() && characterOffset.remaining() && !toNodeEnd) {
     1673        domNode = nextNode(domNode);
     1674        if (!domNode)
     1675            return;
     1676        range = rangeForNodeContents(domNode);
     1677        characterOffset = traverseToOffsetInRange(range, characterOffset.remaining(), toNodeEnd);
     1678    }
     1679   
     1680    setTextMarkerDataWithCharacterOffset(textMarkerData, characterOffset);
     1681}
     1682
     1683Node* AXObjectCache::nextNode(Node* node) const
     1684{
     1685    if (!node)
     1686        return nullptr;
     1687   
     1688    return NodeTraversal::nextSkippingChildren(*node);
     1689}
     1690
     1691Node* AXObjectCache::previousNode(Node* node) const
     1692{
     1693    if (!node)
     1694        return nullptr;
     1695   
     1696    // First child of body shouldn't have previous node.
     1697    if (node->parentNode() && node->parentNode()->renderer() && node->parentNode()->renderer()->isBody() && !node->previousSibling())
     1698        return nullptr;
     1699
     1700    return NodeTraversal::previousSkippingChildren(*node);
     1701}
     1702
     1703VisiblePosition AXObjectCache::visiblePositionFromCharacterOffset(AccessibilityObject* obj, const CharacterOffset& characterOffset)
     1704{
     1705    if (!obj)
     1706        return VisiblePosition();
     1707   
     1708    // nextVisiblePosition means advancing one character. Use this to calculate the character offset.
     1709    VisiblePositionRange vpRange = obj->visiblePositionRange();
     1710    VisiblePosition start = vpRange.start;
     1711    VisiblePosition result = start;
     1712    for (int i = 0; i < characterOffset.offset; i++)
     1713        result = obj->nextVisiblePosition(result);
     1714   
     1715    return result;
     1716}
     1717
     1718CharacterOffset AXObjectCache::characterOffsetFromVisiblePosition(AccessibilityObject* obj, const VisiblePosition& visiblePos)
     1719{
     1720    if (!obj)
     1721        return 0;
     1722   
     1723    // Use nextVisiblePosition to calculate how many characters we need to traverse to the current position.
     1724    Position deepPos = visiblePos.deepEquivalent();
     1725    VisiblePositionRange vpRange = obj->visiblePositionRange();
     1726    VisiblePosition vp = vpRange.start;
     1727    int characterOffset = 0;
     1728    Position vpDeepPos = vp.deepEquivalent();
     1729   
     1730    while (!vpDeepPos.isNull() && !deepPos.equals(vpDeepPos)) {
     1731        vp = obj->nextVisiblePosition(vp);
     1732        vpDeepPos = vp.deepEquivalent();
     1733        characterOffset++;
     1734    }
     1735   
     1736    return traverseToOffsetInRange(rangeForNodeContents(obj->node()), characterOffset, false);
     1737}
     1738
     1739AccessibilityObject* AXObjectCache::accessibilityObjectForTextMarkerData(TextMarkerData& textMarkerData)
     1740{
     1741    if (!isNodeInUse(textMarkerData.node))
     1742        return nullptr;
     1743   
     1744    Node* domNode = textMarkerData.node;
     1745    return this->getOrCreate(domNode);
     1746}
     1747
    14211748void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
    14221749{
     
    14441771    textMarkerData.node = domNode;
    14451772    textMarkerData.offset = deepPos.deprecatedEditingOffset();
    1446     textMarkerData.affinity = visiblePos.affinity();   
     1773    textMarkerData.affinity = visiblePos.affinity();
     1774   
     1775    // convert to character offset
     1776    CharacterOffset characterOffset = characterOffsetFromVisiblePosition(obj.get(), visiblePos);
     1777    textMarkerData.characterOffset = characterOffset.offset;
     1778    textMarkerData.characterStartIndex = characterOffset.startIndex;
    14471779   
    14481780    cache->setNodeInUse(domNode);
  • trunk/Source/WebCore/accessibility/AXObjectCache.h

    r191931 r195240  
    5252    Node* node;
    5353    int offset;
     54    int characterStartIndex;
     55    int characterOffset;
     56    bool ignored;
    5457    EAffinity affinity;
     58};
     59
     60struct CharacterOffset {
     61    Node* node;
     62    int startIndex;
     63    int offset;
     64    int remainingOffset;
     65   
     66    CharacterOffset(Node* n = nullptr, int startIndex = 0, int offset = 0, int remaining = 0)
     67        : node(n)
     68        , startIndex(startIndex)
     69        , offset(offset)
     70        , remainingOffset(remaining)
     71    { }
     72   
     73    int remaining() const { return remainingOffset; }
     74    bool isNull() const { return !node; }
    5575};
    5676
     
    165185    void textMarkerDataForVisiblePosition(TextMarkerData&, const VisiblePosition&);
    166186    VisiblePosition visiblePositionForTextMarkerData(TextMarkerData&);
     187    void textMarkerDataForCharacterOffset(TextMarkerData&, Node&, int, bool toNodeEnd = false);
     188    void startOrEndTextMarkerDataForRange(TextMarkerData&, RefPtr<Range>, bool);
     189    AccessibilityObject* accessibilityObjectForTextMarkerData(TextMarkerData&);
     190    RefPtr<Range> rangeForUnorderedCharacterOffsets(const CharacterOffset&, const CharacterOffset&);
     191    static RefPtr<Range> rangeForNodeContents(Node*);
     192    static int lengthForRange(Range*);
    167193
    168194    enum AXNotification {
     
    255281    void removeNodeForUse(Node* n) { m_textMarkerNodes.remove(n); }
    256282    bool isNodeInUse(Node* n) { return m_textMarkerNodes.contains(n); }
     283   
     284    Node* nextNode(Node*) const;
     285    Node* previousNode(Node*) const;
     286    CharacterOffset traverseToOffsetInRange(RefPtr<Range>, int, bool, bool stayWithinRange = false);
     287    VisiblePosition visiblePositionFromCharacterOffset(AccessibilityObject*, const CharacterOffset&);
     288    CharacterOffset characterOffsetFromVisiblePosition(AccessibilityObject*, const VisiblePosition&);
     289    void setTextMarkerDataWithCharacterOffset(TextMarkerData&, const CharacterOffset&);
    257290
    258291private:
  • trunk/Source/WebCore/accessibility/AccessibilityObject.cpp

    r194496 r195240  
    716716}
    717717
     718RefPtr<Range> AccessibilityObject::elementRange() const
     719{   
     720    return AXObjectCache::rangeForNodeContents(node());
     721}
     722
    718723String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria)
    719724{
     
    12041209}
    12051210
    1206 static bool replacedNodeNeedsCharacter(Node* replacedNode)
     1211bool AccessibilityObject::replacedNodeNeedsCharacter(Node* replacedNode)
    12071212{
    12081213    // we should always be given a rendered node and a replaced node, but be safe
     
    12291234    return nullptr;
    12301235}
    1231    
     1236
     1237static String listMarkerTextForNode(Node* node)
     1238{
     1239    RenderListItem* listItem = renderListItemContainerForNode(node);
     1240    if (!listItem)
     1241        return String();
     1242   
     1243    // If this is in a list item, we need to manually add the text for the list marker
     1244    // because a RenderListMarker does not have a Node equivalent and thus does not appear
     1245    // when iterating text.
     1246    return listItem->markerTextWithSuffix();
     1247}
     1248
    12321249// Returns the text associated with a list marker if this node is contained within a list item.
    12331250String AccessibilityObject::listMarkerTextForNodeAndPosition(Node* node, const VisiblePosition& visiblePositionStart) const
     
    12371254        return String();
    12381255
    1239     RenderListItem* listItem = renderListItemContainerForNode(node);
    1240     if (!listItem)
     1256    return listMarkerTextForNode(node);
     1257}
     1258
     1259String AccessibilityObject::stringForRange(RefPtr<Range> range) const
     1260{
     1261    if (!range)
    12411262        return String();
    1242        
    1243     // If this is in a list item, we need to manually add the text for the list marker
    1244     // because a RenderListMarker does not have a Node equivalent and thus does not appear
    1245     // when iterating text.
    1246     return listItem->markerTextWithSuffix();
     1263   
     1264    TextIterator it(range.get());
     1265    if (it.atEnd())
     1266        return String();
     1267   
     1268    StringBuilder builder;
     1269    for (; !it.atEnd(); it.advance()) {
     1270        // non-zero length means textual node, zero length means replaced node (AKA "attachments" in AX)
     1271        if (it.text().length()) {
     1272            // Add a textual representation for list marker text.
     1273            builder.append(listMarkerTextForNode(it.node()));
     1274            it.appendTextToStringBuilder(builder);
     1275        } else {
     1276            // locate the node and starting offset for this replaced range
     1277            Node& node = it.range()->startContainer();
     1278            ASSERT(&node == &it.range()->endContainer());
     1279            int offset = it.range()->startOffset();
     1280            if (replacedNodeNeedsCharacter(node.traverseToChildAt(offset)))
     1281                builder.append(objectReplacementCharacter);
     1282        }
     1283    }
     1284   
     1285    return builder.toString();
    12471286}
    12481287
  • trunk/Source/WebCore/accessibility/AccessibilityObject.h

    r194496 r195240  
    818818    virtual VisiblePositionRange visiblePositionRangeForLine(unsigned) const { return VisiblePositionRange(); }
    819819   
     820    RefPtr<Range> elementRange() const;
     821    static bool replacedNodeNeedsCharacter(Node* replacedNode);
     822   
    820823    VisiblePositionRange visiblePositionRangeForUnorderedPositions(const VisiblePosition&, const VisiblePosition&) const;
    821824    VisiblePositionRange positionOfLeftWord(const VisiblePosition&) const;
     
    830833
    831834    String stringForVisiblePositionRange(const VisiblePositionRange&) const;
     835    String stringForRange(RefPtr<Range>) const;
    832836    virtual IntRect boundsForVisiblePositionRange(const VisiblePositionRange&) const { return IntRect(); }
    833837    int lengthForVisiblePositionRange(const VisiblePositionRange&) const;
  • trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm

    r194318 r195240  
    782782
    783783#pragma mark Text Marker helpers
     784
     785static bool isTextMarkerIgnored(id textMarker)
     786{
     787    if (!textMarker)
     788        return false;
     789   
     790    TextMarkerData textMarkerData;
     791    if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
     792        return false;
     793   
     794    return textMarkerData.ignored;
     795}
     796
     797- (AccessibilityObject*)accessibilityObjectForTextMarker:(id)textMarker
     798{
     799    return accessibilityObjectForTextMarker(m_object->axObjectCache(), textMarker);
     800}
     801
     802static AccessibilityObject* accessibilityObjectForTextMarker(AXObjectCache* cache, id textMarker)
     803{
     804    if (!textMarker || !cache || isTextMarkerIgnored(textMarker))
     805        return nullptr;
     806   
     807    TextMarkerData textMarkerData;
     808    if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
     809        return nullptr;
     810    return cache->accessibilityObjectForTextMarkerData(textMarkerData);
     811}
     812
     813- (id)textMarkerRangeFromRange:(const RefPtr<Range>)range
     814{
     815    return textMarkerRangeFromRange(m_object->axObjectCache(), range);
     816}
     817
     818static id textMarkerRangeFromRange(AXObjectCache *cache, const RefPtr<Range> range)
     819{
     820    id startTextMarker = startOrEndTextmarkerForRange(cache, range, true);
     821    id endTextMarker = startOrEndTextmarkerForRange(cache, range, false);
     822    return textMarkerRangeFromMarkers(startTextMarker, endTextMarker);
     823}
     824
     825- (id)startOrEndTextMarkerForRange:(const RefPtr<Range>)range isStart:(BOOL)isStart
     826{
     827    return startOrEndTextmarkerForRange(m_object->axObjectCache(), range, isStart);
     828}
     829
     830static id startOrEndTextmarkerForRange(AXObjectCache* cache, RefPtr<Range> range, bool isStart)
     831{
     832    if (!cache)
     833        return nil;
     834   
     835    TextMarkerData textMarkerData;
     836    cache->startOrEndTextMarkerDataForRange(textMarkerData, range, isStart);
     837    if (!textMarkerData.axID)
     838        return nil;
     839   
     840    return CFBridgingRelease(wkCreateAXTextMarker(&textMarkerData, sizeof(textMarkerData)));
     841}
     842
     843- (id)nextTextMarkerForNode:(Node&)node offset:(int)offset
     844{
     845    int nextOffset = offset + 1;
     846    id textMarker = [self textMarkerForNode:node offset:nextOffset];
     847    if (isTextMarkerIgnored(textMarker))
     848        textMarker = [self nextTextMarkerForNode:node offset:nextOffset];
     849    return textMarker;
     850}
     851
     852- (id)previousTextMarkerForNode:(Node&)node offset:(int)offset
     853{
     854    int previousOffset = offset - 1;
     855    id textMarker = [self textMarkerForNode:node offset:previousOffset];
     856    if (isTextMarkerIgnored(textMarker))
     857        textMarker = [self previousTextMarkerForNode:node offset:previousOffset];
     858    return textMarker;
     859}
     860
     861- (id)textMarkerForNode:(Node&)node offset:(int)offset
     862{
     863    return textMarkerForCharacterOffset(m_object->axObjectCache(), node, offset);
     864}
     865
     866static id textMarkerForCharacterOffset(AXObjectCache* cache, Node& node, int offset, bool toNodeEnd = false)
     867{
     868    if (!cache)
     869        return nil;
     870   
     871    Node* domNode = &node;
     872    if (!domNode)
     873        return nil;
     874   
     875    TextMarkerData textMarkerData;
     876    cache->textMarkerDataForCharacterOffset(textMarkerData, node, offset, toNodeEnd);
     877    if (!textMarkerData.axID && !textMarkerData.ignored)
     878        return nil;
     879   
     880    return CFBridgingRelease(wkCreateAXTextMarker(&textMarkerData, sizeof(textMarkerData)));
     881}
     882
     883- (RefPtr<Range>)rangeForTextMarkerRange:(id)textMarkerRange
     884{
     885    if (!textMarkerRange)
     886        return nullptr;
     887   
     888    id startTextMarker = AXTextMarkerRangeStart(textMarkerRange);
     889    id endTextMarker = AXTextMarkerRangeEnd(textMarkerRange);
     890   
     891    if (!startTextMarker || !endTextMarker)
     892        return nullptr;
     893   
     894    AXObjectCache* cache = m_object->axObjectCache();
     895    if (!cache)
     896        return nullptr;
     897   
     898    CharacterOffset startCharacterOffset = [self characterOffsetForTextMarker:startTextMarker];
     899    CharacterOffset endCharacterOffset = [self characterOffsetForTextMarker:endTextMarker];
     900    return cache->rangeForUnorderedCharacterOffsets(startCharacterOffset, endCharacterOffset);
     901}
     902
     903- (CharacterOffset)characterOffsetForTextMarker:(id)textMarker
     904{
     905    if (!textMarker || isTextMarkerIgnored(textMarker))
     906        return CharacterOffset();
     907   
     908    TextMarkerData textMarkerData;
     909    if (!wkGetBytesFromAXTextMarker(textMarker, &textMarkerData, sizeof(textMarkerData)))
     910        return CharacterOffset();
     911   
     912    return CharacterOffset(textMarkerData.node, textMarkerData.characterStartIndex, textMarkerData.characterOffset);
     913}
    784914
    785915static id textMarkerForVisiblePosition(AXObjectCache* cache, const VisiblePosition& visiblePos)
     
    38293959   
    38303960    if ([attribute isEqualToString:@"AXUIElementForTextMarker"]) {
    3831         VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
    3832         AccessibilityObject* axObject = m_object->accessibilityObjectForPosition(visiblePos);
     3961        AccessibilityObject* axObject = [self accessibilityObjectForTextMarker:textMarker];
    38333962        if (!axObject)
    38343963            return nil;
     
    38373966   
    38383967    if ([attribute isEqualToString:@"AXTextMarkerRangeForUIElement"]) {
    3839         VisiblePositionRange vpRange = uiElement.get()->visiblePositionRange();
    3840         return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
     3968        RefPtr<Range> range = uiElement.get()->elementRange();
     3969        return [self textMarkerRangeFromRange:range];
    38413970    }
    38423971   
     
    38523981   
    38533982    if ([attribute isEqualToString:@"AXStringForTextMarkerRange"]) {
    3854         VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
    3855         return m_object->stringForVisiblePositionRange(visiblePosRange);
     3983        RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
     3984        return m_object->stringForRange(range);
    38563985    }
    38573986   
     
    38964025            return nil;
    38974026       
    3898         VisiblePosition visiblePos1 = [self visiblePositionForTextMarker:(textMarker1)];
    3899         VisiblePosition visiblePos2 = [self visiblePositionForTextMarker:(textMarker2)];
    3900         VisiblePositionRange vpRange = m_object->visiblePositionRangeForUnorderedPositions(visiblePos1, visiblePos2);
    3901         return [self textMarkerRangeFromVisiblePositions:vpRange.start endPosition:vpRange.end];
     4027        AXObjectCache* cache = m_object->axObjectCache();
     4028        if (!cache)
     4029            return nil;
     4030        CharacterOffset characterOffset1 = [self characterOffsetForTextMarker:textMarker1];
     4031        CharacterOffset characterOffset2 = [self characterOffsetForTextMarker:textMarker2];
     4032        RefPtr<Range> range = cache->rangeForUnorderedCharacterOffsets(characterOffset1, characterOffset2);
     4033        return [self textMarkerRangeFromRange:range];
    39024034    }
    39034035   
    39044036    if ([attribute isEqualToString:@"AXNextTextMarkerForTextMarker"]) {
    3905         VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
    3906         return [self textMarkerForVisiblePosition:m_object->nextVisiblePosition(visiblePos)];
     4037        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
     4038        return [self nextTextMarkerForNode:*characterOffset.node offset:characterOffset.offset];
    39074039    }
    39084040   
    39094041    if ([attribute isEqualToString:@"AXPreviousTextMarkerForTextMarker"]) {
    3910         VisiblePosition visiblePos = [self visiblePositionForTextMarker:(textMarker)];
    3911         return [self textMarkerForVisiblePosition:m_object->previousVisiblePosition(visiblePos)];
     4042        CharacterOffset characterOffset = [self characterOffsetForTextMarker:textMarker];
     4043        return [self previousTextMarkerForNode:*characterOffset.node offset:characterOffset.offset];
    39124044    }
    39134045   
     
    39954127   
    39964128    if ([attribute isEqualToString:@"AXLengthForTextMarkerRange"]) {
    3997         VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
    3998         int length = m_object->lengthForVisiblePositionRange(visiblePosRange);
     4129        RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
     4130        int length = AXObjectCache::lengthForRange(range.get());
    39994131        if (length < 0)
    40004132            return nil;
     
    40044136    // Used only by DumpRenderTree (so far).
    40054137    if ([attribute isEqualToString:@"AXStartTextMarkerForTextMarkerRange"]) {
    4006         VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
    4007         return [self textMarkerForVisiblePosition:visiblePosRange.start];
     4138        RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
     4139        return [self startOrEndTextMarkerForRange:range isStart:YES];
    40084140    }
    40094141   
    40104142    if ([attribute isEqualToString:@"AXEndTextMarkerForTextMarkerRange"]) {
    4011         VisiblePositionRange visiblePosRange = [self visiblePositionRangeForTextMarkerRange:textMarkerRange];
    4012         return [self textMarkerForVisiblePosition:visiblePosRange.end];
     4143        RefPtr<Range> range = [self rangeForTextMarkerRange:textMarkerRange];
     4144        return [self startOrEndTextMarkerForRange:range isStart:NO];
    40134145    }
    40144146
  • trunk/Tools/ChangeLog

    r195232 r195240  
     12016-01-18  Nan Wang  <n_wang@apple.com>
     2
     3        AX: [Mac] Implement next/previous text marker functions using TextIterator
     4        https://bugs.webkit.org/show_bug.cgi?id=152728
     5
     6        Reviewed by Chris Fleizach.
     7
     8        * WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
     9        (WTR::AccessibilityUIElement::accessibilityElementForTextMarker):
     10
    1112016-01-18  Csaba Osztrogonác  <ossy@webkit.org>
    212
  • trunk/Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm

    r194364 r195240  
    18021802    BEGIN_AX_OBJC_EXCEPTIONS
    18031803    id uiElement = [m_element accessibilityAttributeValue:@"AXUIElementForTextMarker" forParameter:(id)marker->platformTextMarker()];
    1804     return AccessibilityUIElement::create(uiElement);
     1804    if (uiElement)
     1805        return AccessibilityUIElement::create(uiElement);
    18051806    END_AX_OBJC_EXCEPTIONS
    18061807   
Note: See TracChangeset for help on using the changeset viewer.