| Index: Source/core/layout/svg/SVGTextQuery.cpp | 
| diff --git a/Source/core/layout/svg/SVGTextQuery.cpp b/Source/core/layout/svg/SVGTextQuery.cpp | 
| index 490237a0386c0c4defe10d7ee1c2c110497d6082..8b63773d742ea3ef6d0a9a56f102c56c207deefe 100644 | 
| --- a/Source/core/layout/svg/SVGTextQuery.cpp | 
| +++ b/Source/core/layout/svg/SVGTextQuery.cpp | 
| @@ -37,14 +37,14 @@ namespace blink { | 
| struct QueryData { | 
| QueryData() | 
| : isVerticalText(false) | 
| -        , processedCharacters(0) | 
| +        , currentOffset(0) | 
| , textLayoutObject(0) | 
| , textBox(0) | 
| { | 
| } | 
|  | 
| bool isVerticalText; | 
| -    unsigned processedCharacters; | 
| +    unsigned currentOffset; | 
| LayoutSVGInlineText* textLayoutObject; | 
| const SVGInlineTextBox* textBox; | 
| }; | 
| @@ -101,36 +101,70 @@ static void collectTextBoxesInFlowBox(InlineFlowBox* flowBox, Vector<SVGInlineTe | 
|  | 
| typedef bool ProcessTextFragmentCallback(QueryData*, const SVGTextFragment&); | 
|  | 
| -static bool executeQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback) | 
| +static bool queryTextBox(QueryData* queryData, const SVGInlineTextBox* textBox, ProcessTextFragmentCallback fragmentCallback) | 
| +{ | 
| +    queryData->textBox = textBox; | 
| +    queryData->textLayoutObject = &toLayoutSVGInlineText(textBox->layoutObject()); | 
| + | 
| +    queryData->isVerticalText = textBox->layoutObject().style()->svgStyle().isVerticalWritingMode(); | 
| + | 
| +    // Loop over all text fragments in this text box, firing a callback for each. | 
| +    for (const SVGTextFragment& fragment : textBox->textFragments()) { | 
| +        if (fragmentCallback(queryData, fragment)) | 
| +            return true; | 
| +    } | 
| +    return false; | 
| +} | 
| + | 
| +// Execute a query in "spatial" order starting at |queryRoot|. This means | 
| +// walking the lines boxes in the order they would get painted. | 
| +static void spatialQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback) | 
| { | 
| Vector<SVGInlineTextBox*> textBoxes; | 
| collectTextBoxesInFlowBox(flowBoxForLayoutObject(queryRoot), textBoxes); | 
|  | 
| -    unsigned processedCharacters = 0; | 
| -    unsigned textBoxCount = textBoxes.size(); | 
| - | 
| // Loop over all text boxes | 
| -    for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) { | 
| -        queryData->textBox = textBoxes[textBoxPosition]; | 
| -        queryData->textLayoutObject = &toLayoutSVGInlineText(queryData->textBox->layoutObject()); | 
| -        ASSERT(queryData->textLayoutObject->style()); | 
| - | 
| -        queryData->isVerticalText = queryData->textLayoutObject->style()->svgStyle().isVerticalWritingMode(); | 
| -        const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments(); | 
| - | 
| -        // Loop over all text fragments in this text box, firing a callback for each. | 
| -        unsigned fragmentCount = fragments.size(); | 
| -        for (unsigned i = 0; i < fragmentCount; ++i) { | 
| -            const SVGTextFragment& fragment = fragments.at(i); | 
| -            if (fragmentCallback(queryData, fragment)) | 
| -                return true; | 
| - | 
| -            processedCharacters += fragment.length; | 
| -        } | 
| +    for (const SVGInlineTextBox* textBox : textBoxes) { | 
| +        if (queryTextBox(queryData, textBox, fragmentCallback)) | 
| +            return; | 
| +    } | 
| +} | 
| + | 
| +static void collectTextBoxesInLogicalOrder(const LayoutSVGInlineText& textLayoutObject, Vector<SVGInlineTextBox*>& textBoxes) | 
| +{ | 
| +    textBoxes.shrink(0); | 
| +    for (InlineTextBox* textBox = textLayoutObject.firstTextBox(); textBox; textBox = textBox->nextTextBox()) | 
| +        textBoxes.append(toSVGInlineTextBox(textBox)); | 
| +    std::sort(textBoxes.begin(), textBoxes.end(), InlineTextBox::compareByStart); | 
| +} | 
| + | 
| +// Execute a query in "logical" order starting at |queryRoot|. This means | 
| +// walking the lines boxes for each layout object in layout tree (pre)order. | 
| +static void logicalQuery(LayoutObject* queryRoot, QueryData* queryData, ProcessTextFragmentCallback fragmentCallback) | 
| +{ | 
| +    if (!queryRoot) | 
| +        return; | 
| + | 
| +    // Walk the layout tree in pre-order, starting at the specified root, and | 
| +    // run the query for each text node. | 
| +    Vector<SVGInlineTextBox*> textBoxes; | 
| +    for (LayoutObject* layoutObject = queryRoot->slowFirstChild(); layoutObject; layoutObject = layoutObject->nextInPreOrder(queryRoot)) { | 
| +        if (!layoutObject->isSVGInlineText()) | 
| +            continue; | 
|  | 
| -        queryData->processedCharacters = processedCharacters; | 
| +        LayoutSVGInlineText& textLayoutObject = toLayoutSVGInlineText(*layoutObject); | 
| +        ASSERT(textLayoutObject.style()); | 
| + | 
| +        // TODO(fs): Allow filtering the search earlier, since we should be | 
| +        // able to trivially reject (prune) at least some of the queries. | 
| +        collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes); | 
| + | 
| +        for (const SVGInlineTextBox* textBox : textBoxes) { | 
| +            if (queryTextBox(queryData, textBox, fragmentCallback)) | 
| +                return; | 
| +            queryData->currentOffset += textBox->len(); | 
| +        } | 
| } | 
| -    return false; | 
| } | 
|  | 
| static void modifyStartEndPositionsRespectingLigatures(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) | 
| @@ -167,9 +201,11 @@ static void modifyStartEndPositionsRespectingLigatures(const QueryData* queryDat | 
|  | 
| static bool mapStartEndPositionsIntoFragmentCoordinates(const QueryData* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) | 
| { | 
| +    unsigned boxStart = queryData->currentOffset; | 
| + | 
| // Make <startPosition, endPosition> offsets relative to the current text box. | 
| -    startPosition -= queryData->processedCharacters; | 
| -    endPosition -= queryData->processedCharacters; | 
| +    startPosition -= boxStart; | 
| +    endPosition -= boxStart; | 
|  | 
| // Reuse the same logic used for text selection & painting, to map our | 
| // query start/length into start/endPositions of the current text fragment. | 
| @@ -191,8 +227,8 @@ static bool numberOfCharactersCallback(QueryData*, const SVGTextFragment&) | 
| unsigned SVGTextQuery::numberOfCharacters() const | 
| { | 
| QueryData data; | 
| -    executeQuery(m_queryRootLayoutObject, &data, numberOfCharactersCallback); | 
| -    return data.processedCharacters; | 
| +    logicalQuery(m_queryRootLayoutObject, &data, numberOfCharactersCallback); | 
| +    return data.currentOffset; | 
| } | 
|  | 
| // textLength() implementation | 
| @@ -215,7 +251,7 @@ static bool textLengthCallback(QueryData* queryData, const SVGTextFragment& frag | 
| float SVGTextQuery::textLength() const | 
| { | 
| TextLengthData data; | 
| -    executeQuery(m_queryRootLayoutObject, &data, textLengthCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, textLengthCallback); | 
| return data.textLength; | 
| } | 
|  | 
| @@ -251,7 +287,7 @@ static bool subStringLengthCallback(QueryData* queryData, const SVGTextFragment& | 
| float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const | 
| { | 
| SubStringLengthData data(startPosition, length); | 
| -    executeQuery(m_queryRootLayoutObject, &data, subStringLengthCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, subStringLengthCallback); | 
| return data.subStringLength; | 
| } | 
|  | 
| @@ -318,7 +354,7 @@ static bool startPositionOfCharacterCallback(QueryData* queryData, const SVGText | 
| FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const | 
| { | 
| StartPositionOfCharacterData data(position); | 
| -    executeQuery(m_queryRootLayoutObject, &data, startPositionOfCharacterCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, startPositionOfCharacterCallback); | 
| return data.startPosition; | 
| } | 
|  | 
| @@ -352,7 +388,7 @@ static bool endPositionOfCharacterCallback(QueryData* queryData, const SVGTextFr | 
| FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const | 
| { | 
| EndPositionOfCharacterData data(position); | 
| -    executeQuery(m_queryRootLayoutObject, &data, endPositionOfCharacterCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, endPositionOfCharacterCallback); | 
| return data.endPosition; | 
| } | 
|  | 
| @@ -392,7 +428,7 @@ static bool rotationOfCharacterCallback(QueryData* queryData, const SVGTextFragm | 
| float SVGTextQuery::rotationOfCharacter(unsigned position) const | 
| { | 
| RotationOfCharacterData data(position); | 
| -    executeQuery(m_queryRootLayoutObject, &data, rotationOfCharacterCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, rotationOfCharacterCallback); | 
| return data.rotation; | 
| } | 
|  | 
| @@ -484,7 +520,7 @@ static bool extentOfCharacterCallback(QueryData* queryData, const SVGTextFragmen | 
| FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const | 
| { | 
| ExtentOfCharacterData data(position); | 
| -    executeQuery(m_queryRootLayoutObject, &data, extentOfCharacterCallback); | 
| +    logicalQuery(m_queryRootLayoutObject, &data, extentOfCharacterCallback); | 
| return data.extent; | 
| } | 
|  | 
| @@ -492,12 +528,55 @@ FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const | 
| struct CharacterNumberAtPositionData : QueryData { | 
| CharacterNumberAtPositionData(const FloatPoint& queryPosition) | 
| : position(queryPosition) | 
| +        , hitLayoutObject(nullptr) | 
| +        , offsetInTextNode(0) | 
| { | 
| } | 
|  | 
| +    int characterNumberWithin(const LayoutObject* queryRoot) const; | 
| + | 
| FloatPoint position; | 
| +    LayoutObject* hitLayoutObject; | 
| +    int offsetInTextNode; | 
| }; | 
|  | 
| +int CharacterNumberAtPositionData::characterNumberWithin(const LayoutObject* queryRoot) const | 
| +{ | 
| +    // http://www.w3.org/TR/SVG/single-page.html#text-__svg__SVGTextContentElement__getCharNumAtPosition | 
| +    // "If no such character exists, a value of -1 is returned." | 
| +    if (!hitLayoutObject) | 
| +        return -1; | 
| +    ASSERT(queryRoot); | 
| +    int characterNumber = offsetInTextNode; | 
| + | 
| +    // Accumulate the lengths of all the text nodes preceding the target layout | 
| +    // object within the queried root, to get the complete character number. | 
| +    for (const LayoutObject* layoutObject = hitLayoutObject->previousInPreOrder(queryRoot); | 
| +        layoutObject; layoutObject = layoutObject->previousInPreOrder(queryRoot)) { | 
| +        if (!layoutObject->isSVGInlineText()) | 
| +            continue; | 
| +        characterNumber += toLayoutSVGInlineText(layoutObject)->renderedTextLength(); | 
| +    } | 
| +    return characterNumber; | 
| +} | 
| + | 
| +static unsigned logicalOffsetInTextNode(const LayoutSVGInlineText& textLayoutObject, const SVGInlineTextBox* startTextBox, unsigned fragmentOffset) | 
| +{ | 
| +    Vector<SVGInlineTextBox*> textBoxes; | 
| +    collectTextBoxesInLogicalOrder(textLayoutObject, textBoxes); | 
| + | 
| +    ASSERT(startTextBox); | 
| +    size_t index = textBoxes.find(startTextBox); | 
| +    ASSERT(index != kNotFound); | 
| + | 
| +    unsigned offset = fragmentOffset; | 
| +    while (index) { | 
| +        --index; | 
| +        offset += textBoxes[index]->len(); | 
| +    } | 
| +    return offset; | 
| +} | 
| + | 
| static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTextFragment& fragment) | 
| { | 
| CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData); | 
| @@ -516,10 +595,10 @@ static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTex | 
| while (fragmentOffset < fragment.length) { | 
| calculateGlyphBoundaries(queryData, fragment, fragmentOffset, extent); | 
| if (extent.contains(data->position)) { | 
| -            // Compute the character offset of the glyph within the text box | 
| -            // and add to processedCharacters. | 
| -            unsigned characterOffset = fragment.characterOffset + fragmentOffset; | 
| -            data->processedCharacters += characterOffset - data->textBox->start(); | 
| +            // Compute the character offset of the glyph within the text node. | 
| +            unsigned offsetInBox = fragment.characterOffset - queryData->textBox->start() + fragmentOffset; | 
| +            data->offsetInTextNode = logicalOffsetInTextNode(*queryData->textLayoutObject, queryData->textBox, offsetInBox); | 
| +            data->hitLayoutObject = data->textLayoutObject; | 
| return true; | 
| } | 
| fragmentOffset += textMetrics[textMetricsOffset].length(); | 
| @@ -531,10 +610,8 @@ static bool characterNumberAtPositionCallback(QueryData* queryData, const SVGTex | 
| int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const | 
| { | 
| CharacterNumberAtPositionData data(position); | 
| -    if (!executeQuery(m_queryRootLayoutObject, &data, characterNumberAtPositionCallback)) | 
| -        return -1; | 
| - | 
| -    return data.processedCharacters; | 
| +    spatialQuery(m_queryRootLayoutObject, &data, characterNumberAtPositionCallback); | 
| +    return data.characterNumberWithin(m_queryRootLayoutObject); | 
| } | 
|  | 
| } | 
|  |