OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights |
| 3 * reserved. |
| 4 * |
| 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions |
| 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright |
| 11 * notice, this list of conditions and the following disclaimer in the |
| 12 * documentation and/or other materials provided with the distribution. |
| 13 * |
| 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 */ |
| 26 |
| 27 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 28 // Use of this source code is governed by a BSD-style license that can be |
| 29 // found in the LICENSE file. |
| 30 |
| 31 #include "core/editing/VisibleUnits.h" |
| 32 |
| 33 #include "core/editing/EditingUtilities.h" |
| 34 #include "core/layout/LayoutText.h" |
| 35 #include "core/layout/api/LayoutItem.h" |
| 36 |
| 37 namespace blink { |
| 38 |
| 39 namespace { |
| 40 |
| 41 bool NodeIsUserSelectAll(const Node* node) { |
| 42 return node && node->GetLayoutObject() && |
| 43 node->GetLayoutObject()->Style()->UserSelect() == EUserSelect::kAll; |
| 44 } |
| 45 |
| 46 template <typename Strategy> |
| 47 PositionTemplate<Strategy> StartOfParagraphAlgorithm( |
| 48 const PositionTemplate<Strategy>& position, |
| 49 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 50 Node* const start_node = position.AnchorNode(); |
| 51 |
| 52 if (!start_node) |
| 53 return PositionTemplate<Strategy>(); |
| 54 |
| 55 if (IsRenderedAsNonInlineTableImageOrHR(start_node)) |
| 56 return PositionTemplate<Strategy>::BeforeNode(start_node); |
| 57 |
| 58 Element* const start_block = EnclosingBlock( |
| 59 PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node), |
| 60 kCannotCrossEditingBoundary); |
| 61 ContainerNode* const highest_root = HighestEditableRoot(position); |
| 62 const bool start_node_is_editable = HasEditableStyle(*start_node); |
| 63 |
| 64 Node* candidate_node = start_node; |
| 65 PositionAnchorType candidate_type = position.AnchorType(); |
| 66 int candidate_offset = position.ComputeEditingOffset(); |
| 67 |
| 68 Node* previous_node_iterator = start_node; |
| 69 while (previous_node_iterator) { |
| 70 if (boundary_crossing_rule == kCannotCrossEditingBoundary && |
| 71 !NodeIsUserSelectAll(previous_node_iterator) && |
| 72 HasEditableStyle(*previous_node_iterator) != start_node_is_editable) |
| 73 break; |
| 74 if (boundary_crossing_rule == kCanSkipOverEditingBoundary) { |
| 75 while (previous_node_iterator && |
| 76 HasEditableStyle(*previous_node_iterator) != |
| 77 start_node_is_editable) { |
| 78 previous_node_iterator = |
| 79 Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
| 80 } |
| 81 if (!previous_node_iterator || |
| 82 !previous_node_iterator->IsDescendantOf(highest_root)) |
| 83 break; |
| 84 } |
| 85 |
| 86 const LayoutItem layout_item = |
| 87 LayoutItem(previous_node_iterator->GetLayoutObject()); |
| 88 if (layout_item.IsNull()) { |
| 89 previous_node_iterator = |
| 90 Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
| 91 continue; |
| 92 } |
| 93 const ComputedStyle& style = layout_item.StyleRef(); |
| 94 if (style.Visibility() != EVisibility::kVisible) { |
| 95 previous_node_iterator = |
| 96 Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
| 97 continue; |
| 98 } |
| 99 |
| 100 if (layout_item.IsBR() || IsEnclosingBlock(previous_node_iterator)) |
| 101 break; |
| 102 |
| 103 if (layout_item.IsText() && |
| 104 ToLayoutText(previous_node_iterator->GetLayoutObject()) |
| 105 ->ResolvedTextLength()) { |
| 106 SECURITY_DCHECK(previous_node_iterator->IsTextNode()); |
| 107 if (style.PreserveNewline()) { |
| 108 LayoutText* text = |
| 109 ToLayoutText(previous_node_iterator->GetLayoutObject()); |
| 110 int index = text->TextLength(); |
| 111 if (previous_node_iterator == start_node && candidate_offset < index) |
| 112 index = max(0, candidate_offset); |
| 113 while (--index >= 0) { |
| 114 if ((*text)[index] == '\n') { |
| 115 return PositionTemplate<Strategy>(ToText(previous_node_iterator), |
| 116 index + 1); |
| 117 } |
| 118 } |
| 119 } |
| 120 candidate_node = previous_node_iterator; |
| 121 candidate_type = PositionAnchorType::kOffsetInAnchor; |
| 122 candidate_offset = 0; |
| 123 previous_node_iterator = |
| 124 Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
| 125 } else if (EditingIgnoresContent(*previous_node_iterator) || |
| 126 IsDisplayInsideTable(previous_node_iterator)) { |
| 127 candidate_node = previous_node_iterator; |
| 128 candidate_type = PositionAnchorType::kBeforeAnchor; |
| 129 previous_node_iterator = previous_node_iterator->previousSibling() |
| 130 ? previous_node_iterator->previousSibling() |
| 131 : Strategy::PreviousPostOrder( |
| 132 *previous_node_iterator, start_block); |
| 133 } else { |
| 134 previous_node_iterator = |
| 135 Strategy::PreviousPostOrder(*previous_node_iterator, start_block); |
| 136 } |
| 137 } |
| 138 |
| 139 if (candidate_type == PositionAnchorType::kOffsetInAnchor) |
| 140 return PositionTemplate<Strategy>(candidate_node, candidate_offset); |
| 141 |
| 142 return PositionTemplate<Strategy>(candidate_node, candidate_type); |
| 143 } |
| 144 |
| 145 template <typename Strategy> |
| 146 VisiblePositionTemplate<Strategy> StartOfParagraphAlgorithm( |
| 147 const VisiblePositionTemplate<Strategy>& visible_position, |
| 148 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 149 DCHECK(visible_position.IsValid()) << visible_position; |
| 150 return CreateVisiblePosition(StartOfParagraphAlgorithm( |
| 151 visible_position.DeepEquivalent(), boundary_crossing_rule)); |
| 152 } |
| 153 |
| 154 template <typename Strategy> |
| 155 PositionTemplate<Strategy> EndOfParagraphAlgorithm( |
| 156 const PositionTemplate<Strategy>& position, |
| 157 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 158 Node* const start_node = position.AnchorNode(); |
| 159 |
| 160 if (!start_node) |
| 161 return PositionTemplate<Strategy>(); |
| 162 |
| 163 if (IsRenderedAsNonInlineTableImageOrHR(start_node)) |
| 164 return PositionTemplate<Strategy>::AfterNode(start_node); |
| 165 |
| 166 Element* const start_block = EnclosingBlock( |
| 167 PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(start_node), |
| 168 kCannotCrossEditingBoundary); |
| 169 ContainerNode* const highest_root = HighestEditableRoot(position); |
| 170 const bool start_node_is_editable = HasEditableStyle(*start_node); |
| 171 |
| 172 Node* candidate_node = start_node; |
| 173 PositionAnchorType candidate_type = position.AnchorType(); |
| 174 int candidate_offset = position.ComputeEditingOffset(); |
| 175 |
| 176 Node* next_node_iterator = start_node; |
| 177 while (next_node_iterator) { |
| 178 if (boundary_crossing_rule == kCannotCrossEditingBoundary && |
| 179 !NodeIsUserSelectAll(next_node_iterator) && |
| 180 HasEditableStyle(*next_node_iterator) != start_node_is_editable) |
| 181 break; |
| 182 if (boundary_crossing_rule == kCanSkipOverEditingBoundary) { |
| 183 while (next_node_iterator && |
| 184 HasEditableStyle(*next_node_iterator) != start_node_is_editable) |
| 185 next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
| 186 if (!next_node_iterator || |
| 187 !next_node_iterator->IsDescendantOf(highest_root)) |
| 188 break; |
| 189 } |
| 190 |
| 191 LayoutObject* const layout_object = next_node_iterator->GetLayoutObject(); |
| 192 if (!layout_object) { |
| 193 next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
| 194 continue; |
| 195 } |
| 196 const ComputedStyle& style = layout_object->StyleRef(); |
| 197 if (style.Visibility() != EVisibility::kVisible) { |
| 198 next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
| 199 continue; |
| 200 } |
| 201 |
| 202 if (layout_object->IsBR() || IsEnclosingBlock(next_node_iterator)) |
| 203 break; |
| 204 |
| 205 // TODO(editing-dev): We avoid returning a position where the layoutObject |
| 206 // can't accept the caret. |
| 207 if (layout_object->IsText() && |
| 208 ToLayoutText(layout_object)->ResolvedTextLength()) { |
| 209 SECURITY_DCHECK(next_node_iterator->IsTextNode()); |
| 210 LayoutText* const text = ToLayoutText(layout_object); |
| 211 if (style.PreserveNewline()) { |
| 212 const int length = ToLayoutText(layout_object)->TextLength(); |
| 213 for (int i = (next_node_iterator == start_node ? candidate_offset : 0); |
| 214 i < length; ++i) { |
| 215 if ((*text)[i] == '\n') { |
| 216 return PositionTemplate<Strategy>(ToText(next_node_iterator), |
| 217 i + text->TextStartOffset()); |
| 218 } |
| 219 } |
| 220 } |
| 221 |
| 222 candidate_node = next_node_iterator; |
| 223 candidate_type = PositionAnchorType::kOffsetInAnchor; |
| 224 candidate_offset = |
| 225 layout_object->CaretMaxOffset() + text->TextStartOffset(); |
| 226 next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
| 227 } else if (EditingIgnoresContent(*next_node_iterator) || |
| 228 IsDisplayInsideTable(next_node_iterator)) { |
| 229 candidate_node = next_node_iterator; |
| 230 candidate_type = PositionAnchorType::kAfterAnchor; |
| 231 next_node_iterator = |
| 232 Strategy::NextSkippingChildren(*next_node_iterator, start_block); |
| 233 } else { |
| 234 next_node_iterator = Strategy::Next(*next_node_iterator, start_block); |
| 235 } |
| 236 } |
| 237 |
| 238 if (candidate_type == PositionAnchorType::kOffsetInAnchor) |
| 239 return PositionTemplate<Strategy>(candidate_node, candidate_offset); |
| 240 |
| 241 return PositionTemplate<Strategy>(candidate_node, candidate_type); |
| 242 } |
| 243 |
| 244 template <typename Strategy> |
| 245 VisiblePositionTemplate<Strategy> EndOfParagraphAlgorithm( |
| 246 const VisiblePositionTemplate<Strategy>& visible_position, |
| 247 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 248 DCHECK(visible_position.IsValid()) << visible_position; |
| 249 return CreateVisiblePosition(EndOfParagraphAlgorithm( |
| 250 visible_position.DeepEquivalent(), boundary_crossing_rule)); |
| 251 } |
| 252 |
| 253 template <typename Strategy> |
| 254 bool IsStartOfParagraphAlgorithm( |
| 255 const VisiblePositionTemplate<Strategy>& pos, |
| 256 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 257 DCHECK(pos.IsValid()) << pos; |
| 258 return pos.IsNotNull() && |
| 259 pos.DeepEquivalent() == |
| 260 StartOfParagraph(pos, boundary_crossing_rule).DeepEquivalent(); |
| 261 } |
| 262 |
| 263 template <typename Strategy> |
| 264 bool IsEndOfParagraphAlgorithm( |
| 265 const VisiblePositionTemplate<Strategy>& pos, |
| 266 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 267 DCHECK(pos.IsValid()) << pos; |
| 268 return pos.IsNotNull() && |
| 269 pos.DeepEquivalent() == |
| 270 EndOfParagraph(pos, boundary_crossing_rule).DeepEquivalent(); |
| 271 } |
| 272 |
| 273 } // namespace |
| 274 |
| 275 VisiblePosition StartOfParagraph( |
| 276 const VisiblePosition& c, |
| 277 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 278 return StartOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule); |
| 279 } |
| 280 |
| 281 VisiblePositionInFlatTree StartOfParagraph( |
| 282 const VisiblePositionInFlatTree& c, |
| 283 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 284 return StartOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
| 285 c, boundary_crossing_rule); |
| 286 } |
| 287 |
| 288 VisiblePosition EndOfParagraph( |
| 289 const VisiblePosition& c, |
| 290 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 291 return EndOfParagraphAlgorithm<EditingStrategy>(c, boundary_crossing_rule); |
| 292 } |
| 293 |
| 294 VisiblePositionInFlatTree EndOfParagraph( |
| 295 const VisiblePositionInFlatTree& c, |
| 296 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 297 return EndOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
| 298 c, boundary_crossing_rule); |
| 299 } |
| 300 |
| 301 // TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not |
| 302 // always true |
| 303 VisiblePosition StartOfNextParagraph(const VisiblePosition& visible_position) { |
| 304 DCHECK(visible_position.IsValid()) << visible_position; |
| 305 VisiblePosition paragraph_end( |
| 306 EndOfParagraph(visible_position, kCanSkipOverEditingBoundary)); |
| 307 VisiblePosition after_paragraph_end( |
| 308 NextPositionOf(paragraph_end, kCannotCrossEditingBoundary)); |
| 309 // The position after the last position in the last cell of a table |
| 310 // is not the start of the next paragraph. |
| 311 if (TableElementJustBefore(after_paragraph_end)) |
| 312 return NextPositionOf(after_paragraph_end, kCannotCrossEditingBoundary); |
| 313 return after_paragraph_end; |
| 314 } |
| 315 |
| 316 // TODO(editing-dev): isStartOfParagraph(startOfNextParagraph(pos)) is not |
| 317 // always true |
| 318 bool InSameParagraph(const VisiblePosition& a, |
| 319 const VisiblePosition& b, |
| 320 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 321 DCHECK(a.IsValid()) << a; |
| 322 DCHECK(b.IsValid()) << b; |
| 323 return a.IsNotNull() && |
| 324 StartOfParagraph(a, boundary_crossing_rule).DeepEquivalent() == |
| 325 StartOfParagraph(b, boundary_crossing_rule).DeepEquivalent(); |
| 326 } |
| 327 |
| 328 bool IsStartOfParagraph(const VisiblePosition& pos, |
| 329 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 330 return IsStartOfParagraphAlgorithm<EditingStrategy>(pos, |
| 331 boundary_crossing_rule); |
| 332 } |
| 333 |
| 334 bool IsStartOfParagraph(const VisiblePositionInFlatTree& pos, |
| 335 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 336 return IsStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
| 337 pos, boundary_crossing_rule); |
| 338 } |
| 339 |
| 340 bool IsEndOfParagraph(const VisiblePosition& pos, |
| 341 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 342 return IsEndOfParagraphAlgorithm<EditingStrategy>(pos, |
| 343 boundary_crossing_rule); |
| 344 } |
| 345 |
| 346 bool IsEndOfParagraph(const VisiblePositionInFlatTree& pos, |
| 347 EditingBoundaryCrossingRule boundary_crossing_rule) { |
| 348 return IsEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>( |
| 349 pos, boundary_crossing_rule); |
| 350 } |
| 351 |
| 352 // TODO(editing-dev): We should move |PreviousParagraphPosition()| to |
| 353 // "SelectionModifier.cpp" |
| 354 VisiblePosition PreviousParagraphPosition(const VisiblePosition& p, |
| 355 LayoutUnit x) { |
| 356 DCHECK(p.IsValid()) << p; |
| 357 VisiblePosition pos = p; |
| 358 do { |
| 359 VisiblePosition n = PreviousLinePosition(pos, x); |
| 360 if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent()) |
| 361 break; |
| 362 pos = n; |
| 363 } while (InSameParagraph(p, pos)); |
| 364 return pos; |
| 365 } |
| 366 |
| 367 // TODO(editing-dev): We should move |NextParagraphPosition()| to |
| 368 // "SelectionModifier.cpp" |
| 369 VisiblePosition NextParagraphPosition(const VisiblePosition& p, LayoutUnit x) { |
| 370 DCHECK(p.IsValid()) << p; |
| 371 VisiblePosition pos = p; |
| 372 do { |
| 373 VisiblePosition n = NextLinePosition(pos, x); |
| 374 if (n.IsNull() || n.DeepEquivalent() == pos.DeepEquivalent()) |
| 375 break; |
| 376 pos = n; |
| 377 } while (InSameParagraph(p, pos)); |
| 378 return pos; |
| 379 } |
| 380 |
| 381 EphemeralRange ExpandToParagraphBoundary(const EphemeralRange& range) { |
| 382 const VisiblePosition& start = CreateVisiblePosition(range.StartPosition()); |
| 383 DCHECK(start.IsNotNull()) << range.StartPosition(); |
| 384 const Position& paragraph_start = StartOfParagraph(start).DeepEquivalent(); |
| 385 DCHECK(paragraph_start.IsNotNull()) << range.StartPosition(); |
| 386 |
| 387 const VisiblePosition& end = CreateVisiblePosition(range.EndPosition()); |
| 388 DCHECK(end.IsNotNull()) << range.EndPosition(); |
| 389 const Position& paragraph_end = EndOfParagraph(end).DeepEquivalent(); |
| 390 DCHECK(paragraph_end.IsNotNull()) << range.EndPosition(); |
| 391 |
| 392 // TODO(xiaochengh): There are some cases (crbug.com/640112) where we get |
| 393 // |paragraphStart > paragraphEnd|, which is the reason we cannot directly |
| 394 // return |EphemeralRange(paragraphStart, paragraphEnd)|. This is not |
| 395 // desired, though. We should do more investigation to ensure that why |
| 396 // |paragraphStart <= paragraphEnd| is violated. |
| 397 const Position& result_start = |
| 398 paragraph_start.IsNotNull() && paragraph_start <= range.StartPosition() |
| 399 ? paragraph_start |
| 400 : range.StartPosition(); |
| 401 const Position& result_end = |
| 402 paragraph_end.IsNotNull() && paragraph_end >= range.EndPosition() |
| 403 ? paragraph_end |
| 404 : range.EndPosition(); |
| 405 return EphemeralRange(result_start, result_end); |
| 406 } |
| 407 |
| 408 } // namespace blink |
OLD | NEW |