| 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 |
| 11 * documentation and/or other materials provided with the distribution. | 11 * documentation and/or other materials provided with the distribution. |
| 12 * | 12 * |
| 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY | 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR | 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 */ | 24 */ |
| 25 | 25 |
| 26 #include "core/editing/SelectionEditor.h" | 26 #include "core/editing/SelectionEditor.h" |
| 27 | 27 |
| 28 #include "core/dom/NodeWithIndex.h" |
| 29 #include "core/dom/Text.h" |
| 28 #include "core/editing/EditingUtilities.h" | 30 #include "core/editing/EditingUtilities.h" |
| 29 #include "core/editing/Editor.h" | 31 #include "core/editing/Editor.h" |
| 30 #include "core/editing/SelectionAdjuster.h" | 32 #include "core/editing/SelectionAdjuster.h" |
| 31 #include "core/frame/LocalFrame.h" | 33 #include "core/frame/LocalFrame.h" |
| 34 #include <iterator> |
| 35 #include <ostream> // NOLINT |
| 32 | 36 |
| 33 namespace blink { | 37 namespace blink { |
| 34 | 38 |
| 39 #define FOR_EACH_STATE(V) \ |
| 40 V(VisibleSelectionMayBeValid) \ |
| 41 V(DOMTreeIsChanged) \ |
| 42 V(DOMTreeIsChanging) \ |
| 43 V(SelectionInDOMTreeIsSource) |
| 44 |
| 45 enum class SelectionEditor::CacheState { |
| 46 #define V(name) name, |
| 47 FOR_EACH_STATE(V) |
| 48 #undef V |
| 49 }; |
| 50 |
| 51 std::ostream& operator<<(std::ostream& ostream, |
| 52 SelectionEditor::CacheState state) { |
| 53 static const char* names[] = { |
| 54 #define V(name) #name, |
| 55 FOR_EACH_STATE(V) |
| 56 #undef V |
| 57 }; |
| 58 const auto& it = std::begin(names) + static_cast<size_t>(state); |
| 59 if (it < std::begin(names) || it >= std::end(names)) |
| 60 return ostream << "CacheState::Bad(" << static_cast<int>(state) << ')'; |
| 61 return ostream << *it; |
| 62 } |
| 63 |
| 35 SelectionEditor::SelectionEditor(LocalFrame& frame) | 64 SelectionEditor::SelectionEditor(LocalFrame& frame) |
| 36 : m_frame(frame), m_observingVisibleSelection(false) { | 65 : m_frame(frame), m_cacheState(CacheState::VisibleSelectionMayBeValid) { |
| 37 clearVisibleSelection(); | 66 clearVisibleSelection(); |
| 38 } | 67 } |
| 39 | 68 |
| 40 SelectionEditor::~SelectionEditor() {} | 69 SelectionEditor::~SelectionEditor() {} |
| 41 | 70 |
| 42 void SelectionEditor::clearVisibleSelection() { | 71 void SelectionEditor::clearVisibleSelection() { |
| 43 m_selection = VisibleSelection(); | 72 m_selection = SelectionInDOMTree(); |
| 44 m_selectionInFlatTree = VisibleSelectionInFlatTree(); | 73 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 74 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 45 if (!shouldAlwaysUseDirectionalSelection()) | 75 if (!shouldAlwaysUseDirectionalSelection()) |
| 46 return; | 76 return; |
| 47 m_selection.setIsDirectional(true); | 77 m_selection.m_isDirectional = true; |
| 48 m_selectionInFlatTree.setIsDirectional(true); | |
| 49 } | 78 } |
| 50 | 79 |
| 51 void SelectionEditor::dispose() { | 80 void SelectionEditor::dispose() { |
| 52 resetLogicalRange(); | 81 resetLogicalRange(); |
| 53 clearVisibleSelection(); | 82 clearVisibleSelection(); |
| 54 } | 83 } |
| 55 | 84 |
| 56 const Document& SelectionEditor::document() const { | 85 Document& SelectionEditor::document() const { |
| 57 DCHECK(m_document); | 86 DCHECK(lifecycleContext()); |
| 58 return *m_document; | 87 return *lifecycleContext(); |
| 59 } | 88 } |
| 60 | 89 |
| 61 template <> | 90 const SelectionInDOMTree& SelectionEditor::selectionInDOMTree() const { |
| 62 const VisibleSelection& SelectionEditor::visibleSelection<EditingStrategy>() | 91 m_selection.assertValidFor(document()); |
| 92 return m_selection; |
| 93 } |
| 94 |
| 95 bool SelectionEditor::hasEditableStyle() const { |
| 96 return computeVisibleSelectionInDOMTree().hasEditableStyle(); |
| 97 } |
| 98 |
| 99 bool SelectionEditor::isContentEditable() const { |
| 100 return computeVisibleSelectionInDOMTree().isContentEditable(); |
| 101 } |
| 102 |
| 103 bool SelectionEditor::isContentRichlyEditable() const { |
| 104 return computeVisibleSelectionInDOMTree().isContentRichlyEditable(); |
| 105 } |
| 106 |
| 107 const VisibleSelection& SelectionEditor::computeVisibleSelectionInDOMTree() |
| 63 const { | 108 const { |
| 64 DCHECK_EQ(frame()->document(), document()); | 109 DCHECK_EQ(frame()->document(), document()); |
| 65 DCHECK_EQ(frame(), document().frame()); | 110 DCHECK_EQ(frame(), document().frame()); |
| 66 if (m_selection.isNone()) | 111 const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded(); |
| 67 return m_selection; | 112 if (m_cachedVisibleSelectionInDOMTree.isNone()) |
| 68 DCHECK_EQ(m_selection.base().document(), document()); | 113 return m_cachedVisibleSelectionInDOMTree; |
| 69 return m_selection; | 114 DCHECK_EQ(m_cachedVisibleSelectionInDOMTree.base().document(), document()); |
| 70 } | 115 return m_cachedVisibleSelectionInDOMTree; |
| 71 | 116 } |
| 72 template <> | 117 |
| 73 const VisibleSelectionInFlatTree& | 118 const VisibleSelectionInFlatTree& |
| 74 SelectionEditor::visibleSelection<EditingInFlatTreeStrategy>() const { | 119 SelectionEditor::computeVisibleSelectionInFlatTree() const { |
| 75 DCHECK_EQ(frame()->document(), document()); | 120 DCHECK_EQ(frame()->document(), document()); |
| 76 DCHECK_EQ(frame(), document().frame()); | 121 DCHECK_EQ(frame(), document().frame()); |
| 77 if (m_selectionInFlatTree.isNone()) | 122 const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded(); |
| 78 return m_selectionInFlatTree; | 123 if (m_cachedVisibleSelectionInFlatTree.isNone()) |
| 79 DCHECK_EQ(m_selectionInFlatTree.base().document(), document()); | 124 return m_cachedVisibleSelectionInFlatTree; |
| 80 return m_selectionInFlatTree; | 125 DCHECK_EQ(m_cachedVisibleSelectionInFlatTree.base().document(), document()); |
| 81 } | 126 return m_cachedVisibleSelectionInFlatTree; |
| 82 | 127 } |
| 83 void SelectionEditor::setVisibleSelection( | 128 |
| 84 const VisibleSelection& newSelection, | 129 void SelectionEditor::setSelection(const SelectionInDOMTree& newSelection) { |
| 85 FrameSelection::SetSelectionOptions options) { | 130 m_selection.assertValidFor(document()); |
| 86 DCHECK(newSelection.isValidFor(document())) << newSelection; | 131 newSelection.assertValidFor(document()); |
| 132 if (m_selection == newSelection) |
| 133 return; |
| 87 resetLogicalRange(); | 134 resetLogicalRange(); |
| 135 m_cacheState = CacheState::SelectionInDOMTreeIsSource; |
| 136 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 137 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 138 |
| 88 m_selection = newSelection; | 139 m_selection = newSelection; |
| 89 if (options & FrameSelection::DoNotAdjustInFlatTree) { | 140 |
| 90 m_selectionInFlatTree.setWithoutValidation( | 141 m_domTreeVersion = document().domTreeVersion(); |
| 91 toPositionInFlatTree(m_selection.base()), | 142 m_styleVersion = document().styleVersion(); |
| 92 toPositionInFlatTree(m_selection.extent())); | 143 } |
| 93 return; | 144 |
| 94 } | 145 void SelectionEditor::didChangeAttribute(const Element&) { |
| 95 | 146 didFinishDOMMutation(); |
| 96 SelectionAdjuster::adjustSelectionInFlatTree(&m_selectionInFlatTree, | 147 } |
| 97 m_selection); | 148 |
| 98 } | 149 void SelectionEditor::didChangeChildren(const ContainerNode&) { |
| 99 | 150 didFinishDOMMutation(); |
| 100 void SelectionEditor::setVisibleSelection( | 151 } |
| 101 const VisibleSelectionInFlatTree& newSelection, | 152 |
| 102 FrameSelection::SetSelectionOptions options) { | 153 void SelectionEditor::didMoveTreeToNewDocument() { |
| 103 DCHECK(newSelection.isValidFor(document())) << newSelection; | 154 didFinishDOMMutation(); |
| 104 DCHECK(!(options & FrameSelection::DoNotAdjustInFlatTree)); | 155 } |
| 105 resetLogicalRange(); | 156 |
| 106 m_selectionInFlatTree = newSelection; | 157 static Position computePositionForChildrenRemoval(const Position& position, |
| 107 SelectionAdjuster::adjustSelectionInDOMTree(&m_selection, | 158 ContainerNode& container) { |
| 108 m_selectionInFlatTree); | 159 Node* node = position.computeContainerNode(); |
| 109 } | 160 if (container.containsIncludingHostElements(*node)) |
| 110 | 161 return Position::firstPositionInNode(&container); |
| 111 void SelectionEditor::setWithoutValidation(const Position& base, | 162 return position; |
| 112 const Position& extent) { | 163 } |
| 113 resetLogicalRange(); | 164 |
| 114 if (base.isNotNull()) | 165 void SelectionEditor::nodeChildrenWillBeRemoved(ContainerNode& container) { |
| 115 DCHECK_EQ(base.document(), document()); | 166 if (m_selection.isNone()) |
| 116 if (extent.isNotNull()) | 167 return; |
| 117 DCHECK_EQ(extent.document(), document()); | 168 const Position oldBase = m_selection.m_base; |
| 118 m_selection.setWithoutValidation(base, extent); | 169 const Position oldExtent = m_selection.m_extent; |
| 119 m_selectionInFlatTree.setWithoutValidation(toPositionInFlatTree(base), | 170 const Position& newBase = |
| 120 toPositionInFlatTree(extent)); | 171 computePositionForChildrenRemoval(oldBase, container); |
| 172 const Position& newExtent = |
| 173 computePositionForChildrenRemoval(oldExtent, container); |
| 174 if (newBase == oldBase && newExtent == oldExtent) |
| 175 return; |
| 176 m_selection = SelectionInDOMTree::Builder() |
| 177 .setBaseAndExtent(newBase, newExtent) |
| 178 .build(); |
| 179 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 180 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 181 } |
| 182 |
| 183 static Position positionAfterPreviousSiblingOf(const Node& node) { |
| 184 Node* const previousSibling = NodeTraversal::previousSibling(node); |
| 185 if (!previousSibling) { |
| 186 // Returns |BeforeChildren| of a parent of |node| since |node| is the |
| 187 // first child. |
| 188 return Position::firstPositionInNode(NodeTraversal::parent(node)); |
| 189 } |
| 190 #if 0 |
| 191 // When enable this code fragment, |
| 192 // "editing/selection/mouse/drag-user-select-all-textarea.html" is failed. |
| 193 // |
| 194 // When disable this code fragment, |
| 195 // "editing/selection/selection-after-text-normalize.html" is failed. |
| 196 if (previousSibling->isCharacterDataNode()) { |
| 197 return Position(previousSibling, |
| 198 toCharacterData(previousSibling)->length()); |
| 199 } |
| 200 #endif |
| 201 return Position::afterNode(previousSibling); |
| 202 } |
| 203 |
| 204 static Position computePositionForNodeRemoval(const Position& position, |
| 205 Node& nodeToBeRemoved) { |
| 206 Node* const anchorNode = position.anchorNode(); |
| 207 if (anchorNode == nodeToBeRemoved) |
| 208 return positionAfterPreviousSiblingOf(nodeToBeRemoved); |
| 209 if (position.isOffsetInAnchor() && |
| 210 anchorNode == nodeToBeRemoved.parentNode()) { |
| 211 const int positionOffset = position.offsetInContainerNode(); |
| 212 if (positionOffset == 0) |
| 213 return position; |
| 214 // |nodeToBeRemoved| is sibling or itself of |position|. |
| 215 int offset = 0; |
| 216 for (Node& child : |
| 217 NodeTraversal::childrenOf(*nodeToBeRemoved.parentNode())) { |
| 218 if (nodeToBeRemoved == child) { |
| 219 // |position| is after |nodeToBeRemoved|. |
| 220 return Position(anchorNode, positionOffset - 1); |
| 221 } |
| 222 if (positionOffset == offset) { |
| 223 // |position| is before |nodeToBeRemoved|. |
| 224 return position; |
| 225 } |
| 226 ++offset; |
| 227 } |
| 228 NOTREACHED() << position << " should be handled."; |
| 229 } |
| 230 if (!nodeToBeRemoved.isContainerNode()) |
| 231 return position; |
| 232 if (toContainerNode(nodeToBeRemoved) |
| 233 .containsIncludingHostElements(*anchorNode)) |
| 234 return positionAfterPreviousSiblingOf(nodeToBeRemoved); |
| 235 return position; |
| 236 } |
| 237 |
| 238 static Position updatePositionAfterAdoptingTextReplacement( |
| 239 const Position& position, |
| 240 CharacterData* node, |
| 241 unsigned offset, |
| 242 unsigned oldLength, |
| 243 unsigned newLength) { |
| 244 if (position.anchorNode() != node) |
| 245 return position; |
| 246 |
| 247 if (position.isBeforeAnchor()) { |
| 248 return updatePositionAfterAdoptingTextReplacement( |
| 249 Position(node, 0), node, offset, oldLength, newLength); |
| 250 } |
| 251 if (position.isAfterAnchor()) { |
| 252 return updatePositionAfterAdoptingTextReplacement( |
| 253 Position(node, oldLength), node, offset, oldLength, newLength); |
| 254 } |
| 255 |
| 256 // See: |
| 257 // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-
Mutation |
| 258 DCHECK_GE(position.offsetInContainerNode(), 0); |
| 259 unsigned positionOffset = |
| 260 static_cast<unsigned>(position.offsetInContainerNode()); |
| 261 // Replacing text can be viewed as a deletion followed by insertion. |
| 262 if (positionOffset >= offset && positionOffset <= offset + oldLength) |
| 263 positionOffset = offset; |
| 264 |
| 265 // Adjust the offset if the position is after the end of the deleted contents |
| 266 // (positionOffset > offset + oldLength) to avoid having a stale offset. |
| 267 if (positionOffset > offset + oldLength) |
| 268 positionOffset = positionOffset - oldLength + newLength; |
| 269 |
| 270 // Due to case folding |
| 271 // (http://unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), |
| 272 // LayoutText length may be different from Text length. A correct |
| 273 // implementation |
| 274 // would translate the LayoutText offset to a Text offset; this is just a |
| 275 // safety |
| 276 // precaution to avoid offset values that run off the end of the Text. |
| 277 if (positionOffset > node->length()) |
| 278 positionOffset = node->length(); |
| 279 |
| 280 return Position(node, positionOffset); |
| 281 } |
| 282 |
| 283 void SelectionEditor::didUpdateCharacterData(CharacterData* node, |
| 284 unsigned offset, |
| 285 unsigned oldLength, |
| 286 unsigned newLength) { |
| 287 // The fragment check is a performance optimization. See |
| 288 // http://trac.webkit.org/changeset/30062. |
| 289 if (m_selection.isNone() || !node || !node->isConnected()) { |
| 290 didFinishDOMMutation(); |
| 291 return; |
| 292 } |
| 293 const Position& newBase = updatePositionAfterAdoptingTextReplacement( |
| 294 m_selection.m_base, node, offset, oldLength, newLength); |
| 295 const Position& newExtent = updatePositionAfterAdoptingTextReplacement( |
| 296 m_selection.m_extent, node, offset, oldLength, newLength); |
| 297 didFinishTextChange(newBase, newExtent); |
| 298 } |
| 299 |
| 300 static Position updatePostionAfterAdoptingTextNodesMerged( |
| 301 const Position& position, |
| 302 const Text& mergedNode, |
| 303 const NodeWithIndex& nodeToBeRemovedWithIndex, |
| 304 unsigned oldLength) { |
| 305 Node* const anchorNode = position.anchorNode(); |
| 306 const Node& nodeToBeRemoved = nodeToBeRemovedWithIndex.node(); |
| 307 switch (position.anchorType()) { |
| 308 case PositionAnchorType::BeforeChildren: |
| 309 case PositionAnchorType::AfterChildren: |
| 310 return position; |
| 311 case PositionAnchorType::BeforeAnchor: |
| 312 if (anchorNode == nodeToBeRemoved) |
| 313 return Position(mergedNode, mergedNode.length()); |
| 314 return position; |
| 315 case PositionAnchorType::AfterAnchor: |
| 316 if (anchorNode == nodeToBeRemoved) |
| 317 return Position(mergedNode, mergedNode.length()); |
| 318 if (anchorNode == mergedNode) |
| 319 return Position(mergedNode, oldLength); |
| 320 return position; |
| 321 case PositionAnchorType::OffsetInAnchor: { |
| 322 const int offset = position.offsetInContainerNode(); |
| 323 if (anchorNode == nodeToBeRemoved) |
| 324 return Position(mergedNode, oldLength + offset); |
| 325 if (anchorNode == nodeToBeRemoved.parentNode() && |
| 326 offset == nodeToBeRemovedWithIndex.index()) { |
| 327 return Position(mergedNode, oldLength); |
| 328 } |
| 329 return position; |
| 330 } |
| 331 } |
| 332 NOTREACHED() << position; |
| 333 return position; |
| 334 } |
| 335 |
| 336 void SelectionEditor::didMergeTextNodes( |
| 337 const Text& mergedNode, |
| 338 const NodeWithIndex& nodeToBeRemovedWithIndex, |
| 339 unsigned oldLength) { |
| 340 if (m_selection.isNone()) { |
| 341 didFinishDOMMutation(); |
| 342 return; |
| 343 } |
| 344 const Position& newBase = updatePostionAfterAdoptingTextNodesMerged( |
| 345 m_selection.m_base, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
| 346 const Position& newExtent = updatePostionAfterAdoptingTextNodesMerged( |
| 347 m_selection.m_extent, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
| 348 didFinishTextChange(newBase, newExtent); |
| 349 } |
| 350 |
| 351 static Position updatePostionAfterAdoptingTextNodeSplit( |
| 352 const Position& position, |
| 353 const Text& oldNode) { |
| 354 if (position.anchorNode() != &oldNode || !position.isOffsetInAnchor()) |
| 355 return position; |
| 356 // See: |
| 357 // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-
Mutation |
| 358 DCHECK_GE(position.offsetInContainerNode(), 0); |
| 359 unsigned positionOffset = |
| 360 static_cast<unsigned>(position.offsetInContainerNode()); |
| 361 unsigned oldLength = oldNode.length(); |
| 362 if (positionOffset <= oldLength) |
| 363 return position; |
| 364 return Position(toText(oldNode.nextSibling()), positionOffset - oldLength); |
| 365 } |
| 366 |
| 367 void SelectionEditor::didSplitTextNode(const Text& oldNode) { |
| 368 if (m_selection.isNone() || !oldNode.isConnected()) { |
| 369 didFinishDOMMutation(); |
| 370 return; |
| 371 } |
| 372 const Position& newBase = |
| 373 updatePostionAfterAdoptingTextNodeSplit(m_selection.m_base, oldNode); |
| 374 const Position& newExtent = |
| 375 updatePostionAfterAdoptingTextNodeSplit(m_selection.m_extent, oldNode); |
| 376 didFinishTextChange(newBase, newExtent); |
| 377 } |
| 378 |
| 379 void SelectionEditor::didFinishTextChange(const Position& newBase, |
| 380 const Position& newExtent) { |
| 381 if (newBase != m_selection.m_base || newExtent != m_selection.m_extent) { |
| 382 m_selection.m_base = newBase; |
| 383 m_selection.m_extent = newExtent; |
| 384 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 385 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 386 } |
| 387 didFinishDOMMutation(); |
| 388 } |
| 389 |
| 390 void SelectionEditor::didFinishDOMMutation() { |
| 391 m_domTreeVersion = document().domTreeVersion(); |
| 392 #if ENABLE(ASSERT) |
| 393 m_selection.m_domTreeVersion = m_domTreeVersion; |
| 394 #endif |
| 395 m_cacheState = CacheState::DOMTreeIsChanged; |
| 396 // TODO(yosin): Should we notify selection change? |
| 397 m_selection.assertValidFor(document()); |
| 398 } |
| 399 |
| 400 void SelectionEditor::nodeWillBeRemoved(Node& nodeToBeRemoved) { |
| 401 if (m_selection.isNone()) |
| 402 return; |
| 403 const Position oldBase = m_selection.m_base; |
| 404 const Position oldExtent = m_selection.m_extent; |
| 405 const Position& newBase = |
| 406 computePositionForNodeRemoval(oldBase, nodeToBeRemoved); |
| 407 const Position& newExtent = |
| 408 computePositionForNodeRemoval(oldExtent, nodeToBeRemoved); |
| 409 if (newBase == oldBase && newExtent == oldExtent) |
| 410 return; |
| 411 m_cacheState = CacheState::DOMTreeIsChanging; |
| 412 m_selection = SelectionInDOMTree::Builder() |
| 413 .setBaseAndExtent(newBase, newExtent) |
| 414 .build(); |
| 415 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 416 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 121 } | 417 } |
| 122 | 418 |
| 123 void SelectionEditor::documentAttached(Document* document) { | 419 void SelectionEditor::documentAttached(Document* document) { |
| 124 DCHECK(document); | 420 DCHECK(document); |
| 125 DCHECK(!m_document) << m_document; | 421 DCHECK(!lifecycleContext()) << lifecycleContext(); |
| 126 m_document = document; | 422 m_domTreeVersion = document->domTreeVersion(); |
| 127 } | 423 m_styleVersion = static_cast<uint64_t>(-1); |
| 128 | 424 clearVisibleSelection(); |
| 129 void SelectionEditor::documentDetached(const Document& document) { | 425 setContext(document); |
| 130 DCHECK_EQ(m_document, &document); | 426 } |
| 427 |
| 428 void SelectionEditor::contextDestroyed(Document*) { |
| 131 dispose(); | 429 dispose(); |
| 132 m_document = nullptr; | 430 m_domTreeVersion = 0; |
| 431 m_styleVersion = static_cast<uint64_t>(-1); |
| 432 m_selection = SelectionInDOMTree(); |
| 433 m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
| 434 m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
| 133 } | 435 } |
| 134 | 436 |
| 135 void SelectionEditor::resetLogicalRange() { | 437 void SelectionEditor::resetLogicalRange() { |
| 136 // Non-collapsed ranges are not allowed to start at the end of a line that | 438 // Non-collapsed ranges are not allowed to start at the end of a line that |
| 137 // is wrapped, they start at the beginning of the next line instead | 439 // is wrapped, they start at the beginning of the next line instead |
| 138 if (!m_logicalRange) | 440 if (!m_logicalRange) |
| 139 return; | 441 return; |
| 140 m_logicalRange->dispose(); | 442 m_logicalRange->dispose(); |
| 141 m_logicalRange = nullptr; | 443 m_logicalRange = nullptr; |
| 142 } | 444 } |
| 143 | 445 |
| 144 void SelectionEditor::setLogicalRange(Range* range) { | 446 void SelectionEditor::setLogicalRange(Range* range) { |
| 145 DCHECK_EQ(range->ownerDocument(), document()); | 447 DCHECK_EQ(range->ownerDocument(), document()); |
| 146 DCHECK(!m_logicalRange) << "A logical range should be one."; | 448 DCHECK(!m_logicalRange) << "A logical range should be one."; |
| 147 m_logicalRange = range; | 449 m_logicalRange = range; |
| 148 } | 450 } |
| 149 | 451 |
| 150 Range* SelectionEditor::firstRange() const { | 452 Range* SelectionEditor::firstRange() const { |
| 151 if (m_logicalRange) | 453 if (m_logicalRange) |
| 152 return m_logicalRange->cloneRange(); | 454 return m_logicalRange->cloneRange(); |
| 153 return firstRangeOf(m_selection); | 455 return firstRangeOf(computeVisibleSelectionInDOMTree()); |
| 154 } | 456 } |
| 155 | 457 |
| 156 bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const { | 458 bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const { |
| 157 return frame()->editor().behavior().shouldConsiderSelectionAsDirectional(); | 459 return frame()->editor().behavior().shouldConsiderSelectionAsDirectional(); |
| 158 } | 460 } |
| 159 | 461 |
| 160 void SelectionEditor::updateIfNeeded() { | 462 void SelectionEditor::updateIfNeeded() { |
| 161 DCHECK(m_selection.isValidFor(document())) << m_selection; | 463 // TODO(yosin): We should unify |SelectionEditor::updateIfNeeded()| and |
| 162 DCHECK(m_selectionInFlatTree.isValidFor(document())) << m_selection; | 464 // |updateCachedVisibleSelectionIfNeeded()| |
| 163 m_selection.updateIfNeeded(); | 465 updateCachedVisibleSelectionIfNeeded(); |
| 164 m_selectionInFlatTree.updateIfNeeded(); | 466 } |
| 467 |
| 468 class SelectionEditor::CacheUpdateScope { |
| 469 STACK_ALLOCATED(); |
| 470 |
| 471 public: |
| 472 CacheUpdateScope(SelectionEditor*); |
| 473 ~CacheUpdateScope(); |
| 474 |
| 475 private: |
| 476 DEFINE_INLINE_TRACE() { visitor->trace(m_editor); } |
| 477 |
| 478 Member<SelectionEditor> m_editor; |
| 479 DISALLOW_COPY_AND_ASSIGN(CacheUpdateScope); |
| 480 }; |
| 481 |
| 482 SelectionEditor::CacheUpdateScope::CacheUpdateScope(SelectionEditor* editor) |
| 483 : m_editor(editor) { |
| 484 m_editor->m_isUpdatingCachedSelection = true; |
| 485 } |
| 486 |
| 487 SelectionEditor::CacheUpdateScope::~CacheUpdateScope() { |
| 488 m_editor->m_styleVersion = m_editor->document().styleVersion(); |
| 489 m_editor->m_cacheState = CacheState::VisibleSelectionMayBeValid; |
| 490 m_editor->m_isUpdatingCachedSelection = false; |
| 491 } |
| 492 |
| 493 bool SelectionEditor::needsUpdateVisibleSelection() const { |
| 494 DCHECK_EQ(m_domTreeVersion, document().domTreeVersion()); |
| 495 if (m_cacheState != CacheState::VisibleSelectionMayBeValid) |
| 496 return true; |
| 497 return m_styleVersion != document().styleVersion(); |
| 498 } |
| 499 |
| 500 void SelectionEditor::updateCachedVisibleSelectionIfNeeded() { |
| 501 DCHECK(!m_isUpdatingCachedSelection) |
| 502 << "Recursive update should not be happened."; |
| 503 m_selection.assertValidFor(document()); |
| 504 |
| 505 // We should use |DCHECK(!document().needsLayoutTreeUpdate())| but following |
| 506 // tests are failed: |
| 507 // - media/track/css-cue-for-video-in-shadow-2.html |
| 508 // - media/track/css-cue-for-video-in-shadow.html |
| 509 // - media/track/cue-style-invalidation.html |
| 510 // - media/track/opera/track/webvtt/parsing/001.html |
| 511 // - media/track/text-track-selection-menu-add-track.html |
| 512 // - media/track/track-css-all-cues.html |
| 513 // - media/track/track-css-cue-lifetime.html [ Crash |
| 514 // Note: Since we |FrameCaret::updateApperance()| is called from |
| 515 // |FrameView::performPostLayoutTasks()|, we check lifecycle against |
| 516 // |AfterPerformLayout| instead of |LayoutClean|. |
| 517 DCHECK_GE(document().lifecycle().state(), |
| 518 DocumentLifecycle::AfterPerformLayout); |
| 519 if (!needsUpdateVisibleSelection()) |
| 520 return; |
| 521 |
| 522 CacheUpdateScope updatingCache(this); |
| 523 switch (m_cacheState) { |
| 524 case CacheState::VisibleSelectionMayBeValid: |
| 525 // TDOO(yosin): We should study why |
| 526 // createVisibleSelection(m_cachedVisibleSelectionInDOMTree.asSelection()) |
| 527 // not |
| 528 // work. |
| 529 m_cachedVisibleSelectionInDOMTree.validate( |
| 530 m_cachedVisibleSelectionInDOMTree.m_granularity); |
| 531 m_cachedVisibleSelectionInFlatTree.validate( |
| 532 m_cachedVisibleSelectionInFlatTree.m_granularity); |
| 533 return; |
| 534 case CacheState::DOMTreeIsChanged: |
| 535 case CacheState::SelectionInDOMTreeIsSource: { |
| 536 // TODO(yosin): We should get rid of |adjustSelectionInFlatTree| in |
| 537 // |SelectionAdjuster|. |
| 538 m_cachedVisibleSelectionInDOMTree = createVisibleSelection(m_selection); |
| 539 m_cachedVisibleSelectionInFlatTree = createVisibleSelection( |
| 540 SelectionInFlatTree::Builder() |
| 541 .setBaseAndExtent(toPositionInFlatTree(m_selection.base()), |
| 542 toPositionInFlatTree(m_selection.extent())) |
| 543 .setAffinity(m_selection.affinity()) |
| 544 .setHasTrailingWhitespace(m_selection.hasTrailingWhitespace()) |
| 545 .setGranularity(m_selection.granularity()) |
| 546 .setIsDirectional(m_selection.isDirectional()) |
| 547 .build()); |
| 548 return; |
| 549 } |
| 550 case CacheState::DOMTreeIsChanging: |
| 551 NOTREACHED() << "We should not update selection during DOM mutation"; |
| 552 return; |
| 553 } |
| 554 NOTREACHED() << "We should handle " << m_cacheState; |
| 165 } | 555 } |
| 166 | 556 |
| 167 DEFINE_TRACE(SelectionEditor) { | 557 DEFINE_TRACE(SelectionEditor) { |
| 168 visitor->trace(m_document); | |
| 169 visitor->trace(m_frame); | 558 visitor->trace(m_frame); |
| 170 visitor->trace(m_selection); | 559 visitor->trace(m_selection); |
| 171 visitor->trace(m_selectionInFlatTree); | 560 visitor->trace(m_cachedVisibleSelectionInDOMTree); |
| 561 visitor->trace(m_cachedVisibleSelectionInFlatTree); |
| 172 visitor->trace(m_logicalRange); | 562 visitor->trace(m_logicalRange); |
| 563 SynchronousMutationObserver::trace(visitor); |
| 173 } | 564 } |
| 174 | 565 |
| 175 } // namespace blink | 566 } // namespace blink |
| OLD | NEW |