| Differences between
and this patch
- Source/WebCore/ChangeLog +75 lines
Lines 1-3 Source/WebCore/ChangeLog_sec1
1
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
2
3
        REGRESSION(r74971): Selection doesn't work correctly in BiDi Text
4
        https://bugs.webkit.org/show_bug.cgi?id=57340
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        This patch adds the end point adjustment mechanism at bidi boundaries similar to the one NSTextView implements.
9
10
        To understand the problem, suppose we have strong RTL letters "ABC" in a LTR block (visually laid out as CBA).
11
12
        Per NSTextView convention, logical offsets between each letter is placed as (0)C(2)B(1)A(3). In other words,
13
        placing the caret visually on the left of CBA yields the position inside the text node of "ABC" at offset 0.
14
        Likewise, placing it between C and B yields ("ABC", 2), and placing it on the right of CBA yields ("ABC", 3).
15
16
        Now suppose a user attempts to select the letter A by a mouse drag from the right of CBA to a point between
17
        B and A. First, the initial mouse down places the selection's base at ("ABC", 3). Then as the mouse pointer
18
        moves to a point on the left of A, the selection's extent is set at ("ABC", 1), selecting "BC".
19
20
        To mitigate this issue, NSTextView adjusts selection base and extent under certain conditions. In the above
21
        example, NSTextView detects user's intent and changes the selection's base to ("ABC", 0) temporarily.
22
23
        This patch implements a similar trick on WebKit. We adjust the base or the extent when they're at the left
24
        end or at the right end of a bidi run and the other end is inside of the run. In the above example, the
25
        base position on the right of A is the right end of a bidi run and the extent position between B and A is
26
        inside the same run (CBA), so we would adjust the base to be ("ABC", 0) as NSTextView does.
27
28
        Take another example abcABC. Note offsets are assigned as (0)a(1)b(2)c(3)C(5)B(4)A(6) When the user starts
29
        a mouse drag from the right of A to a point between B and A, we adjust the selection base to be ("abcABC", 3)
30
        because the base is at the right end of a bidi run and the extent is in the same run. We keep the adjustment
31
        when the mouse pointer moves to a point between C and B. However, when the mouser pointer reaches a point
32
        between letters b and c, the selection extent is placed at ("abcABC", 2). Because the extent is outside of
33
        the bidi run started from the selection base, we restore the original base at this point. Had we not done this,
34
        we'll end up selecting just "c".
35
36
        While this algorithm is implemented in FrameSelection::setNonDirectionalSelectionIfNeeded, this patch adds
37
        various member functions to RenderedPosition to facilitate abstraction around inline boxes and bidi runs.
38
39
        Test: editing/selection/select-bidi-run.html
40
41
        * editing/FrameSelection.cpp:
42
        (WebCore::adjustEndpointsAtBidiBoundary): Added. Implements the endpoints adjustment algorithm.
43
        (WebCore::FrameSelection::setNonDirectionalSelectionIfNeeded): Calls adjustEndpointsAtBidiBoundary, and
44
        restores the original base as needed.
45
        * editing/FrameSelection.h:
46
        * editing/RenderedPosition.cpp:
47
        (WebCore::RenderedPosition::RenderedPosition):
48
        (WebCore::RenderedPosition::prevLeafChild): Added to cache prevLeafChild of the current inline box.
49
        (WebCore::RenderedPosition::nextLeafChild): Ditto for nextLeafChild.
50
        (WebCore::RenderedPosition::operator==): Compares two RenderedPositions considering neighboring inline boxes
51
        so that the rightmost position in a box and the leftmost position in the following box is considered equal.
52
        (WebCore::RenderedPosition::bidiLevelOnLeft): Added. Returns the bidi level of the run on the left. We can't
53
        add a generic bidiLevel to this class because it'll be ambiguous at bidi boundaries.
54
        (WebCore::RenderedPosition::bidiLevelOnRight): Ditto for the run on the right.
55
        (WebCore::RenderedPosition::leftBoundaryOfBidiRun): Added.
56
        (WebCore::RenderedPosition::rightBoundaryOfBidiRun): Added.
57
        (WebCore::RenderedPosition::atLeftBoundaryOfBidiRun): Added.
58
        (WebCore::RenderedPosition::atRightBoundaryOfBidiRun): Added.
59
        (WebCore::RenderedPosition::positionAtLeftBoundaryOfBiDiRun): Returns Position at the left edge of a bidi run
60
        if RenderedPosition is at such a position. Asserts atLeftBoundaryOfBidiRun.
61
        (WebCore::RenderedPosition::positionAtRightBoundaryOfBiDiRun): Ditto for the right edge.
62
        * editing/RenderedPosition.h:
63
        (WebCore::RenderedPosition::atLeftBoundaryOfBidiRun): Added.
64
        (WebCore::RenderedPosition::atRightBoundaryOfBidiRun): Added.
