Changeset 95964 in webkit
- Timestamp:
- Sep 26, 2011 11:30:29 AM (13 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r95963 r95964 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 Eric Seidel. 7 8 * editing/selection/select-bidi-run-expected.txt: Added. 9 * editing/selection/select-bidi-run.html: Added. 10 1 11 2011-09-26 Csaba Osztrogonác <ossy@webkit.org> 2 12 -
trunk/Source/WebCore/ChangeLog
r95962 r95964 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 Eric Seidel. 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::isEquivalent): 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 76 2011-09-26 Sheriff Bot <webkit.review.bot@gmail.com> 2 77 -
trunk/Source/WebCore/editing/FrameSelection.cpp
r94966 r95964 57 57 #include "RenderView.h" 58 58 #include "RenderWidget.h" 59 #include "RenderedPosition.h" 59 60 #include "SecureTextInput.h" 60 61 #include "Settings.h" … … 164 165 } 165 166 166 void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity) 167 static void adjustEndpointsAtBidiBoundary(VisiblePosition& visibleBase, VisiblePosition& visibleExtent) 168 { 169 RenderedPosition base(visibleBase); 170 RenderedPosition extent(visibleExtent); 171 172 if (base.isNull() || extent.isNull() || base.isEquivalent(extent)) 173 return; 174 175 if (base.atLeftBoundaryOfBidiRun()) { 176 if (!extent.atRightBoundaryOfBidiRun(base.bidiLevelOnRight()) 177 && base.isEquivalent(extent.leftBoundaryOfBidiRun(base.bidiLevelOnRight()))) { 178 visibleBase = base.positionAtLeftBoundaryOfBiDiRun(); 179 return; 180 } 181 return; 182 } 183 184 if (base.atRightBoundaryOfBidiRun()) { 185 if (!extent.atLeftBoundaryOfBidiRun(base.bidiLevelOnLeft()) 186 && base.isEquivalent(extent.rightBoundaryOfBidiRun(base.bidiLevelOnLeft()))) { 187 visibleBase = base.positionAtRightBoundaryOfBiDiRun(); 188 return; 189 } 190 return; 191 } 192 193 if (extent.atLeftBoundaryOfBidiRun() && extent.isEquivalent(base.leftBoundaryOfBidiRun(extent.bidiLevelOnRight()))) { 194 visibleExtent = extent.positionAtLeftBoundaryOfBiDiRun(); 195 return; 196 } 197 198 if (extent.atRightBoundaryOfBidiRun() && extent.isEquivalent(base.rightBoundaryOfBidiRun(extent.bidiLevelOnLeft()))) { 199 visibleExtent = extent.positionAtRightBoundaryOfBiDiRun(); 200 return; 201 } 202 } 203 204 void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity, 205 EndPointsAdjustmentMode endpointsAdjustmentMode) 167 206 { 168 207 VisibleSelection newSelection = passedNewSelection; 169 170 if (shouldAlwaysUseDirectionalSelection(m_frame)) 171 newSelection.setIsDirectional(true); 172 208 bool isDirectional = shouldAlwaysUseDirectionalSelection(m_frame) || newSelection.isDirectional(); 209 210 VisiblePosition base = m_originalBase.isNotNull() ? m_originalBase : newSelection.visibleBase(); 211 VisiblePosition newBase = base; 212 VisiblePosition newExtent = newSelection.visibleExtent(); 213 if (endpointsAdjustmentMode == AdjustEndpointsAtBidiBoundary) 214 adjustEndpointsAtBidiBoundary(newBase, newExtent); 215 216 if (newBase != base || newExtent != newSelection.visibleExtent()) { 217 m_originalBase = base; 218 newSelection.setBase(newBase); 219 newSelection.setExtent(newExtent); 220 } else if (m_originalBase.isNotNull()) { 221 if (m_selection.base() == newSelection.base()) 222 newSelection.setBase(m_originalBase); 223 m_originalBase.clear(); 224 } 225 226 newSelection.setIsDirectional(isDirectional); // Adjusting base and extent will make newSelection always directional 173 227 if (m_selection == newSelection || !shouldChangeSelection(newSelection)) 174 228 return; -
trunk/Source/WebCore/editing/FrameSelection.h
r94966 r95964 224 224 bool shouldChangeSelection(const VisibleSelection&) const; 225 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 228 void setFocusedNodeIfNeeded(); 228 229 void notifyRendererOfSelectionChange(EUserTriggered); … … 283 284 284 285 VisibleSelection m_selection; 286 VisiblePosition m_originalBase; // Used to store base before the adjustment at bidi boundary 285 287 TextGranularity m_granularity; 286 288 -
trunk/Source/WebCore/editing/RenderedPosition.cpp
r95901 r95964 33 33 34 34 #include "InlineBox.h" 35 #include "InlineTextBox.h" 35 36 #include "Position.h" 36 37 #include "VisiblePosition.h" … … 69 70 , m_inlineBox(0) 70 71 , m_offset(0) 72 , m_prevLeafChild(uncachedInlineBox()) 73 , m_nextLeafChild(uncachedInlineBox()) 71 74 { 72 75 if (position.isNull()) … … 83 86 , m_inlineBox(0) 84 87 , m_offset(0) 88 , m_prevLeafChild(uncachedInlineBox()) 89 , m_nextLeafChild(uncachedInlineBox()) 85 90 { 86 91 if (position.isNull()) … … 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::isEquivalent(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 227 LayoutRect RenderedPosition::absoluteRect(int* extraWidthToEndOfLine) const 96 228 { -
trunk/Source/WebCore/editing/RenderedPosition.h
r95901 r95964 47 47 explicit RenderedPosition(const VisiblePosition&); 48 48 explicit RenderedPosition(const Position&, EAffinity); 49 bool isEquivalent(const RenderedPosition&) const; 49 50 50 51 bool isNull() const { return !m_renderer; } 51 52 RootInlineBox* rootBox() { return m_inlineBox ? m_inlineBox->root() : 0; } 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; 52 69 53 70 LayoutRect absoluteRect() const { return absoluteRect(0); } … … 55 72 56 73 private: 74 bool operator==(const RenderedPosition&) const { return false; } 75 explicit RenderedPosition(RenderObject*, InlineBox*, int offset); 76 77 InlineBox* prevLeafChild() const; 78 InlineBox* nextLeafChild() const; 79 bool atLeftmostOffsetInBox() const { return m_inlineBox && m_offset == m_inlineBox->caretLeftmostOffset(); } 80 bool atRightmostOffsetInBox() const { return m_inlineBox && m_offset == m_inlineBox->caretRightmostOffset(); } 81 bool atLeftBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; 82 bool atRightBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; 83 57 84 LayoutRect absoluteRect(int* extraWidthToEndOfLine) const; 58 85 … … 60 87 InlineBox* m_inlineBox; 61 88 int m_offset; 89 90 static InlineBox* uncachedInlineBox() { return reinterpret_cast<InlineBox*>(1); } 91 // Needs to be different form 0 so pick 1 because it's also on the null page. 92 93 mutable InlineBox* m_prevLeafChild; 94 mutable InlineBox* m_nextLeafChild; 62 95 }; 63 96 … … 66 99 , m_inlineBox(0) 67 100 , m_offset(0) 101 , m_prevLeafChild(uncachedInlineBox()) 102 , m_nextLeafChild(uncachedInlineBox()) 103 { 104 } 105 106 inline RenderedPosition::RenderedPosition(RenderObject* renderer, InlineBox* box, int offset) 107 : m_renderer(renderer) 108 , m_inlineBox(box) 109 , m_offset(offset) 110 , m_prevLeafChild(uncachedInlineBox()) 111 , m_nextLeafChild(uncachedInlineBox()) 68 112 { 69 113 } -
trunk/Source/WebCore/editing/VisibleSelection.h
r93134 r95964 70 70 VisiblePosition visibleStart() const { return VisiblePosition(m_start, isRange() ? DOWNSTREAM : affinity()); } 71 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 75 bool isNone() const { return selectionType() == NoSelection; } -
trunk/Source/WebCore/page/EventHandler.cpp
r95922 r95964 691 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 696 #endif // ENABLE(DRAG_SUPPORT)
Note: See TracChangeset
for help on using the changeset viewer.