OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. | 2 * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
171 | 171 |
172 void SelectionEditor::contextDestroyed(Document*) { | 172 void SelectionEditor::contextDestroyed(Document*) { |
173 dispose(); | 173 dispose(); |
174 m_styleVersion = static_cast<uint64_t>(-1); | 174 m_styleVersion = static_cast<uint64_t>(-1); |
175 m_selection = SelectionInDOMTree(); | 175 m_selection = SelectionInDOMTree(); |
176 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); | 176 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
177 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); | 177 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
178 m_cacheIsDirty = false; | 178 m_cacheIsDirty = false; |
179 } | 179 } |
180 | 180 |
| 181 static Position computePositionForChildrenRemoval(const Position& position, |
| 182 ContainerNode& container) { |
| 183 Node* node = position.computeContainerNode(); |
| 184 if (container.containsIncludingHostElements(*node)) |
| 185 return Position::firstPositionInNode(&container); |
| 186 return position; |
| 187 } |
| 188 |
| 189 void SelectionEditor::nodeChildrenWillBeRemoved(ContainerNode& container) { |
| 190 if (m_selection.isNone()) |
| 191 return; |
| 192 const Position oldBase = m_selection.m_base; |
| 193 const Position oldExtent = m_selection.m_extent; |
| 194 const Position& newBase = |
| 195 computePositionForChildrenRemoval(oldBase, container); |
| 196 const Position& newExtent = |
| 197 computePositionForChildrenRemoval(oldExtent, container); |
| 198 if (newBase == oldBase && newExtent == oldExtent) |
| 199 return; |
| 200 m_selection = SelectionInDOMTree::Builder() |
| 201 .setBaseAndExtent(newBase, newExtent) |
| 202 .build(); |
| 203 markCacheDirty(); |
| 204 } |
| 205 |
| 206 static Position computePositionForNodeRemoval(const Position& position, |
| 207 Node& nodeToBeRemoved) { |
| 208 Position result = position; |
| 209 // TODO(yosin): We should rename |updatePositionForNodeRemoval()| |
| 210 // to |computePositionForNodeRemoval()| to avoid using output parameter. |
| 211 updatePositionForNodeRemoval(result, nodeToBeRemoved); |
| 212 return result; |
| 213 } |
| 214 |
| 215 void SelectionEditor::nodeWillBeRemoved(Node& nodeToBeRemoved) { |
| 216 if (m_selection.isNone()) |
| 217 return; |
| 218 const Position oldBase = m_selection.m_base; |
| 219 const Position oldExtent = m_selection.m_extent; |
| 220 const Position& newBase = |
| 221 computePositionForNodeRemoval(oldBase, nodeToBeRemoved); |
| 222 const Position& newExtent = |
| 223 computePositionForNodeRemoval(oldExtent, nodeToBeRemoved); |
| 224 if (newBase == oldBase && newExtent == oldExtent) |
| 225 return; |
| 226 m_selection = SelectionInDOMTree::Builder() |
| 227 .setBaseAndExtent(newBase, newExtent) |
| 228 .build(); |
| 229 markCacheDirty(); |
| 230 } |
| 231 |
| 232 static Position updatePositionAfterAdoptingTextReplacement( |
| 233 const Position& position, |
| 234 CharacterData* node, |
| 235 unsigned offset, |
| 236 unsigned oldLength, |
| 237 unsigned newLength) { |
| 238 if (position.anchorNode() != node) |
| 239 return position; |
| 240 |
| 241 if (position.isBeforeAnchor()) { |
| 242 return updatePositionAfterAdoptingTextReplacement( |
| 243 Position(node, 0), node, offset, oldLength, newLength); |
| 244 } |
| 245 if (position.isAfterAnchor()) { |
| 246 return updatePositionAfterAdoptingTextReplacement( |
| 247 Position(node, oldLength), node, offset, oldLength, newLength); |
| 248 } |
| 249 |
| 250 // See: |
| 251 // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-
Mutation |
| 252 DCHECK_GE(position.offsetInContainerNode(), 0); |
| 253 unsigned positionOffset = |
| 254 static_cast<unsigned>(position.offsetInContainerNode()); |
| 255 // Replacing text can be viewed as a deletion followed by insertion. |
| 256 if (positionOffset >= offset && positionOffset <= offset + oldLength) |
| 257 positionOffset = offset; |
| 258 |
| 259 // Adjust the offset if the position is after the end of the deleted contents |
| 260 // (positionOffset > offset + oldLength) to avoid having a stale offset. |
| 261 if (positionOffset > offset + oldLength) |
| 262 positionOffset = positionOffset - oldLength + newLength; |
| 263 |
| 264 // Due to case folding |
| 265 // (http://unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), LayoutText |
| 266 // length may be different from Text length. A correct implementation would |
| 267 // translate the LayoutText offset to a Text offset; this is just a safety |
| 268 // precaution to avoid offset values that run off the end of the Text. |
| 269 if (positionOffset > node->length()) |
| 270 positionOffset = node->length(); |
| 271 |
| 272 return Position(node, positionOffset); |
| 273 } |
| 274 |
| 275 void SelectionEditor::didUpdateCharacterData(CharacterData* node, |
| 276 unsigned offset, |
| 277 unsigned oldLength, |
| 278 unsigned newLength) { |
| 279 // The fragment check is a performance optimization. See |
| 280 // http://trac.webkit.org/changeset/30062. |
| 281 if (m_selection.isNone() || !node || !node->isConnected()) { |
| 282 didFinishDOMMutation(); |
| 283 return; |
| 284 } |
| 285 const Position& newBase = updatePositionAfterAdoptingTextReplacement( |
| 286 m_selection.m_base, node, offset, oldLength, newLength); |
| 287 const Position& newExtent = updatePositionAfterAdoptingTextReplacement( |
| 288 m_selection.m_extent, node, offset, oldLength, newLength); |
| 289 didFinishTextChange(newBase, newExtent); |
| 290 } |
| 291 |
| 292 // TODO(yosin): We should introduce |Position(const Text&, int)| to avoid |
| 293 // |const_cast<Text*>|. |
| 294 static Position updatePostionAfterAdoptingTextNodesMerged( |
| 295 const Position& position, |
| 296 const Text& mergedNode, |
| 297 const NodeWithIndex& nodeToBeRemovedWithIndex, |
| 298 unsigned oldLength) { |
| 299 Node* const anchorNode = position.anchorNode(); |
| 300 const Node& nodeToBeRemoved = nodeToBeRemovedWithIndex.node(); |
| 301 switch (position.anchorType()) { |
| 302 case PositionAnchorType::BeforeChildren: |
| 303 case PositionAnchorType::AfterChildren: |
| 304 return position; |
| 305 case PositionAnchorType::BeforeAnchor: |
| 306 if (anchorNode == nodeToBeRemoved) |
| 307 return Position(const_cast<Text*>(&mergedNode), mergedNode.length()); |
| 308 return position; |
| 309 case PositionAnchorType::AfterAnchor: |
| 310 if (anchorNode == nodeToBeRemoved) |
| 311 return Position(const_cast<Text*>(&mergedNode), mergedNode.length()); |
| 312 if (anchorNode == mergedNode) |
| 313 return Position(const_cast<Text*>(&mergedNode), oldLength); |
| 314 return position; |
| 315 case PositionAnchorType::OffsetInAnchor: { |
| 316 const int offset = position.offsetInContainerNode(); |
| 317 if (anchorNode == nodeToBeRemoved) |
| 318 return Position(const_cast<Text*>(&mergedNode), oldLength + offset); |
| 319 if (anchorNode == nodeToBeRemoved.parentNode() && |
| 320 offset == nodeToBeRemovedWithIndex.index()) { |
| 321 return Position(const_cast<Text*>(&mergedNode), oldLength); |
| 322 } |
| 323 return position; |
| 324 } |
| 325 } |
| 326 NOTREACHED() << position; |
| 327 return position; |
| 328 } |
| 329 |
| 330 void SelectionEditor::didMergeTextNodes( |
| 331 const Text& mergedNode, |
| 332 const NodeWithIndex& nodeToBeRemovedWithIndex, |
| 333 unsigned oldLength) { |
| 334 if (m_selection.isNone()) { |
| 335 didFinishDOMMutation(); |
| 336 return; |
| 337 } |
| 338 const Position& newBase = updatePostionAfterAdoptingTextNodesMerged( |
| 339 m_selection.m_base, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
| 340 const Position& newExtent = updatePostionAfterAdoptingTextNodesMerged( |
| 341 m_selection.m_extent, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
| 342 didFinishTextChange(newBase, newExtent); |
| 343 } |
| 344 |
| 345 static Position updatePostionAfterAdoptingTextNodeSplit( |
| 346 const Position& position, |
| 347 const Text& oldNode) { |
| 348 if (!position.anchorNode() || position.anchorNode() != &oldNode || |
| 349 !position.isOffsetInAnchor()) |
| 350 return position; |
| 351 // See: |
| 352 // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-
Mutation |
| 353 DCHECK_GE(position.offsetInContainerNode(), 0); |
| 354 unsigned positionOffset = |
| 355 static_cast<unsigned>(position.offsetInContainerNode()); |
| 356 unsigned oldLength = oldNode.length(); |
| 357 if (positionOffset <= oldLength) |
| 358 return position; |
| 359 return Position(toText(oldNode.nextSibling()), positionOffset - oldLength); |
| 360 } |
| 361 |
| 362 void SelectionEditor::didSplitTextNode(const Text& oldNode) { |
| 363 if (m_selection.isNone() || !oldNode.isConnected()) { |
| 364 didFinishDOMMutation(); |
| 365 return; |
| 366 } |
| 367 const Position& newBase = |
| 368 updatePostionAfterAdoptingTextNodeSplit(m_selection.m_base, oldNode); |
| 369 const Position& newExtent = |
| 370 updatePostionAfterAdoptingTextNodeSplit(m_selection.m_extent, oldNode); |
| 371 didFinishTextChange(newBase, newExtent); |
| 372 } |
| 373 |
181 void SelectionEditor::resetLogicalRange() { | 374 void SelectionEditor::resetLogicalRange() { |
182 // Non-collapsed ranges are not allowed to start at the end of a line that | 375 // Non-collapsed ranges are not allowed to start at the end of a line that |
183 // is wrapped, they start at the beginning of the next line instead | 376 // is wrapped, they start at the beginning of the next line instead |
184 if (!m_logicalRange) | 377 if (!m_logicalRange) |
185 return; | 378 return; |
186 m_logicalRange->dispose(); | 379 m_logicalRange->dispose(); |
187 m_logicalRange = nullptr; | 380 m_logicalRange = nullptr; |
188 } | 381 } |
189 | 382 |
190 void SelectionEditor::setLogicalRange(Range* range) { | 383 void SelectionEditor::setLogicalRange(Range* range) { |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
253 visitor->trace(m_frame); | 446 visitor->trace(m_frame); |
254 visitor->trace(m_selection); | 447 visitor->trace(m_selection); |
255 visitor->trace(m_cachedVisibleSelectionInDOMTree); | 448 visitor->trace(m_cachedVisibleSelectionInDOMTree); |
256 visitor->trace(m_cachedVisibleSelectionInFlatTree); | 449 visitor->trace(m_cachedVisibleSelectionInFlatTree); |
257 visitor->trace(m_logicalRange); | 450 visitor->trace(m_logicalRange); |
258 visitor->trace(m_cachedRange); | 451 visitor->trace(m_cachedRange); |
259 SynchronousMutationObserver::trace(visitor); | 452 SynchronousMutationObserver::trace(visitor); |
260 } | 453 } |
261 | 454 |
262 } // namespace blink | 455 } // namespace blink |
OLD | NEW |