65
        (WebCore::RenderedPosition::atLeftmostOffsetInBox): Added.
66
        (WebCore::RenderedPosition::atRightmostOffsetInBox): Added.
67
        (WebCore::RenderedPosition::uncachedInlineBox): Added. We can't use a static const variable because gcc thinks
68
        reinterpret_cast<InlineBox*>(1) is not an integral value.
69
        (WebCore::RenderedPosition::RenderedPosition):
70
        * editing/VisibleSelection.h:
71
        (WebCore::VisibleSelection::visibleBase): Added.
72
        (WebCore::VisibleSelection::visibleExtent): Added.
73
        * page/EventHandler.cpp:
74
        (WebCore::EventHandler::updateSelectionForMouseDrag):
75
1
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
76
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
2
77
3
        REGRESSION: Moving up doesn't work in some cases
78
        REGRESSION: Moving up doesn't work in some cases
- Source/WebCore/editing/FrameSelection.cpp -3 / +59 lines
Lines 56-61 Source/WebCore/editing/FrameSelection.cpp_sec1
56
#include "RenderTheme.h"
56
#include "RenderTheme.h"
57
#include "RenderView.h"
57
#include "RenderView.h"
58
#include "RenderWidget.h"
58
#include "RenderWidget.h"
59
#include "RenderedPosition.h"
59
#include "SecureTextInput.h"
60
#include "SecureTextInput.h"
60
#include "Settings.h"
61
#include "Settings.h"
61
#include "SpatialNavigation.h"
62
#include "SpatialNavigation.h"
Lines 163-175 void DragCaretController::setCaretPositi Source/WebCore/editing/FrameSelection.cpp_sec2
163
        updateCaretRect(document, m_position);
164
        updateCaretRect(document, m_position);
164
}
165
}
165
166
166
void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity)
167
static void adjustEndpointsAtBidiBoundary(VisiblePosition& base, VisiblePosition& extent)
168
{
169
    RenderedPosition basePosition(base);
170
    RenderedPosition extentPosition(extent);
171
172
    if (basePosition.isNull() || extentPosition.isNull() || basePosition == extentPosition)
173
        return;
174
175
    if (basePosition.atLeftBoundaryOfBidiRun()) {
176
        if (!extentPosition.atRightBoundaryOfBidiRun(basePosition.bidiLevelOnRight())
177
            && basePosition == extentPosition.leftBoundaryOfBidiRun(basePosition.bidiLevelOnRight())) {
178
            base = basePosition.positionAtLeftBoundaryOfBiDiRun();
179
            return;
180
        }
181
        return;
182
    }
183
184
    if (basePosition.atRightBoundaryOfBidiRun()) {
185
        if (!extentPosition.atLeftBoundaryOfBidiRun(basePosition.bidiLevelOnLeft())
186
            && basePosition == extentPosition.rightBoundaryOfBidiRun(basePosition.bidiLevelOnLeft())) {
187
            base = basePosition.positionAtRightBoundaryOfBiDiRun();
188
            return;
189
        }
190
        return;
191
    }
192
193
    if (extentPosition.atLeftBoundaryOfBidiRun()
194
        && extentPosition == basePosition.leftBoundaryOfBidiRun(extentPosition.bidiLevelOnRight())) {
195
        extent = extentPosition.positionAtLeftBoundaryOfBiDiRun();
196
        return;
197
    }
198
199
    if (extentPosition.atRightBoundaryOfBidiRun()
200
        && extentPosition == basePosition.rightBoundaryOfBidiRun(extentPosition.bidiLevelOnLeft())) {
201
        extent = extentPosition.positionAtRightBoundaryOfBiDiRun();
202
        return;
203
    }
204
}
205
206
void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity,
207
    EndPointsAdjustmentMode endpointsAdjustmentMode)
