| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. | 2 * Copyright (C) 2005 Apple Computer, 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 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 m_deleteIntoBlockquoteStyle(nullptr) {} | 111 m_deleteIntoBlockquoteStyle(nullptr) {} |
| 112 | 112 |
| 113 void DeleteSelectionCommand::initializeStartEnd(Position& start, | 113 void DeleteSelectionCommand::initializeStartEnd(Position& start, |
| 114 Position& end) { | 114 Position& end) { |
| 115 HTMLElement* startSpecialContainer = nullptr; | 115 HTMLElement* startSpecialContainer = nullptr; |
| 116 HTMLElement* endSpecialContainer = nullptr; | 116 HTMLElement* endSpecialContainer = nullptr; |
| 117 | 117 |
| 118 start = m_selectionToDelete.start(); | 118 start = m_selectionToDelete.start(); |
| 119 end = m_selectionToDelete.end(); | 119 end = m_selectionToDelete.end(); |
| 120 | 120 |
| 121 // For HRs, we'll get a position at (HR,1) when hitting delete from the beginn
ing of the previous line, or (HR,0) when forward deleting, | 121 // For HRs, we'll get a position at (HR,1) when hitting delete from the |
| 122 // but in these cases, we want to delete it, so manually expand the selection | 122 // beginning of the previous line, or (HR,0) when forward deleting, but in |
| 123 // these cases, we want to delete it, so manually expand the selection |
| 123 if (isHTMLHRElement(*start.anchorNode())) | 124 if (isHTMLHRElement(*start.anchorNode())) |
| 124 start = Position::beforeNode(start.anchorNode()); | 125 start = Position::beforeNode(start.anchorNode()); |
| 125 else if (isHTMLHRElement(*end.anchorNode())) | 126 else if (isHTMLHRElement(*end.anchorNode())) |
| 126 end = Position::afterNode(end.anchorNode()); | 127 end = Position::afterNode(end.anchorNode()); |
| 127 | 128 |
| 128 // FIXME: This is only used so that moveParagraphs can avoid the bugs in speci
al element expansion. | 129 // FIXME: This is only used so that moveParagraphs can avoid the bugs in |
| 130 // special element expansion. |
| 129 if (!m_expandForSpecialElements) | 131 if (!m_expandForSpecialElements) |
| 130 return; | 132 return; |
| 131 | 133 |
| 132 while (1) { | 134 while (1) { |
| 133 startSpecialContainer = 0; | 135 startSpecialContainer = 0; |
| 134 endSpecialContainer = 0; | 136 endSpecialContainer = 0; |
| 135 | 137 |
| 136 Position s = | 138 Position s = |
| 137 positionBeforeContainingSpecialElement(start, &startSpecialContainer); | 139 positionBeforeContainingSpecialElement(start, &startSpecialContainer); |
| 138 Position e = | 140 Position e = |
| 139 positionAfterContainingSpecialElement(end, &endSpecialContainer); | 141 positionAfterContainingSpecialElement(end, &endSpecialContainer); |
| 140 | 142 |
| 141 if (!startSpecialContainer && !endSpecialContainer) | 143 if (!startSpecialContainer && !endSpecialContainer) |
| 142 break; | 144 break; |
| 143 | 145 |
| 144 if (createVisiblePositionDeprecated(start).deepEquivalent() != | 146 if (createVisiblePositionDeprecated(start).deepEquivalent() != |
| 145 m_selectionToDelete.visibleStartDeprecated().deepEquivalent() || | 147 m_selectionToDelete.visibleStartDeprecated().deepEquivalent() || |
| 146 createVisiblePositionDeprecated(end).deepEquivalent() != | 148 createVisiblePositionDeprecated(end).deepEquivalent() != |
| 147 m_selectionToDelete.visibleEndDeprecated().deepEquivalent()) | 149 m_selectionToDelete.visibleEndDeprecated().deepEquivalent()) |
| 148 break; | 150 break; |
| 149 | 151 |
| 150 // If we're going to expand to include the startSpecialContainer, it must be
fully selected. | 152 // If we're going to expand to include the startSpecialContainer, it must be |
| 153 // fully selected. |
| 151 if (startSpecialContainer && !endSpecialContainer && | 154 if (startSpecialContainer && !endSpecialContainer && |
| 152 comparePositions(Position::inParentAfterNode(*startSpecialContainer), | 155 comparePositions(Position::inParentAfterNode(*startSpecialContainer), |
| 153 end) > -1) | 156 end) > -1) |
| 154 break; | 157 break; |
| 155 | 158 |
| 156 // If we're going to expand to include the endSpecialContainer, it must be f
ully selected. | 159 // If we're going to expand to include the endSpecialContainer, it must be |
| 160 // fully selected. |
| 157 if (endSpecialContainer && !startSpecialContainer && | 161 if (endSpecialContainer && !startSpecialContainer && |
| 158 comparePositions( | 162 comparePositions( |
| 159 start, Position::inParentBeforeNode(*endSpecialContainer)) > -1) | 163 start, Position::inParentBeforeNode(*endSpecialContainer)) > -1) |
| 160 break; | 164 break; |
| 161 | 165 |
| 162 if (startSpecialContainer && | 166 if (startSpecialContainer && |
| 163 startSpecialContainer->isDescendantOf(endSpecialContainer)) { | 167 startSpecialContainer->isDescendantOf(endSpecialContainer)) { |
| 164 // Don't adjust the end yet, it is the end of a special element that conta
ins the start | 168 // Don't adjust the end yet, it is the end of a special element that |
| 165 // special element (which may or may not be fully selected). | 169 // contains the start special element (which may or may not be fully |
| 170 // selected). |
| 166 start = s; | 171 start = s; |
| 167 } else if (endSpecialContainer && | 172 } else if (endSpecialContainer && |
| 168 endSpecialContainer->isDescendantOf(startSpecialContainer)) { | 173 endSpecialContainer->isDescendantOf(startSpecialContainer)) { |
| 169 // Don't adjust the start yet, it is the start of a special element that c
ontains the end | 174 // Don't adjust the start yet, it is the start of a special element that |
| 170 // special element (which may or may not be fully selected). | 175 // contains the end special element (which may or may not be fully |
| 176 // selected). |
| 171 end = e; | 177 end = e; |
| 172 } else { | 178 } else { |
| 173 start = s; | 179 start = s; |
| 174 end = e; | 180 end = e; |
| 175 } | 181 } |
| 176 } | 182 } |
| 177 } | 183 } |
| 178 | 184 |
| 179 void DeleteSelectionCommand::setStartingSelectionOnSmartDelete( | 185 void DeleteSelectionCommand::setStartingSelectionOnSmartDelete( |
| 180 const Position& start, | 186 const Position& start, |
| (...skipping 30 matching lines...) Expand all Loading... |
| 211 | 217 |
| 212 m_startRoot = rootEditableElementOf(start); | 218 m_startRoot = rootEditableElementOf(start); |
| 213 m_endRoot = rootEditableElementOf(end); | 219 m_endRoot = rootEditableElementOf(end); |
| 214 | 220 |
| 215 m_startTableRow = | 221 m_startTableRow = |
| 216 toHTMLTableRowElement(enclosingNodeOfType(start, &isHTMLTableRowElement)); | 222 toHTMLTableRowElement(enclosingNodeOfType(start, &isHTMLTableRowElement)); |
| 217 m_endTableRow = | 223 m_endTableRow = |
| 218 toHTMLTableRowElement(enclosingNodeOfType(end, &isHTMLTableRowElement)); | 224 toHTMLTableRowElement(enclosingNodeOfType(end, &isHTMLTableRowElement)); |
| 219 | 225 |
| 220 // Don't move content out of a table cell. | 226 // Don't move content out of a table cell. |
| 221 // If the cell is non-editable, enclosingNodeOfType won't return it by default
, so | 227 // If the cell is non-editable, enclosingNodeOfType won't return it by |
| 222 // tell that function that we don't care if it returns non-editable nodes. | 228 // default, so tell that function that we don't care if it returns |
| 229 // non-editable nodes. |
| 223 Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, | 230 Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, |
| 224 CanCrossEditingBoundary); | 231 CanCrossEditingBoundary); |
| 225 Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, | 232 Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, |
| 226 CanCrossEditingBoundary); | 233 CanCrossEditingBoundary); |
| 227 // FIXME: This isn't right. A borderless table with two rows and a single col
umn would appear as two paragraphs. | 234 // FIXME: This isn't right. A borderless table with two rows and a single |
| 235 // column would appear as two paragraphs. |
| 228 if (endCell && endCell != startCell) | 236 if (endCell && endCell != startCell) |
| 229 m_mergeBlocksAfterDelete = false; | 237 m_mergeBlocksAfterDelete = false; |
| 230 | 238 |
| 231 // Usually the start and the end of the selection to delete are pulled togethe
r as a result of the deletion. | 239 // Usually the start and the end of the selection to delete are pulled |
| 232 // Sometimes they aren't (like when no merge is requested), so we must choose
one position to hold the caret | 240 // together as a result of the deletion. Sometimes they aren't (like when no |
| 241 // merge is requested), so we must choose one position to hold the caret |
| 233 // and receive the placeholder after deletion. | 242 // and receive the placeholder after deletion. |
| 234 VisiblePosition visibleEnd = createVisiblePositionDeprecated(m_downstreamEnd); | 243 VisiblePosition visibleEnd = createVisiblePositionDeprecated(m_downstreamEnd); |
| 235 if (m_mergeBlocksAfterDelete && !isEndOfParagraphDeprecated(visibleEnd)) | 244 if (m_mergeBlocksAfterDelete && !isEndOfParagraphDeprecated(visibleEnd)) |
| 236 m_endingPosition = m_downstreamEnd; | 245 m_endingPosition = m_downstreamEnd; |
| 237 else | 246 else |
| 238 m_endingPosition = m_downstreamStart; | 247 m_endingPosition = m_downstreamStart; |
| 239 | 248 |
| 240 // We don't want to merge into a block if it will mean changing the quote leve
l of content after deleting | 249 // We don't want to merge into a block if it will mean changing the quote |
| 241 // selections that contain a whole number paragraphs plus a line break, since
it is unclear to most users | 250 // level of content after deleting selections that contain a whole number |
| 242 // that such a selection actually ends at the start of the next paragraph. Thi
s matches TextEdit behavior | 251 // paragraphs plus a line break, since it is unclear to most users that such a |
| 243 // for indented paragraphs. | 252 // selection actually ends at the start of the next paragraph. This matches |
| 244 // Only apply this rule if the endingSelection is a range selection. If it is
a caret, then other operations have created | 253 // TextEdit behavior for indented paragraphs. |
| 245 // the selection we're deleting (like the process of creating a selection to d
elete during a backspace), and the user isn't in the situation described above. | 254 // Only apply this rule if the endingSelection is a range selection. If it is |
| 255 // a caret, then other operations have created the selection we're deleting |
| 256 // (like the process of creating a selection to delete during a backspace), |
| 257 // and the user isn't in the situation described above. |
| 246 if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) && | 258 if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) && |
| 247 isStartOfParagraphDeprecated(visibleEnd) && | 259 isStartOfParagraphDeprecated(visibleEnd) && |
| 248 isStartOfParagraph(createVisiblePositionDeprecated(start)) && | 260 isStartOfParagraph(createVisiblePositionDeprecated(start)) && |
| 249 endingSelection().isRange()) { | 261 endingSelection().isRange()) { |
| 250 m_mergeBlocksAfterDelete = false; | 262 m_mergeBlocksAfterDelete = false; |
| 251 m_pruneStartBlockIfNecessary = true; | 263 m_pruneStartBlockIfNecessary = true; |
| 252 } | 264 } |
| 253 | 265 |
| 254 // Handle leading and trailing whitespace, as well as smart delete adjustments
to the selection | 266 // Handle leading and trailing whitespace, as well as smart delete adjustments |
| 267 // to the selection |
| 255 m_leadingWhitespace = leadingWhitespacePosition( | 268 m_leadingWhitespace = leadingWhitespacePosition( |
| 256 m_upstreamStart, m_selectionToDelete.affinity()); | 269 m_upstreamStart, m_selectionToDelete.affinity()); |
| 257 m_trailingWhitespace = | 270 m_trailingWhitespace = |
| 258 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY); | 271 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY); |
| 259 | 272 |
| 260 if (m_smartDelete) { | 273 if (m_smartDelete) { |
| 261 // skip smart delete if the selection to delete already starts or ends with
whitespace | 274 // skip smart delete if the selection to delete already starts or ends with |
| 275 // whitespace |
| 262 Position pos = createVisiblePositionDeprecated( | 276 Position pos = createVisiblePositionDeprecated( |
| 263 m_upstreamStart, m_selectionToDelete.affinity()) | 277 m_upstreamStart, m_selectionToDelete.affinity()) |
| 264 .deepEquivalent(); | 278 .deepEquivalent(); |
| 265 bool skipSmartDelete = | 279 bool skipSmartDelete = |
| 266 trailingWhitespacePosition(pos, VP_DEFAULT_AFFINITY, | 280 trailingWhitespacePosition(pos, VP_DEFAULT_AFFINITY, |
| 267 ConsiderNonCollapsibleWhitespace) | 281 ConsiderNonCollapsibleWhitespace) |
| 268 .isNotNull(); | 282 .isNotNull(); |
| 269 if (!skipSmartDelete) | 283 if (!skipSmartDelete) |
| 270 skipSmartDelete = | 284 skipSmartDelete = |
| 271 leadingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY, | 285 leadingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY, |
| (...skipping 14 matching lines...) Expand all Loading... |
| 286 // Expand out one character upstream for smart delete and recalculate | 300 // Expand out one character upstream for smart delete and recalculate |
| 287 // positions based on this change. | 301 // positions based on this change. |
| 288 m_upstreamStart = mostBackwardCaretPosition(pos); | 302 m_upstreamStart = mostBackwardCaretPosition(pos); |
| 289 m_downstreamStart = mostForwardCaretPosition(pos); | 303 m_downstreamStart = mostForwardCaretPosition(pos); |
| 290 m_leadingWhitespace = | 304 m_leadingWhitespace = |
| 291 leadingWhitespacePosition(m_upstreamStart, visiblePos.affinity()); | 305 leadingWhitespacePosition(m_upstreamStart, visiblePos.affinity()); |
| 292 | 306 |
| 293 setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd); | 307 setStartingSelectionOnSmartDelete(m_upstreamStart, m_upstreamEnd); |
| 294 } | 308 } |
| 295 | 309 |
| 296 // trailing whitespace is only considered for smart delete if there is no le
ading | 310 // trailing whitespace is only considered for smart delete if there is no |
| 297 // whitespace, as in the case where you double-click the first word of a par
agraph. | 311 // leading whitespace, as in the case where you double-click the first word |
| 312 // of a paragraph. |
| 298 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && | 313 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && |
| 299 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY, | 314 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY, |
| 300 ConsiderNonCollapsibleWhitespace) | 315 ConsiderNonCollapsibleWhitespace) |
| 301 .isNotNull()) { | 316 .isNotNull()) { |
| 302 // Expand out one character downstream for smart delete and recalculate | 317 // Expand out one character downstream for smart delete and recalculate |
| 303 // positions based on this change. | 318 // positions based on this change. |
| 304 pos = nextPositionOf(createVisiblePositionDeprecated(m_downstreamEnd, | 319 pos = nextPositionOf(createVisiblePositionDeprecated(m_downstreamEnd, |
| 305 VP_DEFAULT_AFFINITY)) | 320 VP_DEFAULT_AFFINITY)) |
| 306 .deepEquivalent(); | 321 .deepEquivalent(); |
| 307 m_upstreamEnd = mostBackwardCaretPosition(pos); | 322 m_upstreamEnd = mostBackwardCaretPosition(pos); |
| 308 m_downstreamEnd = mostForwardCaretPosition(pos); | 323 m_downstreamEnd = mostForwardCaretPosition(pos); |
| 309 m_trailingWhitespace = | 324 m_trailingWhitespace = |
| 310 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY); | 325 trailingWhitespacePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY); |
| 311 | 326 |
| 312 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd); | 327 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd); |
| 313 } | 328 } |
| 314 } | 329 } |
| 315 | 330 |
| 316 // We must pass call parentAnchoredEquivalent on the positions since some edit
ing positions | 331 // We must pass call parentAnchoredEquivalent on the positions since some |
| 317 // that appear inside their nodes aren't really inside them. [hr, 0] is one e
xample. | 332 // editing positions that appear inside their nodes aren't really inside them. |
| 318 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing e
lement getters | 333 // [hr, 0] is one example. |
| 319 // like the one below, since editing functions should obviously accept editing
positions. | 334 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing |
| 320 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return
a non-editable | 335 // element getters like the one below, since editing functions should |
| 321 // node. This was done to match existing behavior, but it seems wrong. | 336 // obviously accept editing positions. |
| 337 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return |
| 338 // a non-editable node. This was done to match existing behavior, but it |
| 339 // seems wrong. |
| 322 m_startBlock = | 340 m_startBlock = |
| 323 enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), | 341 enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), |
| 324 &isEnclosingBlock, CanCrossEditingBoundary); | 342 &isEnclosingBlock, CanCrossEditingBoundary); |
| 325 m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), | 343 m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), |
| 326 &isEnclosingBlock, CanCrossEditingBoundary); | 344 &isEnclosingBlock, CanCrossEditingBoundary); |
| 327 } | 345 } |
| 328 | 346 |
| 329 // We don't want to inherit style from an element which can't have contents. | 347 // We don't want to inherit style from an element which can't have contents. |
| 330 static bool shouldNotInheritStyleFrom(const Node& node) { | 348 static bool shouldNotInheritStyleFrom(const Node& node) { |
| 331 return !node.canContainRangeEndPoint(); | 349 return !node.canContainRangeEndPoint(); |
| 332 } | 350 } |
| 333 | 351 |
| 334 void DeleteSelectionCommand::saveTypingStyleState() { | 352 void DeleteSelectionCommand::saveTypingStyleState() { |
| 335 // A common case is deleting characters that are all from the same text node.
In | 353 // A common case is deleting characters that are all from the same text node. |
| 336 // that case, the style at the start of the selection before deletion will be
the | 354 // In that case, the style at the start of the selection before deletion will |
| 337 // same as the style at the start of the selection after deletion (since those | 355 // be the same as the style at the start of the selection after deletion |
| 338 // two positions will be identical). Therefore there is no need to save the | 356 // (since those two positions will be identical). Therefore there is no need |
| 339 // typing style at the start of the selection, nor is there a reason to | 357 // to save the typing style at the start of the selection, nor is there a |
| 340 // compute the style at the start of the selection after deletion (see the | 358 // reason to compute the style at the start of the selection after deletion |
| 341 // early return in calculateTypingStyleAfterDelete). | 359 // (see the early return in calculateTypingStyleAfterDelete). |
| 342 if (m_upstreamStart.anchorNode() == m_downstreamEnd.anchorNode() && | 360 if (m_upstreamStart.anchorNode() == m_downstreamEnd.anchorNode() && |
| 343 m_upstreamStart.anchorNode()->isTextNode()) | 361 m_upstreamStart.anchorNode()->isTextNode()) |
| 344 return; | 362 return; |
| 345 | 363 |
| 346 if (shouldNotInheritStyleFrom(*m_selectionToDelete.start().anchorNode())) | 364 if (shouldNotInheritStyleFrom(*m_selectionToDelete.start().anchorNode())) |
| 347 return; | 365 return; |
| 348 | 366 |
| 349 // Figure out the typing style in effect before the delete is done. | 367 // Figure out the typing style in effect before the delete is done. |
| 350 m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), | 368 m_typingStyle = EditingStyle::create(m_selectionToDelete.start(), |
| 351 EditingStyle::EditingPropertiesInEffect); | 369 EditingStyle::EditingPropertiesInEffect); |
| 352 m_typingStyle->removeStyleAddedByElement( | 370 m_typingStyle->removeStyleAddedByElement( |
| 353 enclosingAnchorElement(m_selectionToDelete.start())); | 371 enclosingAnchorElement(m_selectionToDelete.start())); |
| 354 | 372 |
| 355 // If we're deleting into a Mail blockquote, save the style at end() instead o
f start() | 373 // If we're deleting into a Mail blockquote, save the style at end() instead |
| 356 // We'll use this later in computeTypingStyleAfterDelete if we end up outside
of a Mail blockquote | 374 // of start(). We'll use this later in computeTypingStyleAfterDelete if we end |
| 375 // up outside of a Mail blockquote |
| 357 if (enclosingNodeOfType(m_selectionToDelete.start(), | 376 if (enclosingNodeOfType(m_selectionToDelete.start(), |
| 358 isMailHTMLBlockquoteElement)) | 377 isMailHTMLBlockquoteElement)) |
| 359 m_deleteIntoBlockquoteStyle = | 378 m_deleteIntoBlockquoteStyle = |
| 360 EditingStyle::create(m_selectionToDelete.end()); | 379 EditingStyle::create(m_selectionToDelete.end()); |
| 361 else | 380 else |
| 362 m_deleteIntoBlockquoteStyle = nullptr; | 381 m_deleteIntoBlockquoteStyle = nullptr; |
| 363 } | 382 } |
| 364 | 383 |
| 365 bool DeleteSelectionCommand::handleSpecialCaseBRDelete( | 384 bool DeleteSelectionCommand::handleSpecialCaseBRDelete( |
| 366 EditingState* editingState) { | 385 EditingState* editingState) { |
| 367 Node* nodeAfterUpstreamStart = m_upstreamStart.computeNodeAfterPosition(); | 386 Node* nodeAfterUpstreamStart = m_upstreamStart.computeNodeAfterPosition(); |
| 368 Node* nodeAfterDownstreamStart = m_downstreamStart.computeNodeAfterPosition(); | 387 Node* nodeAfterDownstreamStart = m_downstreamStart.computeNodeAfterPosition(); |
| 369 // Upstream end will appear before BR due to canonicalization | 388 // Upstream end will appear before BR due to canonicalization |
| 370 Node* nodeAfterUpstreamEnd = m_upstreamEnd.computeNodeAfterPosition(); | 389 Node* nodeAfterUpstreamEnd = m_upstreamEnd.computeNodeAfterPosition(); |
| 371 | 390 |
| 372 if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart) | 391 if (!nodeAfterUpstreamStart || !nodeAfterDownstreamStart) |
| 373 return false; | 392 return false; |
| 374 | 393 |
| 375 // Check for special-case where the selection contains only a BR on a line by
itself after another BR. | 394 // Check for special-case where the selection contains only a BR on a line by |
| 395 // itself after another BR. |
| 376 bool upstreamStartIsBR = isHTMLBRElement(*nodeAfterUpstreamStart); | 396 bool upstreamStartIsBR = isHTMLBRElement(*nodeAfterUpstreamStart); |
| 377 bool downstreamStartIsBR = isHTMLBRElement(*nodeAfterDownstreamStart); | 397 bool downstreamStartIsBR = isHTMLBRElement(*nodeAfterDownstreamStart); |
| 378 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && | 398 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && |
| 379 nodeAfterDownstreamStart == nodeAfterUpstreamEnd; | 399 nodeAfterDownstreamStart == nodeAfterUpstreamEnd; |
| 380 if (isBROnLineByItself) { | 400 if (isBROnLineByItself) { |
| 381 removeNode(nodeAfterDownstreamStart, editingState); | 401 removeNode(nodeAfterDownstreamStart, editingState); |
| 382 return true; | 402 return true; |
| 383 } | 403 } |
| 384 | 404 |
| 385 // FIXME: This code doesn't belong in here. | 405 // FIXME: This code doesn't belong in here. |
| 386 // We detect the case where the start is an empty line consisting of BR not wr
apped in a block element. | 406 // We detect the case where the start is an empty line consisting of BR not |
| 407 // wrapped in a block element. |
| 387 if (upstreamStartIsBR && downstreamStartIsBR && | 408 if (upstreamStartIsBR && downstreamStartIsBR && |
| 388 !(isStartOfBlock(VisiblePosition::beforeNode(nodeAfterUpstreamStart)) && | 409 !(isStartOfBlock(VisiblePosition::beforeNode(nodeAfterUpstreamStart)) && |
| 389 isEndOfBlock(VisiblePosition::afterNode(nodeAfterUpstreamStart)))) { | 410 isEndOfBlock(VisiblePosition::afterNode(nodeAfterUpstreamStart)))) { |
| 390 m_startsAtEmptyLine = true; | 411 m_startsAtEmptyLine = true; |
| 391 m_endingPosition = m_downstreamEnd; | 412 m_endingPosition = m_downstreamEnd; |
| 392 } | 413 } |
| 393 | 414 |
| 394 return false; | 415 return false; |
| 395 } | 416 } |
| 396 | 417 |
| 397 static Position firstEditablePositionInNode(Node* node) { | 418 static Position firstEditablePositionInNode(Node* node) { |
| 398 DCHECK(node); | 419 DCHECK(node); |
| 399 Node* next = node; | 420 Node* next = node; |
| 400 while (next && !hasEditableStyle(*next)) | 421 while (next && !hasEditableStyle(*next)) |
| 401 next = NodeTraversal::next(*next, node); | 422 next = NodeTraversal::next(*next, node); |
| 402 return next ? firstPositionInOrBeforeNode(next) : Position(); | 423 return next ? firstPositionInOrBeforeNode(next) : Position(); |
| 403 } | 424 } |
| 404 | 425 |
| 405 void DeleteSelectionCommand::removeNode( | 426 void DeleteSelectionCommand::removeNode( |
| 406 Node* node, | 427 Node* node, |
| 407 EditingState* editingState, | 428 EditingState* editingState, |
| 408 ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { | 429 ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) { |
| 409 if (!node) | 430 if (!node) |
| 410 return; | 431 return; |
| 411 | 432 |
| 412 if (m_startRoot != m_endRoot && | 433 if (m_startRoot != m_endRoot && |
| 413 !(node->isDescendantOf(m_startRoot.get()) && | 434 !(node->isDescendantOf(m_startRoot.get()) && |
| 414 node->isDescendantOf(m_endRoot.get()))) { | 435 node->isDescendantOf(m_endRoot.get()))) { |
| 415 // If a node is not in both the start and end editable roots, remove it only
if its inside an editable region. | 436 // If a node is not in both the start and end editable roots, remove it only |
| 437 // if its inside an editable region. |
| 416 if (!hasEditableStyle(*node->parentNode())) { | 438 if (!hasEditableStyle(*node->parentNode())) { |
| 417 // Don't remove non-editable atomic nodes. | 439 // Don't remove non-editable atomic nodes. |
| 418 if (!node->hasChildren()) | 440 if (!node->hasChildren()) |
| 419 return; | 441 return; |
| 420 // Search this non-editable region for editable regions to empty. | 442 // Search this non-editable region for editable regions to empty. |
| 421 Node* child = node->firstChild(); | 443 Node* child = node->firstChild(); |
| 422 while (child) { | 444 while (child) { |
| 423 Node* nextChild = child->nextSibling(); | 445 Node* nextChild = child->nextSibling(); |
| 424 removeNode(child, editingState, shouldAssumeContentIsAlwaysEditable); | 446 removeNode(child, editingState, shouldAssumeContentIsAlwaysEditable); |
| 425 if (editingState->isAborted()) | 447 if (editingState->isAborted()) |
| 426 return; | 448 return; |
| 427 // Bail if nextChild is no longer node's child. | 449 // Bail if nextChild is no longer node's child. |
| 428 if (nextChild && nextChild->parentNode() != node) | 450 if (nextChild && nextChild->parentNode() != node) |
| 429 return; | 451 return; |
| 430 child = nextChild; | 452 child = nextChild; |
| 431 } | 453 } |
| 432 | 454 |
| 433 // Don't remove editable regions that are inside non-editable ones, just c
lear them. | 455 // Don't remove editable regions that are inside non-editable ones, just |
| 456 // clear them. |
| 434 return; | 457 return; |
| 435 } | 458 } |
| 436 } | 459 } |
| 437 | 460 |
| 438 if (isTableStructureNode(node) || isRootEditableElement(*node)) { | 461 if (isTableStructureNode(node) || isRootEditableElement(*node)) { |
| 439 // Do not remove an element of table structure; remove its contents. | 462 // Do not remove an element of table structure; remove its contents. |
| 440 // Likewise for the root editable element. | 463 // Likewise for the root editable element. |
| 441 Node* child = node->firstChild(); | 464 Node* child = node->firstChild(); |
| 442 while (child) { | 465 while (child) { |
| 443 Node* remove = child; | 466 Node* remove = child; |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 535 | 558 |
| 536 int startOffset = m_upstreamStart.computeEditingOffset(); | 559 int startOffset = m_upstreamStart.computeEditingOffset(); |
| 537 Node* startNode = m_upstreamStart.anchorNode(); | 560 Node* startNode = m_upstreamStart.anchorNode(); |
| 538 DCHECK(startNode); | 561 DCHECK(startNode); |
| 539 | 562 |
| 540 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss( | 563 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss( |
| 541 editingState); | 564 editingState); |
| 542 if (editingState->isAborted()) | 565 if (editingState->isAborted()) |
| 543 return; | 566 return; |
| 544 | 567 |
| 545 // Never remove the start block unless it's a table, in which case we won't me
rge content in. | 568 // Never remove the start block unless it's a table, in which case we won't |
| 569 // merge content in. |
| 546 if (startNode == m_startBlock.get() && !startOffset && | 570 if (startNode == m_startBlock.get() && !startOffset && |
| 547 canHaveChildrenForEditing(startNode) && !isHTMLTableElement(*startNode)) { | 571 canHaveChildrenForEditing(startNode) && !isHTMLTableElement(*startNode)) { |
| 548 startOffset = 0; | 572 startOffset = 0; |
| 549 startNode = NodeTraversal::next(*startNode); | 573 startNode = NodeTraversal::next(*startNode); |
| 550 if (!startNode) | 574 if (!startNode) |
| 551 return; | 575 return; |
| 552 } | 576 } |
| 553 | 577 |
| 554 if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) { | 578 if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) { |
| 555 Text* text = toText(startNode); | 579 Text* text = toText(startNode); |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 611 } else if (startNode == m_upstreamEnd.anchorNode() && | 635 } else if (startNode == m_upstreamEnd.anchorNode() && |
| 612 startNode->isTextNode()) { | 636 startNode->isTextNode()) { |
| 613 Text* text = toText(m_upstreamEnd.anchorNode()); | 637 Text* text = toText(m_upstreamEnd.anchorNode()); |
| 614 deleteTextFromNode(text, 0, m_upstreamEnd.computeOffsetInContainerNode()); | 638 deleteTextFromNode(text, 0, m_upstreamEnd.computeOffsetInContainerNode()); |
| 615 } | 639 } |
| 616 | 640 |
| 617 // handle deleting all nodes that are completely selected | 641 // handle deleting all nodes that are completely selected |
| 618 while (node && node != m_downstreamEnd.anchorNode()) { | 642 while (node && node != m_downstreamEnd.anchorNode()) { |
| 619 if (comparePositions(firstPositionInOrBeforeNode(node), | 643 if (comparePositions(firstPositionInOrBeforeNode(node), |
| 620 m_downstreamEnd) >= 0) { | 644 m_downstreamEnd) >= 0) { |
| 621 // NodeTraversal::nextSkippingChildren just blew past the end position,
so stop deleting | 645 // NodeTraversal::nextSkippingChildren just blew past the end position, |
| 646 // so stop deleting |
| 622 node = nullptr; | 647 node = nullptr; |
| 623 } else if (!m_downstreamEnd.anchorNode()->isDescendantOf(node)) { | 648 } else if (!m_downstreamEnd.anchorNode()->isDescendantOf(node)) { |
| 624 Node* nextNode = NodeTraversal::nextSkippingChildren(*node); | 649 Node* nextNode = NodeTraversal::nextSkippingChildren(*node); |
| 625 // if we just removed a node from the end container, update end position
so the | 650 // if we just removed a node from the end container, update end position |
| 626 // check above will work | 651 // so the check above will work |
| 627 updatePositionForNodeRemoval(m_downstreamEnd, *node); | 652 updatePositionForNodeRemoval(m_downstreamEnd, *node); |
| 628 removeNode(node, editingState); | 653 removeNode(node, editingState); |
| 629 if (editingState->isAborted()) | 654 if (editingState->isAborted()) |
| 630 return; | 655 return; |
| 631 node = nextNode; | 656 node = nextNode; |
| 632 } else { | 657 } else { |
| 633 Node& n = NodeTraversal::lastWithinOrSelf(*node); | 658 Node& n = NodeTraversal::lastWithinOrSelf(*node); |
| 634 if (m_downstreamEnd.anchorNode() == n && | 659 if (m_downstreamEnd.anchorNode() == n && |
| 635 m_downstreamEnd.computeEditingOffset() >= caretMaxOffset(&n)) { | 660 m_downstreamEnd.computeEditingOffset() >= caretMaxOffset(&n)) { |
| 636 removeNode(node, editingState); | 661 removeNode(node, editingState); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 653 !canHaveChildrenForEditing(m_downstreamEnd.anchorNode())) { | 678 !canHaveChildrenForEditing(m_downstreamEnd.anchorNode())) { |
| 654 // The node itself is fully selected, not just its contents. Delete it. | 679 // The node itself is fully selected, not just its contents. Delete it. |
| 655 removeNode(m_downstreamEnd.anchorNode(), editingState); | 680 removeNode(m_downstreamEnd.anchorNode(), editingState); |
| 656 } else { | 681 } else { |
| 657 if (m_downstreamEnd.anchorNode()->isTextNode()) { | 682 if (m_downstreamEnd.anchorNode()->isTextNode()) { |
| 658 // in a text node that needs to be trimmed | 683 // in a text node that needs to be trimmed |
| 659 Text* text = toText(m_downstreamEnd.anchorNode()); | 684 Text* text = toText(m_downstreamEnd.anchorNode()); |
| 660 if (m_downstreamEnd.computeEditingOffset() > 0) { | 685 if (m_downstreamEnd.computeEditingOffset() > 0) { |
| 661 deleteTextFromNode(text, 0, m_downstreamEnd.computeEditingOffset()); | 686 deleteTextFromNode(text, 0, m_downstreamEnd.computeEditingOffset()); |
| 662 } | 687 } |
| 663 // Remove children of m_downstreamEnd.anchorNode() that come after m_u
pstreamStart. | 688 // Remove children of m_downstreamEnd.anchorNode() that come after |
| 664 // Don't try to remove children if m_upstreamStart was inside m_downst
reamEnd.anchorNode() | 689 // m_upstreamStart. Don't try to remove children if m_upstreamStart |
| 665 // and m_upstreamStart has been removed from the document, because the
n we don't | 690 // was inside m_downstreamEnd.anchorNode() and m_upstreamStart has |
| 666 // know how many children to remove. | 691 // been removed from the document, because then we don't know how many |
| 667 // FIXME: Make m_upstreamStart a position we update as we remove conte
nt, then we can | 692 // children to remove. |
| 668 // always know which children to remove. | 693 // FIXME: Make m_upstreamStart a position we update as we remove |
| 694 // content, then we can always know which children to remove. |
| 669 } else if (!(startNodeWasDescendantOfEndNode && | 695 } else if (!(startNodeWasDescendantOfEndNode && |
| 670 !m_upstreamStart.isConnected())) { | 696 !m_upstreamStart.isConnected())) { |
| 671 int offset = 0; | 697 int offset = 0; |
| 672 if (m_upstreamStart.anchorNode()->isDescendantOf( | 698 if (m_upstreamStart.anchorNode()->isDescendantOf( |
| 673 m_downstreamEnd.anchorNode())) { | 699 m_downstreamEnd.anchorNode())) { |
| 674 Node* n = m_upstreamStart.anchorNode(); | 700 Node* n = m_upstreamStart.anchorNode(); |
| 675 while (n && n->parentNode() != m_downstreamEnd.anchorNode()) | 701 while (n && n->parentNode() != m_downstreamEnd.anchorNode()) |
| 676 n = n->parentNode(); | 702 n = n->parentNode(); |
| 677 if (n) | 703 if (n) |
| 678 offset = n->nodeIndex() + 1; | 704 offset = n->nodeIndex() + 1; |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 712 Text* textNode = toText(m_trailingWhitespace.anchorNode()); | 738 Text* textNode = toText(m_trailingWhitespace.anchorNode()); |
| 713 DCHECK(!textNode->layoutObject() || | 739 DCHECK(!textNode->layoutObject() || |
| 714 textNode->layoutObject()->style()->collapseWhiteSpace()) | 740 textNode->layoutObject()->style()->collapseWhiteSpace()) |
| 715 << textNode; | 741 << textNode; |
| 716 replaceTextInNodePreservingMarkers( | 742 replaceTextInNodePreservingMarkers( |
| 717 textNode, m_trailingWhitespace.computeOffsetInContainerNode(), 1, | 743 textNode, m_trailingWhitespace.computeOffsetInContainerNode(), 1, |
| 718 nonBreakingSpaceString()); | 744 nonBreakingSpaceString()); |
| 719 } | 745 } |
| 720 } | 746 } |
| 721 | 747 |
| 722 // If a selection starts in one block and ends in another, we have to merge to b
ring content before the | 748 // If a selection starts in one block and ends in another, we have to merge to |
| 723 // start together with content after the end. | 749 // bring content before the start together with content after the end. |
| 724 void DeleteSelectionCommand::mergeParagraphs(EditingState* editingState) { | 750 void DeleteSelectionCommand::mergeParagraphs(EditingState* editingState) { |
| 725 if (!m_mergeBlocksAfterDelete) { | 751 if (!m_mergeBlocksAfterDelete) { |
| 726 if (m_pruneStartBlockIfNecessary) { | 752 if (m_pruneStartBlockIfNecessary) { |
| 727 // We aren't going to merge into the start block, so remove it if it's emp
ty. | 753 // We aren't going to merge into the start block, so remove it if it's |
| 754 // empty. |
| 728 prune(m_startBlock, editingState); | 755 prune(m_startBlock, editingState); |
| 729 if (editingState->isAborted()) | 756 if (editingState->isAborted()) |
| 730 return; | 757 return; |
| 731 // Removing the start block during a deletion is usually an indication tha
t we need | 758 // Removing the start block during a deletion is usually an indication |
| 732 // a placeholder, but not in this case. | 759 // that we need a placeholder, but not in this case. |
| 733 m_needPlaceholder = false; | 760 m_needPlaceholder = false; |
| 734 } | 761 } |
| 735 return; | 762 return; |
| 736 } | 763 } |
| 737 | 764 |
| 738 // It shouldn't have been asked to both try and merge content into the start b
lock and prune it. | 765 // It shouldn't have been asked to both try and merge content into the start |
| 766 // block and prune it. |
| 739 DCHECK(!m_pruneStartBlockIfNecessary); | 767 DCHECK(!m_pruneStartBlockIfNecessary); |
| 740 | 768 |
| 741 // FIXME: Deletion should adjust selection endpoints as it removes nodes so th
at we never get into this state (4099839). | 769 // FIXME: Deletion should adjust selection endpoints as it removes nodes so |
| 770 // that we never get into this state (4099839). |
| 742 if (!m_downstreamEnd.isConnected() || !m_upstreamStart.isConnected()) | 771 if (!m_downstreamEnd.isConnected() || !m_upstreamStart.isConnected()) |
| 743 return; | 772 return; |
| 744 | 773 |
| 745 // FIXME: The deletion algorithm shouldn't let this happen. | 774 // FIXME: The deletion algorithm shouldn't let this happen. |
| 746 if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0) | 775 if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0) |
| 747 return; | 776 return; |
| 748 | 777 |
| 749 // There's nothing to merge. | 778 // There's nothing to merge. |
| 750 if (m_upstreamStart == m_downstreamEnd) | 779 if (m_upstreamStart == m_downstreamEnd) |
| 751 return; | 780 return; |
| 752 | 781 |
| 753 VisiblePosition startOfParagraphToMove = | 782 VisiblePosition startOfParagraphToMove = |
| 754 createVisiblePositionDeprecated(m_downstreamEnd); | 783 createVisiblePositionDeprecated(m_downstreamEnd); |
| 755 VisiblePosition mergeDestination = | 784 VisiblePosition mergeDestination = |
| 756 createVisiblePositionDeprecated(m_upstreamStart); | 785 createVisiblePositionDeprecated(m_upstreamStart); |
| 757 | 786 |
| 758 // m_downstreamEnd's block has been emptied out by deletion. There is no cont
ent inside of it to | 787 // m_downstreamEnd's block has been emptied out by deletion. There is no |
| 759 // move, so just remove it. | 788 // content inside of it to move, so just remove it. |
| 760 Element* endBlock = enclosingBlock(m_downstreamEnd.anchorNode()); | 789 Element* endBlock = enclosingBlock(m_downstreamEnd.anchorNode()); |
| 761 if (!endBlock || | 790 if (!endBlock || |
| 762 !endBlock->contains( | 791 !endBlock->contains( |
| 763 startOfParagraphToMove.deepEquivalent().anchorNode()) || | 792 startOfParagraphToMove.deepEquivalent().anchorNode()) || |
| 764 !startOfParagraphToMove.deepEquivalent().anchorNode()) { | 793 !startOfParagraphToMove.deepEquivalent().anchorNode()) { |
| 765 removeNode(enclosingBlock(m_downstreamEnd.anchorNode()), editingState); | 794 removeNode(enclosingBlock(m_downstreamEnd.anchorNode()), editingState); |
| 766 return; | 795 return; |
| 767 } | 796 } |
| 768 | 797 |
| 769 // We need to merge into m_upstreamStart's block, but it's been emptied out an
d collapsed by deletion. | 798 // We need to merge into m_upstreamStart's block, but it's been emptied out |
| 799 // and collapsed by deletion. |
| 770 if (!mergeDestination.deepEquivalent().anchorNode() || | 800 if (!mergeDestination.deepEquivalent().anchorNode() || |
| 771 (!mergeDestination.deepEquivalent().anchorNode()->isDescendantOf( | 801 (!mergeDestination.deepEquivalent().anchorNode()->isDescendantOf( |
| 772 enclosingBlock(m_upstreamStart.computeContainerNode())) && | 802 enclosingBlock(m_upstreamStart.computeContainerNode())) && |
| 773 (!mergeDestination.deepEquivalent().anchorNode()->hasChildren() || | 803 (!mergeDestination.deepEquivalent().anchorNode()->hasChildren() || |
| 774 !m_upstreamStart.computeContainerNode()->hasChildren())) || | 804 !m_upstreamStart.computeContainerNode()->hasChildren())) || |
| 775 (m_startsAtEmptyLine && | 805 (m_startsAtEmptyLine && |
| 776 mergeDestination.deepEquivalent() != | 806 mergeDestination.deepEquivalent() != |
| 777 startOfParagraphToMove.deepEquivalent())) { | 807 startOfParagraphToMove.deepEquivalent())) { |
| 778 PositionWithAffinity storedStartOfParagraphToMove = | 808 PositionWithAffinity storedStartOfParagraphToMove = |
| 779 startOfParagraphToMove.toPositionWithAffinity(); | 809 startOfParagraphToMove.toPositionWithAffinity(); |
| (...skipping 11 matching lines...) Expand all Loading... |
| 791 startOfParagraphToMove.deepEquivalent()) | 821 startOfParagraphToMove.deepEquivalent()) |
| 792 return; | 822 return; |
| 793 | 823 |
| 794 VisiblePosition endOfParagraphToMove = endOfParagraphDeprecated( | 824 VisiblePosition endOfParagraphToMove = endOfParagraphDeprecated( |
| 795 startOfParagraphToMove, CanSkipOverEditingBoundary); | 825 startOfParagraphToMove, CanSkipOverEditingBoundary); |
| 796 | 826 |
| 797 if (mergeDestination.deepEquivalent() == | 827 if (mergeDestination.deepEquivalent() == |
| 798 endOfParagraphToMove.deepEquivalent()) | 828 endOfParagraphToMove.deepEquivalent()) |
| 799 return; | 829 return; |
| 800 | 830 |
| 801 // If the merge destination and source to be moved are both list items of diff
erent lists, merge them into single list. | 831 // If the merge destination and source to be moved are both list items of |
| 832 // different lists, merge them into single list. |
| 802 Node* listItemInFirstParagraph = | 833 Node* listItemInFirstParagraph = |
| 803 enclosingNodeOfType(m_upstreamStart, isListItem); | 834 enclosingNodeOfType(m_upstreamStart, isListItem); |
| 804 Node* listItemInSecondParagraph = | 835 Node* listItemInSecondParagraph = |
| 805 enclosingNodeOfType(m_downstreamEnd, isListItem); | 836 enclosingNodeOfType(m_downstreamEnd, isListItem); |
| 806 if (listItemInFirstParagraph && listItemInSecondParagraph && | 837 if (listItemInFirstParagraph && listItemInSecondParagraph && |
| 807 listItemInFirstParagraph->parentElement() != | 838 listItemInFirstParagraph->parentElement() != |
| 808 listItemInSecondParagraph->parentElement() && | 839 listItemInSecondParagraph->parentElement() && |
| 809 canMergeLists(listItemInFirstParagraph->parentElement(), | 840 canMergeLists(listItemInFirstParagraph->parentElement(), |
| 810 listItemInSecondParagraph->parentElement())) { | 841 listItemInSecondParagraph->parentElement())) { |
| 811 mergeIdenticalElements(listItemInFirstParagraph->parentElement(), | 842 mergeIdenticalElements(listItemInFirstParagraph->parentElement(), |
| 812 listItemInSecondParagraph->parentElement(), | 843 listItemInSecondParagraph->parentElement(), |
| 813 editingState); | 844 editingState); |
| 814 if (editingState->isAborted()) | 845 if (editingState->isAborted()) |
| 815 return; | 846 return; |
| 816 m_endingPosition = mergeDestination.deepEquivalent(); | 847 m_endingPosition = mergeDestination.deepEquivalent(); |
| 817 return; | 848 return; |
| 818 } | 849 } |
| 819 | 850 |
| 820 // The rule for merging into an empty block is: only do so if its farther to t
he right. | 851 // The rule for merging into an empty block is: only do so if its farther to |
| 852 // the right. |
| 821 // FIXME: Consider RTL. | 853 // FIXME: Consider RTL. |
| 822 if (!m_startsAtEmptyLine && isStartOfParagraphDeprecated(mergeDestination) && | 854 if (!m_startsAtEmptyLine && isStartOfParagraphDeprecated(mergeDestination) && |
| 823 absoluteCaretBoundsOf(startOfParagraphToMove).x() > | 855 absoluteCaretBoundsOf(startOfParagraphToMove).x() > |
| 824 absoluteCaretBoundsOf(mergeDestination).x()) { | 856 absoluteCaretBoundsOf(mergeDestination).x()) { |
| 825 if (isHTMLBRElement( | 857 if (isHTMLBRElement( |
| 826 *mostForwardCaretPosition(mergeDestination.deepEquivalent()) | 858 *mostForwardCaretPosition(mergeDestination.deepEquivalent()) |
| 827 .anchorNode())) { | 859 .anchorNode())) { |
| 828 removeNodeAndPruneAncestors( | 860 removeNodeAndPruneAncestors( |
| 829 mostForwardCaretPosition(mergeDestination.deepEquivalent()) | 861 mostForwardCaretPosition(mergeDestination.deepEquivalent()) |
| 830 .anchorNode(), | 862 .anchorNode(), |
| 831 editingState); | 863 editingState); |
| 832 if (editingState->isAborted()) | 864 if (editingState->isAborted()) |
| 833 return; | 865 return; |
| 834 m_endingPosition = startOfParagraphToMove.deepEquivalent(); | 866 m_endingPosition = startOfParagraphToMove.deepEquivalent(); |
| 835 return; | 867 return; |
| 836 } | 868 } |
| 837 } | 869 } |
| 838 | 870 |
| 839 // Block images, tables and horizontal rules cannot be made inline with conten
t at mergeDestination. If there is | 871 // Block images, tables and horizontal rules cannot be made inline with |
| 840 // any (!isStartOfParagraphDeprecated(mergeDestination)), don't merge, just mo
ve the caret to just before the selection we deleted. | 872 // content at mergeDestination. If there is any |
| 841 // See https://bugs.webkit.org/show_bug.cgi?id=25439 | 873 // (!isStartOfParagraphDeprecated(mergeDestination)), don't merge, just move |
| 874 // the caret to just before the selection we deleted. See |
| 875 // https://bugs.webkit.org/show_bug.cgi?id=25439 |
| 842 if (isRenderedAsNonInlineTableImageOrHR( | 876 if (isRenderedAsNonInlineTableImageOrHR( |
| 843 startOfParagraphToMove.deepEquivalent().anchorNode()) && | 877 startOfParagraphToMove.deepEquivalent().anchorNode()) && |
| 844 !isStartOfParagraphDeprecated(mergeDestination)) { | 878 !isStartOfParagraphDeprecated(mergeDestination)) { |
| 845 m_endingPosition = m_upstreamStart; | 879 m_endingPosition = m_upstreamStart; |
| 846 return; | 880 return; |
| 847 } | 881 } |
| 848 | 882 |
| 849 // moveParagraphs will insert placeholders if it removes blocks that would req
uire their use, don't let block | 883 // moveParagraphs will insert placeholders if it removes blocks that would |
| 850 // removals that it does cause the insertion of *another* placeholder. | 884 // require their use, don't let block removals that it does cause the |
| 885 // insertion of *another* placeholder. |
| 851 bool needPlaceholder = m_needPlaceholder; | 886 bool needPlaceholder = m_needPlaceholder; |
| 852 bool paragraphToMergeIsEmpty = startOfParagraphToMove.deepEquivalent() == | 887 bool paragraphToMergeIsEmpty = startOfParagraphToMove.deepEquivalent() == |
| 853 endOfParagraphToMove.deepEquivalent(); | 888 endOfParagraphToMove.deepEquivalent(); |
| 854 moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, | 889 moveParagraph(startOfParagraphToMove, endOfParagraphToMove, mergeDestination, |
| 855 editingState, DoNotPreserveSelection, | 890 editingState, DoNotPreserveSelection, |
| 856 paragraphToMergeIsEmpty ? DoNotPreserveStyle : PreserveStyle); | 891 paragraphToMergeIsEmpty ? DoNotPreserveStyle : PreserveStyle); |
| 857 if (editingState->isAborted()) | 892 if (editingState->isAborted()) |
| 858 return; | 893 return; |
| 859 m_needPlaceholder = needPlaceholder; | 894 m_needPlaceholder = needPlaceholder; |
| 860 // The endingPosition was likely clobbered by the move, so recompute it (moveP
aragraph selects the moved paragraph). | 895 // The endingPosition was likely clobbered by the move, so recompute it |
| 896 // (moveParagraph selects the moved paragraph). |
| 861 m_endingPosition = endingSelection().start(); | 897 m_endingPosition = endingSelection().start(); |
| 862 } | 898 } |
| 863 | 899 |
| 864 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows( | 900 void DeleteSelectionCommand::removePreviouslySelectedEmptyTableRows( |
| 865 EditingState* editingState) { | 901 EditingState* editingState) { |
| 866 if (m_endTableRow && m_endTableRow->isConnected() && | 902 if (m_endTableRow && m_endTableRow->isConnected() && |
| 867 m_endTableRow != m_startTableRow) { | 903 m_endTableRow != m_startTableRow) { |
| 868 Node* row = m_endTableRow->previousSibling(); | 904 Node* row = m_endTableRow->previousSibling(); |
| 869 while (row && row != m_startTableRow) { | 905 while (row && row != m_startTableRow) { |
| 870 Node* previousRow = row->previousSibling(); | 906 Node* previousRow = row->previousSibling(); |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 913 } | 949 } |
| 914 } | 950 } |
| 915 | 951 |
| 916 void DeleteSelectionCommand::calculateTypingStyleAfterDelete() { | 952 void DeleteSelectionCommand::calculateTypingStyleAfterDelete() { |
| 917 // Clearing any previously set typing style and doing an early return. | 953 // Clearing any previously set typing style and doing an early return. |
| 918 if (!m_typingStyle) { | 954 if (!m_typingStyle) { |
| 919 document().frame()->selection().clearTypingStyle(); | 955 document().frame()->selection().clearTypingStyle(); |
| 920 return; | 956 return; |
| 921 } | 957 } |
| 922 | 958 |
| 923 // Compute the difference between the style before the delete and the style no
w | 959 // Compute the difference between the style before the delete and the style |
| 924 // after the delete has been done. Set this style on the frame, so other editi
ng | 960 // now after the delete has been done. Set this style on the frame, so other |
| 925 // commands being composed with this one will work, and also cache it on the c
ommand, | 961 // editing commands being composed with this one will work, and also cache it |
| 926 // so the LocalFrame::appliedEditing can set it after the whole composite comm
and | 962 // on the command, so the LocalFrame::appliedEditing can set it after the |
| 927 // has completed. | 963 // whole composite command has completed. |
| 928 | 964 |
| 929 // If we deleted into a blockquote, but are now no longer in a blockquote, use
the alternate typing style | 965 // If we deleted into a blockquote, but are now no longer in a blockquote, use |
| 966 // the alternate typing style |
| 930 if (m_deleteIntoBlockquoteStyle && | 967 if (m_deleteIntoBlockquoteStyle && |
| 931 !enclosingNodeOfType(m_endingPosition, isMailHTMLBlockquoteElement, | 968 !enclosingNodeOfType(m_endingPosition, isMailHTMLBlockquoteElement, |
| 932 CanCrossEditingBoundary)) | 969 CanCrossEditingBoundary)) |
| 933 m_typingStyle = m_deleteIntoBlockquoteStyle; | 970 m_typingStyle = m_deleteIntoBlockquoteStyle; |
| 934 m_deleteIntoBlockquoteStyle = nullptr; | 971 m_deleteIntoBlockquoteStyle = nullptr; |
| 935 | 972 |
| 936 m_typingStyle->prepareToApplyAt(m_endingPosition); | 973 m_typingStyle->prepareToApplyAt(m_endingPosition); |
| 937 if (m_typingStyle->isEmpty()) | 974 if (m_typingStyle->isEmpty()) |
| 938 m_typingStyle = nullptr; | 975 m_typingStyle = nullptr; |
| 939 // This is where we've deleted all traces of a style but not a whole paragraph
(that's handled above). | 976 // This is where we've deleted all traces of a style but not a whole paragraph |
| 940 // In this case if we start typing, the new characters should have the same st
yle as the just deleted ones, | 977 // (that's handled above). In this case if we start typing, the new characters |
| 941 // but, if we change the selection, come back and start typing that style shou
ld be lost. Also see | 978 // should have the same style as the just deleted ones, but, if we change the |
| 979 // selection, come back and start typing that style should be lost. Also see |
| 942 // preserveTypingStyle() below. | 980 // preserveTypingStyle() below. |
| 943 document().frame()->selection().setTypingStyle(m_typingStyle); | 981 document().frame()->selection().setTypingStyle(m_typingStyle); |
| 944 } | 982 } |
| 945 | 983 |
| 946 void DeleteSelectionCommand::clearTransientState() { | 984 void DeleteSelectionCommand::clearTransientState() { |
| 947 m_selectionToDelete = VisibleSelection(); | 985 m_selectionToDelete = VisibleSelection(); |
| 948 m_upstreamStart = Position(); | 986 m_upstreamStart = Position(); |
| 949 m_downstreamStart = Position(); | 987 m_downstreamStart = Position(); |
| 950 m_upstreamEnd = Position(); | 988 m_upstreamEnd = Position(); |
| 951 m_downstreamEnd = Position(); | 989 m_downstreamEnd = Position(); |
| 952 m_endingPosition = Position(); | 990 m_endingPosition = Position(); |
| 953 m_leadingWhitespace = Position(); | 991 m_leadingWhitespace = Position(); |
| 954 m_trailingWhitespace = Position(); | 992 m_trailingWhitespace = Position(); |
| 955 m_referenceMovePosition = Position(); | 993 m_referenceMovePosition = Position(); |
| 956 } | 994 } |
| 957 | 995 |
| 958 // This method removes div elements with no attributes that have only one child
or no children at all. | 996 // This method removes div elements with no attributes that have only one child |
| 997 // or no children at all. |
| 959 void DeleteSelectionCommand::removeRedundantBlocks(EditingState* editingState) { | 998 void DeleteSelectionCommand::removeRedundantBlocks(EditingState* editingState) { |
| 960 Node* node = m_endingPosition.computeContainerNode(); | 999 Node* node = m_endingPosition.computeContainerNode(); |
| 961 Element* rootElement = rootEditableElement(*node); | 1000 Element* rootElement = rootEditableElement(*node); |
| 962 | 1001 |
| 963 while (node != rootElement) { | 1002 while (node != rootElement) { |
| 964 if (isRemovableBlock(node)) { | 1003 if (isRemovableBlock(node)) { |
| 965 if (node == m_endingPosition.anchorNode()) | 1004 if (node == m_endingPosition.anchorNode()) |
| 966 updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node); | 1005 updatePositionForNodeRemovalPreservingChildren(m_endingPosition, *node); |
| 967 | 1006 |
| 968 CompositeEditCommand::removeNodePreservingChildren(node, editingState); | 1007 CompositeEditCommand::removeNodePreservingChildren(node, editingState); |
| 969 if (editingState->isAborted()) | 1008 if (editingState->isAborted()) |
| 970 return; | 1009 return; |
| 971 node = m_endingPosition.anchorNode(); | 1010 node = m_endingPosition.anchorNode(); |
| 972 } else { | 1011 } else { |
| 973 node = node->parentNode(); | 1012 node = node->parentNode(); |
| 974 } | 1013 } |
| 975 } | 1014 } |
| 976 } | 1015 } |
| 977 | 1016 |
| 978 void DeleteSelectionCommand::doApply(EditingState* editingState) { | 1017 void DeleteSelectionCommand::doApply(EditingState* editingState) { |
| 979 // If selection has not been set to a custom selection when the command was cr
eated, | 1018 // If selection has not been set to a custom selection when the command was |
| 980 // use the current ending selection. | 1019 // created, use the current ending selection. |
| 981 if (!m_hasSelectionToDelete) | 1020 if (!m_hasSelectionToDelete) |
| 982 m_selectionToDelete = endingSelection(); | 1021 m_selectionToDelete = endingSelection(); |
| 983 | 1022 |
| 984 if (!m_selectionToDelete.isNonOrphanedRange() || | 1023 if (!m_selectionToDelete.isNonOrphanedRange() || |
| 985 !m_selectionToDelete.isContentEditable()) | 1024 !m_selectionToDelete.isContentEditable()) |
| 986 return; | 1025 return; |
| 987 | 1026 |
| 988 RelocatablePosition relocatableReferencePosition(m_referenceMovePosition); | 1027 RelocatablePosition relocatableReferencePosition(m_referenceMovePosition); |
| 989 | 1028 |
| 990 // save this to later make the selection with | 1029 // save this to later make the selection with |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1092 calculateTypingStyleAfterDelete(); | 1131 calculateTypingStyleAfterDelete(); |
| 1093 | 1132 |
| 1094 setEndingSelection(createVisibleSelectionDeprecated( | 1133 setEndingSelection(createVisibleSelectionDeprecated( |
| 1095 m_endingPosition, affinity, endingSelection().isDirectional())); | 1134 m_endingPosition, affinity, endingSelection().isDirectional())); |
| 1096 | 1135 |
| 1097 if (relocatableReferencePosition.position().isNull()) { | 1136 if (relocatableReferencePosition.position().isNull()) { |
| 1098 clearTransientState(); | 1137 clearTransientState(); |
| 1099 return; | 1138 return; |
| 1100 } | 1139 } |
| 1101 | 1140 |
| 1102 // This deletion command is part of a move operation, we need to cleanup after
deletion. | 1141 // This deletion command is part of a move operation, we need to cleanup after |
| 1142 // deletion. |
| 1103 m_referenceMovePosition = relocatableReferencePosition.position(); | 1143 m_referenceMovePosition = relocatableReferencePosition.position(); |
| 1104 // If the node for the destination has been removed as a result of the deletio
n, | 1144 // If the node for the destination has been removed as a result of the |
| 1105 // set the destination to the ending point after the deletion. | 1145 // deletion, set the destination to the ending point after the deletion. |
| 1106 // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectio
nCommand; | 1146 // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in |
| 1107 // selection is empty, leading to null deref | 1147 // ReplaceSelectionCommand; selection is empty, leading to null deref |
| 1108 if (!m_referenceMovePosition.isConnected()) | 1148 if (!m_referenceMovePosition.isConnected()) |
| 1109 m_referenceMovePosition = endingSelection().start(); | 1149 m_referenceMovePosition = endingSelection().start(); |
| 1110 | 1150 |
| 1111 // Move selection shouldn't left empty <li> block. | 1151 // Move selection shouldn't left empty <li> block. |
| 1112 cleanupAfterDeletion( | 1152 cleanupAfterDeletion( |
| 1113 editingState, createVisiblePositionDeprecated(m_referenceMovePosition)); | 1153 editingState, createVisiblePositionDeprecated(m_referenceMovePosition)); |
| 1114 if (editingState->isAborted()) | 1154 if (editingState->isAborted()) |
| 1115 return; | 1155 return; |
| 1116 | 1156 |
| 1117 clearTransientState(); | 1157 clearTransientState(); |
| 1118 } | 1158 } |
| 1119 | 1159 |
| 1120 InputEvent::InputType DeleteSelectionCommand::inputType() const { | 1160 InputEvent::InputType DeleteSelectionCommand::inputType() const { |
| 1121 // |DeleteSelectionCommand| could be used with Cut, Menu Bar deletion and |Typ
ingCommand|. | 1161 // |DeleteSelectionCommand| could be used with Cut, Menu Bar deletion and |
| 1162 // |TypingCommand|. |
| 1122 // 1. Cut and Menu Bar deletion should rely on correct |m_inputType|. | 1163 // 1. Cut and Menu Bar deletion should rely on correct |m_inputType|. |
| 1123 // 2. |TypingCommand| will supply the |inputType()|, so |m_inputType| could de
fault to |InputType::None|. | 1164 // 2. |TypingCommand| will supply the |inputType()|, so |m_inputType| could |
| 1165 // default to |InputType::None|. |
| 1124 return m_inputType; | 1166 return m_inputType; |
| 1125 } | 1167 } |
| 1126 | 1168 |
| 1127 // Normally deletion doesn't preserve the typing style that was present before i
t. For example, | 1169 // Normally deletion doesn't preserve the typing style that was present before |
| 1128 // type a character, Bold, then delete the character and start typing. The Bold
typing style shouldn't | 1170 // it. For example, type a character, Bold, then delete the character and start |
| 1129 // stick around. Deletion should preserve a typing style that *it* sets, howeve
r. | 1171 // typing. The Bold typing style shouldn't stick around. Deletion should |
| 1172 // preserve a typing style that *it* sets, however. |
| 1130 bool DeleteSelectionCommand::preservesTypingStyle() const { | 1173 bool DeleteSelectionCommand::preservesTypingStyle() const { |
| 1131 return m_typingStyle; | 1174 return m_typingStyle; |
| 1132 } | 1175 } |
| 1133 | 1176 |
| 1134 DEFINE_TRACE(DeleteSelectionCommand) { | 1177 DEFINE_TRACE(DeleteSelectionCommand) { |
| 1135 visitor->trace(m_selectionToDelete); | 1178 visitor->trace(m_selectionToDelete); |
| 1136 visitor->trace(m_upstreamStart); | 1179 visitor->trace(m_upstreamStart); |
| 1137 visitor->trace(m_downstreamStart); | 1180 visitor->trace(m_downstreamStart); |
| 1138 visitor->trace(m_upstreamEnd); | 1181 visitor->trace(m_upstreamEnd); |
| 1139 visitor->trace(m_downstreamEnd); | 1182 visitor->trace(m_downstreamEnd); |
| 1140 visitor->trace(m_endingPosition); | 1183 visitor->trace(m_endingPosition); |
| 1141 visitor->trace(m_leadingWhitespace); | 1184 visitor->trace(m_leadingWhitespace); |
| 1142 visitor->trace(m_trailingWhitespace); | 1185 visitor->trace(m_trailingWhitespace); |
| 1143 visitor->trace(m_referenceMovePosition); | 1186 visitor->trace(m_referenceMovePosition); |
| 1144 visitor->trace(m_startBlock); | 1187 visitor->trace(m_startBlock); |
| 1145 visitor->trace(m_endBlock); | 1188 visitor->trace(m_endBlock); |
| 1146 visitor->trace(m_typingStyle); | 1189 visitor->trace(m_typingStyle); |
| 1147 visitor->trace(m_deleteIntoBlockquoteStyle); | 1190 visitor->trace(m_deleteIntoBlockquoteStyle); |
| 1148 visitor->trace(m_startRoot); | 1191 visitor->trace(m_startRoot); |
| 1149 visitor->trace(m_endRoot); | 1192 visitor->trace(m_endRoot); |
| 1150 visitor->trace(m_startTableRow); | 1193 visitor->trace(m_startTableRow); |
| 1151 visitor->trace(m_endTableRow); | 1194 visitor->trace(m_endTableRow); |
| 1152 visitor->trace(m_temporaryPlaceholder); | 1195 visitor->trace(m_temporaryPlaceholder); |
| 1153 CompositeEditCommand::trace(visitor); | 1196 CompositeEditCommand::trace(visitor); |
| 1154 } | 1197 } |
| 1155 | 1198 |
| 1156 } // namespace blink | 1199 } // namespace blink |
| OLD | NEW |