167
{
208
{
168
    VisibleSelection newSelection = passedNewSelection;
209
    VisibleSelection newSelection = passedNewSelection;
210
    bool isDirectional = shouldAlwaysUseDirectionalSelection(m_frame) || newSelection.isDirectional();
169
211
170
    if (shouldAlwaysUseDirectionalSelection(m_frame))
212
    VisiblePosition base = m_originalBase.isNotNull() ? m_originalBase : newSelection.visibleBase();
171
        newSelection.setIsDirectional(true);
213
    VisiblePosition newBase = base;
214
    VisiblePosition newExtent = newSelection.visibleExtent();
215
    if (endpointsAdjustmentMode == AdjustEndpointsAtBidiBoundary)
216
        adjustEndpointsAtBidiBoundary(newBase, newExtent);
217
218
    if (newBase != base || newExtent != newSelection.visibleExtent()) {
219
        m_originalBase = base;
220
        newSelection.setBase(newBase);
221
        newSelection.setExtent(newExtent);
222
    } else if (m_originalBase.isNotNull()) {
223
        if (m_selection.base() == newSelection.base())
224
            newSelection.setBase(m_originalBase);
225
        m_originalBase.clear();
226
    }
172
227
228
    newSelection.setIsDirectional(isDirectional); // Adjusting base and extent will make newSelection always directional
173
    if (m_selection == newSelection || !shouldChangeSelection(newSelection))
229
    if (m_selection == newSelection || !shouldChangeSelection(newSelection))
174
        return;
230
        return;
175
231
- Source/WebCore/editing/FrameSelection.h -1 / +3 lines
Lines 223-229 public: Source/WebCore/editing/FrameSelection.h_sec1
223
223
224
    bool shouldChangeSelection(const VisibleSelection&) const;
224
    bool shouldChangeSelection(const VisibleSelection&) const;
225
    bool shouldDeleteSelection(const VisibleSelection&) const;
225
    bool shouldDeleteSelection(const VisibleSelection&) const;
226
    void setNonDirectionalSelectionIfNeeded(const VisibleSelection&, TextGranularity);
226
    enum EndPointsAdjustmentMode { AdjustEndpointsAtBidiBoundary, DoNotAdjsutEndpoints };
227
    void setNonDirectionalSelectionIfNeeded(const VisibleSelection&, TextGranularity, EndPointsAdjustmentMode = DoNotAdjsutEndpoints);
227
    void setFocusedNodeIfNeeded();
228
    void setFocusedNodeIfNeeded();
228
    void notifyRendererOfSelectionChange(EUserTriggered);
229
    void notifyRendererOfSelectionChange(EUserTriggered);
229
230
Lines 282-287 private: Source/WebCore/editing/FrameSelection.h_sec2
282
    LayoutUnit m_xPosForVerticalArrowNavigation;
283
    LayoutUnit m_xPosForVerticalArrowNavigation;
283
284
284
    VisibleSelection m_selection;
285
    VisibleSelection m_selection;
286
    VisiblePosition m_originalBase; // Used to store base before the adjustment at bidi boundary
285
    TextGranularity m_granularity;
287
    TextGranularity m_granularity;
286
288
287
    RefPtr<EditingStyle> m_typingStyle;
289
    RefPtr<EditingStyle> m_typingStyle;
- Source/WebCore/editing/RenderedPosition.cpp +132 lines
Lines 32-37 Source/WebCore/editing/RenderedPosition.cpp_sec1
32
#include "RenderedPosition.h"
32
#include "RenderedPosition.h"
33
33
34
#include "InlineBox.h"
34
#include "InlineBox.h"
35
#include "InlineTextBox.h"
35
#include "Position.h"
36
#include "Position.h"
36
#include "VisiblePosition.h"
37
#include "VisiblePosition.h"
37
38
Lines 68-73 RenderedPosition::RenderedPosition(const Source/WebCore/editing/RenderedPosition.cpp_sec2
68
    : m_renderer(0)
69
    : m_renderer(0)
69
    , m_inlineBox(0)
70
    , m_inlineBox(0)
70
    , m_offset(0)
71
    , m_offset(0)
72
    , m_prevLeafChild(uncachedInlineBox())
73
    , m_nextLeafChild(uncachedInlineBox())
71
{
74
{
72
    if (position.isNull())
75
    if (position.isNull())
73
        return;
76
        return;
Lines 82-87 RenderedPosition::RenderedPosition(const Source/WebCore/editing/RenderedPosition.cpp_sec3
82
    : m_renderer(0)
85
    : m_renderer(0)
83
    , m_inlineBox(0)
86
    , m_inlineBox(0)
84
    , m_offset(0)
87
    , m_offset(0)
88
    , m_prevLeafChild(uncachedInlineBox())
89
    , m_nextLeafChild(uncachedInlineBox())
85
{
90
{
86
    if (position.isNull())
91
    if (position.isNull())
87
        return;
92
        return;
Lines 92-97 RenderedPosition::RenderedPosition(const Source/WebCore/editing/RenderedPosition.cpp_sec4
92
        m_renderer = rendererFromPosition(position);
97
        m_renderer = rendererFromPosition(position);
93
}
98
}
94
99
100
InlineBox* RenderedPosition::prevLeafChild() const
101
{
102
    if (m_prevLeafChild == uncachedInlineBox())
103
        m_prevLeafChild = m_inlineBox->prevLeafChild();
104
    return m_prevLeafChild;
105
}
106
107
InlineBox* RenderedPosition::nextLeafChild() const
108
{
109
    if (m_nextLeafChild == uncachedInlineBox())
110
        m_nextLeafChild = m_inlineBox->nextLeafChild();
111
    return m_nextLeafChild;
112
}
113
114
bool RenderedPosition::operator==(const RenderedPosition& other) const
115
{
116
    return (m_renderer == other.m_renderer && m_inlineBox == other.m_inlineBox && m_offset == other.m_offset)
117
        || (atLeftmostOffsetInBox() && other.atRightmostOffsetInBox() && prevLeafChild() == other.m_inlineBox)
118
        || (atRightmostOffsetInBox() && other.atLeftmostOffsetInBox() && nextLeafChild() == other.m_inlineBox);
119
}
120
121
unsigned char RenderedPosition::bidiLevelOnLeft() const
122
{
123
    InlineBox* box = atLeftmostOffsetInBox() ? prevLeafChild() : m_inlineBox;
124
    return box ? box->bidiLevel() : 0;
125
}
126
127
unsigned char RenderedPosition::bidiLevelOnRight() const
128
{
129
    InlineBox* box = atRightmostOffsetInBox() ? nextLeafChild() : m_inlineBox;
130
    return box ? box->bidiLevel() : 0;
131
}
132
133
RenderedPosition RenderedPosition::leftBoundaryOfBidiRun(unsigned char bidiLevelOfRun)
134
{
135
    if (!m_inlineBox || bidiLevelOfRun > m_inlineBox->bidiLevel())
136
        return RenderedPosition();
137
138
    InlineBox* box = m_inlineBox;
139
    do {
140
        InlineBox* prev = box->prevLeafChild();
141
        if (!prev || prev->bidiLevel() < bidiLevelOfRun)
142
            return RenderedPosition(box->renderer(), box, box->caretLeftmostOffset());
143
        box = prev;
144
    } while (box);
145
146
    ASSERT_NOT_REACHED();
147
    return RenderedPosition();
148
}
149
150
RenderedPosition RenderedPosition::rightBoundaryOfBidiRun(unsigned char bidiLevelOfRun)
151
{
152
    if (!m_inlineBox || bidiLevelOfRun > m_inlineBox->bidiLevel())
153
        return RenderedPosition();
154
155
    InlineBox* box = m_inlineBox;
156
    do {
157
        InlineBox* next = box->nextLeafChild();
158
        if (!next || next->bidiLevel() < bidiLevelOfRun)
159
            return RenderedPosition(box->renderer(), box, box->caretRightmostOffset());
160
        box = next;
161
    } while (box);
162
163
    ASSERT_NOT_REACHED();
164
    return RenderedPosition();
165
}
166
167
bool RenderedPosition::atLeftBoundaryOfBidiRun(ShouldMatchBidiLevel shouldMatchBidiLevel, unsigned char bidiLevelOfRun) const
168
{
169
    if (!m_inlineBox)
170
        return false;
171
172
    if (atLeftmostOffsetInBox()) {
173
        if (shouldMatchBidiLevel == IgnoreBidiLevel)
174
            return !prevLeafChild() || prevLeafChild()->bidiLevel() < m_inlineBox->bidiLevel();
175
        return m_inlineBox->bidiLevel() >= bidiLevelOfRun && (!prevLeafChild() || prevLeafChild()->bidiLevel() < bidiLevelOfRun);
176
    }
177
178
    if (atRightmostOffsetInBox()) {
179
        if (shouldMatchBidiLevel == IgnoreBidiLevel)
180
            return nextLeafChild() && m_inlineBox->bidiLevel() < nextLeafChild()->bidiLevel();
181
        return nextLeafChild() && m_inlineBox->bidiLevel() < bidiLevelOfRun && nextLeafChild()->bidiLevel() >= bidiLevelOfRun;
182
    }
183
184
    return false;
185
}
186
187
bool RenderedPosition::atRightBoundaryOfBidiRun(ShouldMatchBidiLevel shouldMatchBidiLevel, unsigned char bidiLevelOfRun) const
188
{
189
    if (!m_inlineBox)
190
        return false;
191
192
    if (atRightmostOffsetInBox()) {
193
        if (shouldMatchBidiLevel == IgnoreBidiLevel)
194
            return !nextLeafChild() || nextLeafChild()->bidiLevel() < m_inlineBox->bidiLevel();
195
        return m_inlineBox->bidiLevel() >= bidiLevelOfRun && (!nextLeafChild() || nextLeafChild()->bidiLevel() < bidiLevelOfRun);
196
    }
197
198
    if (atLeftmostOffsetInBox()) {
199
        if (shouldMatchBidiLevel == IgnoreBidiLevel)
200
            return prevLeafChild() && m_inlineBox->bidiLevel() < prevLeafChild()->bidiLevel();
201
        return prevLeafChild() && m_inlineBox->bidiLevel() < bidiLevelOfRun && prevLeafChild()->bidiLevel() >= bidiLevelOfRun;
202
    }
203
204
    return false;
205
}
206
207
Position RenderedPosition::positionAtLeftBoundaryOfBiDiRun() const
208
{
209
    ASSERT(atLeftBoundaryOfBidiRun());
210
211
    if (atLeftmostOffsetInBox())
212
        return createLegacyEditingPosition(m_renderer->node(), m_offset);
213
214
    return createLegacyEditingPosition(nextLeafChild()->renderer()->node(), m_offset);
215
}
216
217
Position RenderedPosition::positionAtRightBoundaryOfBiDiRun() const
218
{
219
    ASSERT(atRightBoundaryOfBidiRun());
220
221
    if (atRightmostOffsetInBox())
222
        return createLegacyEditingPosition(m_renderer->node(), m_offset);
223
224
    return createLegacyEditingPosition(prevLeafChild()->renderer()->node(), m_offset);
225
}
226
95
LayoutRect RenderedPosition::absoluteRect(int* extraWidthToEndOfLine) const
227
LayoutRect RenderedPosition::absoluteRect(int* extraWidthToEndOfLine) const
96
{
228
{
97
    if (isNull())
229
    if (isNull())
- Source/WebCore/editing/RenderedPosition.h +41 lines
Lines 46-70 public: Source/WebCore/editing/RenderedPosition.h_sec1
46
    RenderedPosition();
46
    RenderedPosition();
47
    explicit RenderedPosition(const VisiblePosition&);
47
    explicit RenderedPosition(const VisiblePosition&);
48
    explicit RenderedPosition(const Position&, EAffinity);
48
    explicit RenderedPosition(const Position&, EAffinity);
49
    bool operator==(const RenderedPosition& other) const;
49
50
50
    bool isNull() const { return !m_renderer; }
51
    bool isNull() const { return !m_renderer; }
51
    RootInlineBox* rootBox() { return m_inlineBox ? m_inlineBox->root() : 0; }
52
    RootInlineBox* rootBox() { return m_inlineBox ? m_inlineBox->root() : 0; }
52
53
54
    unsigned char bidiLevelOnLeft() const;
55
    unsigned char bidiLevelOnRight() const;
56
    RenderedPosition leftBoundaryOfBidiRun(unsigned char bidiLevelOfRun);
57
    RenderedPosition rightBoundaryOfBidiRun(unsigned char bidiLevelOfRun);
58
59
    enum ShouldMatchBidiLevel { MatchBidiLevel, IgnoreBidiLevel };
60
    bool atLeftBoundaryOfBidiRun() const { return atLeftBoundaryOfBidiRun(IgnoreBidiLevel, 0); }
61
    bool atRightBoundaryOfBidiRun() const { return atRightBoundaryOfBidiRun(IgnoreBidiLevel, 0); }
62
    // The following two functions return true only if the current position is at the end of the bidi run
63
    // of the specified bidi embedding level.
64
    bool atLeftBoundaryOfBidiRun(unsigned char bidiLevelOfRun) const { return atLeftBoundaryOfBidiRun(MatchBidiLevel, bidiLevelOfRun); }
65
    bool atRightBoundaryOfBidiRun(unsigned char bidiLevelOfRun) const { return atRightBoundaryOfBidiRun(MatchBidiLevel, bidiLevelOfRun); }
66
67
    Position positionAtLeftBoundaryOfBiDiRun() const;
68
    Position positionAtRightBoundaryOfBiDiRun() const;
69
53
    LayoutRect absoluteRect() const { return absoluteRect(0); }
70
    LayoutRect absoluteRect() const { return absoluteRect(0); }
54
    LayoutRect absoluteRect(int& extraWidthToEndOfLine) const { return absoluteRect(&extraWidthToEndOfLine); }
71
    LayoutRect absoluteRect(int& extraWidthToEndOfLine) const { return absoluteRect(&extraWidthToEndOfLine); }
55
72
56
private:
73
private:
74
    explicit RenderedPosition(RenderObject*, InlineBox*, int offset);
75
76
    InlineBox* prevLeafChild() const;
77
    InlineBox* nextLeafChild() const;
78
    bool atLeftmostOffsetInBox() const { return m_inlineBox && m_offset == m_inlineBox->caretLeftmostOffset(); }
79
    bool atRightmostOffsetInBox() const { return m_inlineBox && m_offset == m_inlineBox->caretRightmostOffset(); }
80
    bool atLeftBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const;
81
    bool atRightBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const;
82
57
    LayoutRect absoluteRect(int* extraWidthToEndOfLine) const;
83
    LayoutRect absoluteRect(int* extraWidthToEndOfLine) const;
58
84
59
    RenderObject* m_renderer;
85
    RenderObject* m_renderer;
60
    InlineBox* m_inlineBox;
86
    InlineBox* m_inlineBox;
61
    int m_offset;
87
    int m_offset;
88
89
    static InlineBox* uncachedInlineBox() { return reinterpret_cast<InlineBox*>(1); }
90
    mutable InlineBox* m_prevLeafChild;
91
    mutable InlineBox* m_nextLeafChild;
62
};
92
};
63
93
64
inline RenderedPosition::RenderedPosition()
94
inline RenderedPosition::RenderedPosition()
65
    : m_renderer(0)
95
    : m_renderer(0)
66
    , m_inlineBox(0)
96
    , m_inlineBox(0)
67
    , m_offset(0)
97
    , m_offset(0)
98
    , m_prevLeafChild(uncachedInlineBox())
99
    , m_nextLeafChild(uncachedInlineBox())
100
{
101
}
102
103
inline RenderedPosition::RenderedPosition(RenderObject* renderer, InlineBox* box, int offset)
104
    : m_renderer(renderer)
105
    , m_inlineBox(box)
106
    , m_offset(offset)
107
    , m_prevLeafChild(uncachedInlineBox())
108
    , m_nextLeafChild(uncachedInlineBox())
68
{
109
{
69
}
110
}
70
111
- Source/WebCore/editing/VisibleSelection.h +2 lines
Lines 69-74 public: Source/WebCore/editing/VisibleSelection.h_sec1
69
    
69
    
70
    VisiblePosition visibleStart() const { return VisiblePosition(m_start, isRange() ? DOWNSTREAM : affinity()); }
70
    VisiblePosition visibleStart() const { return VisiblePosition(m_start, isRange() ? DOWNSTREAM : affinity()); }
71
    VisiblePosition visibleEnd() const { return VisiblePosition(m_end, isRange() ? UPSTREAM : affinity()); }
71
    VisiblePosition visibleEnd() const { return VisiblePosition(m_end, isRange() ? UPSTREAM : affinity()); }
72
    VisiblePosition visibleBase() const { return VisiblePosition(m_base, isRange() ? (isBaseFirst() ? UPSTREAM : DOWNSTREAM) : affinity()); }
73
    VisiblePosition visibleExtent() const { return VisiblePosition(m_extent, isRange() ? (isBaseFirst() ? DOWNSTREAM : UPSTREAM) : affinity()); }
72
74
73
    bool isNone() const { return selectionType() == NoSelection; }
75
    bool isNone() const { return selectionType() == NoSelection; }
74
    bool isCaret() const { return selectionType() == CaretSelection; }
76
    bool isCaret() const { return selectionType() == CaretSelection; }
- Source/WebCore/page/EventHandler.cpp -1 / +2 lines
Lines 690-696 void EventHandler::updateSelectionForMou Source/WebCore/page/EventHandler.cpp_sec1
690
    if (m_frame->selection()->granularity() != CharacterGranularity)
690
    if (m_frame->selection()->granularity() != CharacterGranularity)
691
        newSelection.expandUsingGranularity(m_frame->selection()->granularity());
691
        newSelection.expandUsingGranularity(m_frame->selection()->granularity());
692
692
693
    m_frame->selection()->setNonDirectionalSelectionIfNeeded(newSelection, m_frame->selection()->granularity());
693
    m_frame->selection()->setNonDirectionalSelectionIfNeeded(newSelection, m_frame->selection()->granularity(),
694
        FrameSelection::AdjustEndpointsAtBidiBoundary);
694
}
695
}
695
#endif // ENABLE(DRAG_SUPPORT)
696
#endif // ENABLE(DRAG_SUPPORT)
696
697
- LayoutTests/ChangeLog +10 lines
Lines 1-3 LayoutTests/ChangeLog_sec1
1
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
2
3
        REGRESSION(r74971): Selection doesn't work correctly in BiDi Text
4
        https://bugs.webkit.org/show_bug.cgi?id=57340
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        * editing/selection/select-bidi-run-expected.txt: Added.
9
        * editing/selection/select-bidi-run.html: Added.
10
1
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
11
2011-09-12  Ryosuke Niwa  <rniwa@webkit.org>
2
12
3
        REGRESSION: Moving up doesn't work in some cases
13
        REGRESSION: Moving up doesn't work in some cases
- LayoutTests/editing/selection/select-bidi-run-expected.txt +122 lines
Line 0 LayoutTests/editing/selection/select-bidi-run-expected.txt_sec1
1
This test ensures WebKit lets user select bidirectional text intuitively. To manually test, select text in blue box in each test case below by a mouse drag from left to right. The changes in the selected text should match the expectations before |. Do the same by a mouse drag from right to left and expectations are after |.
2
3
Test "abcABC" in "abcABC":
4
Selecting from left to right
5
PASS selected "a"
6
PASS selected "ab"
7
PASS selected "abc"
8
PASS selected "abcAB"
9
PASS selected "abcA"
10
PASS selected "abcABC"
11
Selecting from right to left
12
PASS selected "A"
13
PASS selected "AB"
14
PASS selected "ABC"
15
PASS selected "cABC"
16
PASS selected "bcABC"
17
PASS selected "abcABC"
18
19
Test "ABCdef" in "ABCdef":
20
Selecting from left to right
21
PASS selected "C"
22
PASS selected "BC"
23
PASS selected "ABC"
24
PASS selected "ABCd"
25
FAIL selected "ABCde" but expected "ABCef"
26
PASS selected "ABCdef"
27
Selecting from right to left
28
PASS selected "f"
29
PASS selected "ef"
30
PASS selected "def"
31
PASS selected "BCdef"
32
PASS selected "Cdef"
33
PASS selected "ABCdef"
34
35
Test "ABC" in "abcABCdef":
36
Selecting from left to right
37
PASS selected "C"
38
PASS selected "BC"
39
PASS selected "ABC"
40
Selecting from right to left
41
PASS selected "A"
42
PASS selected "AB"
43
PASS selected "ABC"
44
45
Test "ABC" in "ABCdef":
46
Selecting from left to right
47
PASS selected "C"
48
PASS selected "BC"
49
PASS selected "ABC"
50
Selecting from right to left
51
PASS selected "A"
52
PASS selected "AB"
53
PASS selected "ABC"
54
55
Test "ef" in "ABCdef":
56
Selecting from left to right
57
PASS selected "e"
58
PASS selected "ef"
59
Selecting from right to left
60
PASS selected "f"
61
PASS selected "ef"
62
63
Test "AB" in "abcABC":
64
Selecting from left to right
65
PASS selected "B"
66
PASS selected "AB"
67
Selecting from right to left
68
PASS selected "A"
69
PASS selected "AB"
70
71
Test "12" in "aXM12JNd":
72
Selecting from left to right
73
PASS selected "1"
74
PASS selected "12"
75
Selecting from right to left
76
PASS selected "2"
77
PASS selected "12"
78
79
Test "ABC 123" in "ABC 123":
80
Selecting from left to right
81
PASS selected "1"
82
PASS selected "12"
83
PASS selected "123"
84
PASS selected " 123"
85
PASS selected "C 123"
86
PASS selected "BC 123"
87
PASS selected "ABC 123"
88
Selecting from right to left
89
PASS selected "A"
90
PASS selected "AB"
91
PASS selected "ABC"
92
PASS selected "ABC "
93
PASS selected "ABC 12"
94
PASS selected "ABC 1"
95
PASS selected "ABC 123"
96
97
Test "ABC 123" in "ABC 123":
98
Selecting from left to right
99
PASS selected "1"
100
PASS selected "12"
101
FAIL selected "123" but expected " 123"
102
FAIL selected " " but expected "C 123"
103
FAIL selected "C " but expected "BC 123"
104
FAIL selected "BC " but expected "ABC 123"
105
FAIL selected "123" but expected "undefined"
106
Selecting from right to left
107
PASS selected "A"
108
PASS selected "AB"
109
PASS selected "ABC"
110
FAIL selected "" but expected "ABC "
111
PASS selected "ABC 12"
112
PASS selected "ABC 1"
113
FAIL selected "123" but expected "ABC 123"
114
115
PASS successfullyParsed is true
116
117
TEST COMPLETE
118
119
120
121
122
- LayoutTests/editing/selection/select-bidi-run.html +141 lines
Line 0 LayoutTests/editing/selection/select-bidi-run.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta http-equiv="content-type" content="text/html; charset=utf-8">
5
<link rel="stylesheet" href="../../fast/js/resources/js-test-style.css">
6
<script src="../../fast/js/resources/js-test-pre.js"></script>
7
<style type="text/css">
8
#tests { font-size: 2.5em; padding: 0px; margin: 0px; }
9
dt { width: 15ex; padding: 0px 10px; margin: 0px; }
10
dd { font-size: 0.6em; margin: 0px; padding: 0px 10px; }
11
.target { background-color: #bbeeff; }
12
</style>
13
</head>
14
<body>
15
<p>This test ensures WebKit lets user select bidirectional text intuitively.
16
To manually test, select text in blue box in each test case below by a mouse drag from left to right.
17
The changes in the selected text should match the expectations before |.
18
Do the same by a mouse drag from right to left and expectations are after |.</p>
19
<div>Selected text: <span id="log"></span></div>
20
<dl id="tests">
21
<dt contenteditable><span class="target">abcאבג</span></dt>
22
<dd>a,ab,abc,abcAB,abcA,abcABC|A,AB,ABC,cABC,bcABC,abcABC</dd>
23
24
<dt contenteditable><span class="target">אבגdef</span></dt>
25
<dd>C,BC,ABC,ABCd,ABCef,ABCdef|f,ef,def,BCdef,Cdef,ABCdef</dd>
26
27
<dt contenteditable>abc<span class="target">אבג</span>def</dt>
28
<dd>C,BC,ABC|A,AB,ABC</dd>
29
30
<dt dir="rtl" contenteditable><span class="target">אבג</span>def</dt>
31
<dd>C,BC,ABC|A,AB,ABC</dd>
32
33
<dt dir="rtl">אבגd<span class="target">ef</span></dt>
34
<dd>e,ef|f,ef</dd>
35
36
<dt contenteditable>abc<span class="target">אב</span>ג</dt>
37
<dd>B,AB|A,AB</dd>
38
39
<dt contenteditable>aקל<span class="target">12</span>יםd</dt>
40
<dd>1,12|2,12</dd>
41
42
<dt contenteditable dir="rtl"><span class="target">אבג 123</span></dt>
43
<dd>1,12,123, 123,C 123,BC 123,ABC 123|A,AB,ABC,ABC ,ABC 12,ABC 1,ABC 123</dd>
44
45
<dt contenteditable><span class="target">אבג 123</span></dt>
46
<dd>1,12, 123,C 123,BC 123,ABC 123|A,AB,ABC,ABC ,ABC 12,ABC 1,ABC 123</dd>
47
48
<!--<dt contenteditable><span class="target">אבג 123 - 456</span></dt>
49
<dd>1,12, 123,C 123,BC 123,ABC 123|A,AB,ABC,ABC ,ABC 12,ABC 1,ABC 123</dd>-->
50
51
</dl>
52
<div id="console"></div>
53
<pre><script>
54
var tests = document.getElementById('tests');
55
56
String.prototype.fold = function () {
57
    var result = '';
58
    for (var i = 0; i < this.length; i++) {
59
        var code = this.charCodeAt(i);
60
        if (0x05d0 <= code && code <= 0x05ea)// Hebrew Alef
61
            code += -0x05d0 + 'A'.charCodeAt(0);
62
        result += String.fromCharCode(code);
63
    }
64
    return result;
65
}
66
67
function assertEqual(expected, actual) {
68
    document.writeln('E:' + expected + ' A:' + actual);
69
}
70
71
function selectByMouseDragAndVerifyResult(target, leftToRight, expectations) {
72
    var y = target.offsetTop + target.offsetHeight / 2;
73
    var left = target.offsetLeft;
74
75
    var startX = left + (leftToRight ? 0 : target.offsetWidth);
76
    eventSender.dragMode = false;
77
    eventSender.leapForward(3000);
78
    eventSender.mouseMoveTo(startX, y);
79
    eventSender.mouseDown();
80
81
    var previousSelectedText = '';
82
    var j = 0;
83
    var xIncrement = leftToRight ? 1 : -1;
84
    for (var x = startX; left <= x && x <= left + target.offsetWidth; x += xIncrement) {
85
        eventSender.leapForward(100);
86
        eventSender.mouseMoveTo(x, y);
87
88
        var selectedText = window.getSelection().toString().fold();
89
        if (previousSelectedText != selectedText) {
90
            if (expectations[j] == selectedText)
91
                testPassed('selected "' + selectedText + '"');
92
            else
93
                testFailed('selected "' + selectedText + '" but expected "' + expectations[j] + '"');
94
            previousSelectedText = selectedText;
95
            j++;
96
        }
97
    }
98
99
    eventSender.mouseUp();
100
}
101
102
if (window.layoutTestController) {
103
    layoutTestController.dumpAsText();
104
105
    var tests = document.getElementById('tests').getElementsByTagName('dt');
106
    var testExpectations = document.getElementById('tests').getElementsByTagName('dd');
107
108
    for (var i = 0; i < tests.length; i++) {        
109
        tests[i].style.display = null;
110
        testExpectations[i].style.display = null;
111
112
        var target = tests[i].getElementsByClassName('target')[0];
113
        var testExpectation = testExpectations[i].textContent;
114
115
        debug('Test "' + target.textContent.fold() + '" in "' + target.parentNode.textContent.fold() + '":');
116
        debug('Selecting from left to right');
117
        selectByMouseDragAndVerifyResult(target, true, testExpectation.split(/\|/)[0].split(/,/));
118
        debug('Selecting from right to left');
119
        selectByMouseDragAndVerifyResult(target, false, testExpectation.split(/\|/)[1].split(/,/));
120
        debug('');
121
122
        // Some tests may be ouside of the window so bring them in as we process.
123
        tests[i].style.display = 'none';
124
        testExpectations[i].style.display = 'none';
125
    }
126
127
    document.getElementById('tests').style.display = 'none';
128
    document.getElementById('log').parentNode.style.display = 'none';
129
} else {
130
    debug('This test requires eventSender');
131
    document.onselectionchange = function () {
132
        document.getElementById('log').textContent = window.getSelection().toString().fold();
133
    }
134
}
135
136
var successfullyParsed = true;
137
138
</script>
139
<script src="../../fast/js/resources/js-test-post.js"></script>
140
</body>
141
</html>

Return to Bug 57340