| 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 29 matching lines...) Expand all Loading... |
| 40 #include "core/html/HTMLInputElement.h" | 40 #include "core/html/HTMLInputElement.h" |
| 41 #include "core/html/HTMLTextAreaElement.h" | 41 #include "core/html/HTMLTextAreaElement.h" |
| 42 #include "core/page/EditorClient.h" | 42 #include "core/page/EditorClient.h" |
| 43 #include "core/page/Frame.h" | 43 #include "core/page/Frame.h" |
| 44 #include "core/rendering/RenderTableCell.h" | 44 #include "core/rendering/RenderTableCell.h" |
| 45 | 45 |
| 46 namespace WebCore { | 46 namespace WebCore { |
| 47 | 47 |
| 48 using namespace HTMLNames; | 48 using namespace HTMLNames; |
| 49 | 49 |
| 50 static bool isTableRow(const Node* node) | 50 static bool isTableRow(const Handle<const Node>& node) |
| 51 { | 51 { |
| 52 return node && node->hasTagName(trTag); | 52 return node && node->hasTagName(trTag); |
| 53 } | 53 } |
| 54 | 54 |
| 55 static bool isTableCellEmpty(const Handle<Node>& cell) | 55 static bool isTableCellEmpty(const Handle<Node>& cell) |
| 56 { | 56 { |
| 57 ASSERT(isTableCell(cell.raw())); | 57 ASSERT(isTableCell(cell)); |
| 58 return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPos
itionInNode(cell)); | 58 return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPos
itionInNode(cell)); |
| 59 } | 59 } |
| 60 | 60 |
| 61 static bool isTableRowEmpty(const Handle<Node>& row) | 61 static bool isTableRowEmpty(const Handle<Node>& row) |
| 62 { | 62 { |
| 63 if (!isTableRow(row.raw())) | 63 if (!isTableRow(row)) |
| 64 return false; | 64 return false; |
| 65 | 65 |
| 66 for (Handle<Node> child = row->firstChild(); child; child = child->nextSibli
ng()) { | 66 for (Handle<Node> child = row->firstChild(); child; child = child->nextSibli
ng()) { |
| 67 HandleScope scope; | 67 HandleScope scope; |
| 68 if (isTableCell(child.raw()) && !isTableCellEmpty(child)) | 68 if (isTableCell(child) && !isTableCellEmpty(child)) |
| 69 return false; | 69 return false; |
| 70 } | 70 } |
| 71 | 71 |
| 72 return true; | 72 return true; |
| 73 } | 73 } |
| 74 | 74 |
| 75 DeleteSelectionCommand::DeleteSelectionCommand(const Handle<Document>& document,
bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpec
ialElements, bool sanitizeMarkup) | 75 DeleteSelectionCommand::DeleteSelectionCommand(const Handle<Document>& document,
bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpec
ialElements, bool sanitizeMarkup) |
| 76 : CompositeEditCommand(document) | 76 : CompositeEditCommand(document) |
| 77 , m_hasSelectionToDelete(false) | 77 , m_hasSelectionToDelete(false) |
| 78 , m_smartDelete(smartDelete) | 78 , m_smartDelete(smartDelete) |
| (...skipping 25 matching lines...) Expand all Loading... |
| 104 , m_selectionToDelete(selection) | 104 , m_selectionToDelete(selection) |
| 105 , m_startBlock(0) | 105 , m_startBlock(0) |
| 106 , m_endBlock(0) | 106 , m_endBlock(0) |
| 107 , m_typingStyle(0) | 107 , m_typingStyle(0) |
| 108 , m_deleteIntoBlockquoteStyle(0) | 108 , m_deleteIntoBlockquoteStyle(0) |
| 109 { | 109 { |
| 110 } | 110 } |
| 111 | 111 |
| 112 void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) | 112 void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) |
| 113 { | 113 { |
| 114 Node* startSpecialContainer = 0; | 114 Handle<Node> startSpecialContainer; |
| 115 Node* endSpecialContainer = 0; | 115 Handle<Node> endSpecialContainer; |
| 116 | 116 |
| 117 start = m_selectionToDelete.start(); | 117 start = m_selectionToDelete.start(); |
| 118 end = m_selectionToDelete.end(); | 118 end = m_selectionToDelete.end(); |
| 119 | 119 |
| 120 // For HRs, we'll get a position at (HR,1) when hitting delete from the begi
nning of the previous line, or (HR,0) when forward deleting, | 120 // For HRs, we'll get a position at (HR,1) when hitting delete from the begi
nning of the previous line, or (HR,0) when forward deleting, |
| 121 // but in these cases, we want to delete it, so manually expand the selectio
n | 121 // but in these cases, we want to delete it, so manually expand the selectio
n |
| 122 if (start.deprecatedNode()->hasTagName(hrTag)) | 122 if (start.deprecatedNode()->hasTagName(hrTag)) |
| 123 start = positionBeforeNode(start.deprecatedNode()); | 123 start = positionBeforeNode(start.deprecatedNode()); |
| 124 else if (end.deprecatedNode()->hasTagName(hrTag)) | 124 else if (end.deprecatedNode()->hasTagName(hrTag)) |
| 125 end = positionAfterNode(end.deprecatedNode()); | 125 end = positionAfterNode(end.deprecatedNode()); |
| 126 | 126 |
| 127 // FIXME: This is only used so that moveParagraphs can avoid the bugs in spe
cial element expansion. | 127 // FIXME: This is only used so that moveParagraphs can avoid the bugs in spe
cial element expansion. |
| 128 if (!m_expandForSpecialElements) | 128 if (!m_expandForSpecialElements) |
| 129 return; | 129 return; |
| 130 | 130 |
| 131 while (1) { | 131 while (1) { |
| 132 HandleScope scope; | 132 HandleScope scope; |
| 133 startSpecialContainer = 0; | 133 startSpecialContainer = nullptr; |
| 134 endSpecialContainer = 0; | 134 endSpecialContainer = nullptr; |
| 135 | 135 |
| 136 Position s = positionBeforeContainingSpecialElement(start, &startSpecial
Container); | 136 Position s = positionBeforeContainingSpecialElement(start, &startSpecial
Container); |
| 137 Position e = positionAfterContainingSpecialElement(end, &endSpecialConta
iner); | 137 Position e = positionAfterContainingSpecialElement(end, &endSpecialConta
iner); |
| 138 | 138 |
| 139 if (!startSpecialContainer && !endSpecialContainer) | 139 if (!startSpecialContainer && !endSpecialContainer) |
| 140 break; | 140 break; |
| 141 | 141 |
| 142 if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || Visi
blePosition(end) != m_selectionToDelete.visibleEnd()) | 142 if (VisiblePosition(start) != m_selectionToDelete.visibleStart() || Visi
blePosition(end) != m_selectionToDelete.visibleEnd()) |
| 143 break; | 143 break; |
| 144 | 144 |
| 145 // If we're going to expand to include the startSpecialContainer, it mus
t be fully selected. | 145 // If we're going to expand to include the startSpecialContainer, it mus
t be fully selected. |
| 146 if (startSpecialContainer && !endSpecialContainer && comparePositions(po
sitionInParentAfterNode(adoptRawResult(startSpecialContainer)), end) > -1) | 146 if (startSpecialContainer && !endSpecialContainer && comparePositions(po
sitionInParentAfterNode(startSpecialContainer), end) > -1) |
| 147 break; | 147 break; |
| 148 | 148 |
| 149 // If we're going to expand to include the endSpecialContainer, it must
be fully selected. | 149 // If we're going to expand to include the endSpecialContainer, it must
be fully selected. |
| 150 if (endSpecialContainer && !startSpecialContainer && comparePositions(st
art, positionInParentBeforeNode(adoptRawResult(endSpecialContainer))) > -1) | 150 if (endSpecialContainer && !startSpecialContainer && comparePositions(st
art, positionInParentBeforeNode(endSpecialContainer)) > -1) |
| 151 break; | 151 break; |
| 152 | 152 |
| 153 if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSp
ecialContainer)) | 153 if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSp
ecialContainer.raw())) |
| 154 // Don't adjust the end yet, it is the end of a special element that
contains the start | 154 // Don't adjust the end yet, it is the end of a special element that
contains the start |
| 155 // special element (which may or may not be fully selected). | 155 // special element (which may or may not be fully selected). |
| 156 start = s; | 156 start = s; |
| 157 else if (endSpecialContainer && endSpecialContainer->isDescendantOf(star
tSpecialContainer)) | 157 else if (endSpecialContainer && endSpecialContainer->isDescendantOf(star
tSpecialContainer.raw())) |
| 158 // Don't adjust the start yet, it is the start of a special element
that contains the end | 158 // Don't adjust the start yet, it is the start of a special element
that contains the end |
| 159 // special element (which may or may not be fully selected). | 159 // special element (which may or may not be fully selected). |
| 160 end = e; | 160 end = e; |
| 161 else { | 161 else { |
| 162 start = s; | 162 start = s; |
| 163 end = e; | 163 end = e; |
| 164 } | 164 } |
| 165 } | 165 } |
| 166 } | 166 } |
| 167 | 167 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 185 initializeStartEnd(start, end); | 185 initializeStartEnd(start, end); |
| 186 | 186 |
| 187 m_upstreamStart = start.upstream(); | 187 m_upstreamStart = start.upstream(); |
| 188 m_downstreamStart = start.downstream(); | 188 m_downstreamStart = start.downstream(); |
| 189 m_upstreamEnd = end.upstream(); | 189 m_upstreamEnd = end.upstream(); |
| 190 m_downstreamEnd = end.downstream(); | 190 m_downstreamEnd = end.downstream(); |
| 191 | 191 |
| 192 m_startRoot = editableRootForPosition(start); | 192 m_startRoot = editableRootForPosition(start); |
| 193 m_endRoot = editableRootForPosition(end); | 193 m_endRoot = editableRootForPosition(end); |
| 194 | 194 |
| 195 m_startTableRow = adoptRawResult(enclosingNodeOfType(start, &isTableRow)); | 195 m_startTableRow = enclosingNodeOfType(start, &isTableRow); |
| 196 m_endTableRow = adoptRawResult(enclosingNodeOfType(end, &isTableRow)); | 196 m_endTableRow = enclosingNodeOfType(end, &isTableRow); |
| 197 | 197 |
| 198 // Don't move content out of a table cell. | 198 // Don't move content out of a table cell. |
| 199 // If the cell is non-editable, enclosingNodeOfType won't return it by defau
lt, so | 199 // If the cell is non-editable, enclosingNodeOfType won't return it by defau
lt, so |
| 200 // tell that function that we don't care if it returns non-editable nodes. | 200 // tell that function that we don't care if it returns non-editable nodes. |
| 201 Handle<Node> startCell = adoptRawResult(enclosingNodeOfType(m_upstreamStart,
&isTableCell, CanCrossEditingBoundary)); | 201 Handle<Node> startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell,
CanCrossEditingBoundary); |
| 202 Handle<Node> endCell = adoptRawResult(enclosingNodeOfType(m_downstreamEnd, &
isTableCell, CanCrossEditingBoundary)); | 202 Handle<Node> endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, Ca
nCrossEditingBoundary); |
| 203 // FIXME: This isn't right. A borderless table with two rows and a single c
olumn would appear as two paragraphs. | 203 // FIXME: This isn't right. A borderless table with two rows and a single c
olumn would appear as two paragraphs. |
| 204 if (endCell && endCell != startCell) | 204 if (endCell && endCell != startCell) |
| 205 m_mergeBlocksAfterDelete = false; | 205 m_mergeBlocksAfterDelete = false; |
| 206 | 206 |
| 207 // Usually the start and the end of the selection to delete are pulled toget
her as a result of the deletion. | 207 // Usually the start and the end of the selection to delete are pulled toget
her as a result of the deletion. |
| 208 // Sometimes they aren't (like when no merge is requested), so we must choos
e one position to hold the caret | 208 // Sometimes they aren't (like when no merge is requested), so we must choos
e one position to hold the caret |
| 209 // and receive the placeholder after deletion. | 209 // and receive the placeholder after deletion. |
| 210 VisiblePosition visibleEnd(m_downstreamEnd); | 210 VisiblePosition visibleEnd(m_downstreamEnd); |
| 211 if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd)) | 211 if (m_mergeBlocksAfterDelete && !isEndOfParagraph(visibleEnd)) |
| 212 m_endingPosition = m_downstreamEnd; | 212 m_endingPosition = m_downstreamEnd; |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 265 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd
); | 265 setStartingSelectionOnSmartDelete(m_downstreamStart, m_downstreamEnd
); |
| 266 } | 266 } |
| 267 } | 267 } |
| 268 | 268 |
| 269 // We must pass call parentAnchoredEquivalent on the positions since some ed
iting positions | 269 // We must pass call parentAnchoredEquivalent on the positions since some ed
iting positions |
| 270 // that appear inside their nodes aren't really inside them. [hr, 0] is one
example. | 270 // that appear inside their nodes aren't really inside them. [hr, 0] is one
example. |
| 271 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing
element getters | 271 // FIXME: parentAnchoredEquivalent should eventually be moved into enclosing
element getters |
| 272 // like the one below, since editing functions should obviously accept editi
ng positions. | 272 // like the one below, since editing functions should obviously accept editi
ng positions. |
| 273 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to retu
rn a non-editable | 273 // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to retu
rn a non-editable |
| 274 // node. This was done to match existing behavior, but it seems wrong. | 274 // node. This was done to match existing behavior, but it seems wrong. |
| 275 m_startBlock = adoptRawResult(enclosingNodeOfType(m_downstreamStart.parentAn
choredEquivalent(), &isBlock, CanCrossEditingBoundary)); | 275 m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalen
t(), &isBlock, CanCrossEditingBoundary); |
| 276 m_endBlock = adoptRawResult(enclosingNodeOfType(m_upstreamEnd.parentAnchored
Equivalent(), &isBlock, CanCrossEditingBoundary)); | 276 m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &
isBlock, CanCrossEditingBoundary); |
| 277 } | 277 } |
| 278 | 278 |
| 279 void DeleteSelectionCommand::saveTypingStyleState() | 279 void DeleteSelectionCommand::saveTypingStyleState() |
| 280 { | 280 { |
| 281 // A common case is deleting characters that are all from the same text node
. In | 281 // A common case is deleting characters that are all from the same text node
. In |
| 282 // that case, the style at the start of the selection before deletion will b
e the | 282 // that case, the style at the start of the selection before deletion will b
e the |
| 283 // same as the style at the start of the selection after deletion (since tho
se | 283 // same as the style at the start of the selection after deletion (since tho
se |
| 284 // two positions will be identical). Therefore there is no need to save the | 284 // two positions will be identical). Therefore there is no need to save the |
| 285 // typing style at the start of the selection, nor is there a reason to | 285 // typing style at the start of the selection, nor is there a reason to |
| 286 // compute the style at the start of the selection after deletion (see the | 286 // compute the style at the start of the selection after deletion (see the |
| 287 // early return in calculateTypingStyleAfterDelete). | 287 // early return in calculateTypingStyleAfterDelete). |
| 288 if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() &&
m_upstreamStart.deprecatedNode()->isTextNode()) | 288 if (m_upstreamStart.deprecatedNode() == m_downstreamEnd.deprecatedNode() &&
m_upstreamStart.deprecatedNode()->isTextNode()) |
| 289 return; | 289 return; |
| 290 | 290 |
| 291 // Figure out the typing style in effect before the delete is done. | 291 // Figure out the typing style in effect before the delete is done. |
| 292 m_typingStyle = EditingStyle::create(m_selectionToDelete.start()); | 292 m_typingStyle = EditingStyle::create(m_selectionToDelete.start()); |
| 293 m_typingStyle->removeStyleAddedByNode(adoptRawResult(enclosingAnchorElement(
m_selectionToDelete.start()))); | 293 m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDe
lete.start())); |
| 294 | 294 |
| 295 // If we're deleting into a Mail blockquote, save the style at end() instead
of start() | 295 // If we're deleting into a Mail blockquote, save the style at end() instead
of start() |
| 296 // We'll use this later in computeTypingStyleAfterDelete if we end up outsid
e of a Mail blockquote | 296 // We'll use this later in computeTypingStyleAfterDelete if we end up outsid
e of a Mail blockquote |
| 297 if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote)) | 297 if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote)) |
| 298 m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.e
nd()); | 298 m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.e
nd()); |
| 299 else | 299 else |
| 300 m_deleteIntoBlockquoteStyle = 0; | 300 m_deleteIntoBlockquoteStyle = 0; |
| 301 } | 301 } |
| 302 | 302 |
| 303 bool DeleteSelectionCommand::handleSpecialCaseBRDelete() | 303 bool DeleteSelectionCommand::handleSpecialCaseBRDelete() |
| (...skipping 26 matching lines...) Expand all Loading... |
| 330 } | 330 } |
| 331 | 331 |
| 332 static Position firstEditablePositionInNode(const Handle<Node>& node) | 332 static Position firstEditablePositionInNode(const Handle<Node>& node) |
| 333 { | 333 { |
| 334 ASSERT(node); | 334 ASSERT(node); |
| 335 Handle<Node> next = node; | 335 Handle<Node> next = node; |
| 336 while (next && !next->rendererIsEditable()) { | 336 while (next && !next->rendererIsEditable()) { |
| 337 HandleScope scope; | 337 HandleScope scope; |
| 338 next = NodeTraversal::next(next, node); | 338 next = NodeTraversal::next(next, node); |
| 339 } | 339 } |
| 340 return next ? firstPositionInOrBeforeNode(next.raw()) : Position(); | 340 return next ? firstPositionInOrBeforeNode(next) : Position(); |
| 341 } | 341 } |
| 342 | 342 |
| 343 void DeleteSelectionCommand::removeNode(const Handle<Node>& node, ShouldAssumeCo
ntentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) | 343 void DeleteSelectionCommand::removeNode(const Handle<Node>& node, ShouldAssumeCo
ntentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable) |
| 344 { | 344 { |
| 345 if (!node) | 345 if (!node) |
| 346 return; | 346 return; |
| 347 | 347 |
| 348 if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.raw()) &&
node->isDescendantOf(m_endRoot.raw()))) { | 348 if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.raw()) &&
node->isDescendantOf(m_endRoot.raw()))) { |
| 349 // If a node is not in both the start and end editable roots, remove it
only if its inside an editable region. | 349 // If a node is not in both the start and end editable roots, remove it
only if its inside an editable region. |
| 350 if (!node->parentNode()->rendererIsEditable()) { | 350 if (!node->parentNode()->rendererIsEditable()) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 361 if (nextChild && nextChild->parentNode() != node) | 361 if (nextChild && nextChild->parentNode() != node) |
| 362 return; | 362 return; |
| 363 child = nextChild; | 363 child = nextChild; |
| 364 } | 364 } |
| 365 | 365 |
| 366 // Don't remove editable regions that are inside non-editable ones,
just clear them. | 366 // Don't remove editable regions that are inside non-editable ones,
just clear them. |
| 367 return; | 367 return; |
| 368 } | 368 } |
| 369 } | 369 } |
| 370 | 370 |
| 371 if (isTableStructureNode(node.raw()) || node->isRootEditableElement()) { | 371 if (isTableStructureNode(node) || node->isRootEditableElement()) { |
| 372 // Do not remove an element of table structure; remove its contents. | 372 // Do not remove an element of table structure; remove its contents. |
| 373 // Likewise for the root editable element. | 373 // Likewise for the root editable element. |
| 374 for (Handle<Node> child = node->firstChild(); child; ) { | 374 for (Handle<Node> child = node->firstChild(); child; ) { |
| 375 HandleScope scope; | 375 HandleScope scope; |
| 376 Handle<Node> remove = child; | 376 Handle<Node> remove = child; |
| 377 child = child->nextSibling(); | 377 child = child->nextSibling(); |
| 378 removeNode(remove, shouldAssumeContentIsAlwaysEditable); | 378 removeNode(remove, shouldAssumeContentIsAlwaysEditable); |
| 379 } | 379 } |
| 380 | 380 |
| 381 // Make sure empty cell has some height, if a placeholder can be inserte
d. | 381 // Make sure empty cell has some height, if a placeholder can be inserte
d. |
| 382 document()->updateLayoutIgnorePendingStylesheets(); | 382 document()->updateLayoutIgnorePendingStylesheets(); |
| 383 RenderObject *r = node->renderer(); | 383 RenderObject *r = node->renderer(); |
| 384 if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0)
{ | 384 if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0)
{ |
| 385 Position firstEditablePosition = firstEditablePositionInNode(node); | 385 Position firstEditablePosition = firstEditablePositionInNode(node); |
| 386 if (firstEditablePosition.isNotNull()) | 386 if (firstEditablePosition.isNotNull()) |
| 387 insertBlockPlaceholder(firstEditablePosition); | 387 insertBlockPlaceholder(firstEditablePosition); |
| 388 } | 388 } |
| 389 return; | 389 return; |
| 390 } | 390 } |
| 391 | 391 |
| 392 if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNod
e(m_startBlock)).previous())) | 392 if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNod
e(m_startBlock)).previous())) |
| 393 m_needPlaceholder = true; | 393 m_needPlaceholder = true; |
| 394 else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionI
nNode(m_startBlock)).next())) | 394 else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionI
nNode(m_startBlock)).next())) |
| 395 m_needPlaceholder = true; | 395 m_needPlaceholder = true; |
| 396 | 396 |
| 397 // FIXME: Update the endpoints of the range being deleted. | 397 // FIXME: Update the endpoints of the range being deleted. |
| 398 updatePositionForNodeRemoval(m_endingPosition, node.raw()); | 398 updatePositionForNodeRemoval(m_endingPosition, node); |
| 399 updatePositionForNodeRemoval(m_leadingWhitespace, node.raw()); | 399 updatePositionForNodeRemoval(m_leadingWhitespace, node); |
| 400 updatePositionForNodeRemoval(m_trailingWhitespace, node.raw()); | 400 updatePositionForNodeRemoval(m_trailingWhitespace, node); |
| 401 | 401 |
| 402 CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable); | 402 CompositeEditCommand::removeNode(node, shouldAssumeContentIsAlwaysEditable); |
| 403 } | 403 } |
| 404 | 404 |
| 405 static void updatePositionForTextRemoval(const Handle<Node>& node, int offset, i
nt count, Position& position) | 405 static void updatePositionForTextRemoval(const Handle<Node>& node, int offset, i
nt count, Position& position) |
| 406 { | 406 { |
| 407 if (position.anchorType() != Position::PositionIsOffsetInAnchor || position.
containerNode() != node) | 407 if (position.anchorType() != Position::PositionIsOffsetInAnchor || position.
containerNode() != node) |
| 408 return; | 408 return; |
| 409 | 409 |
| 410 if (position.offsetInContainerNode() > offset + count) | 410 if (position.offsetInContainerNode() > offset + count) |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 445 { | 445 { |
| 446 if (m_upstreamStart.isNull()) | 446 if (m_upstreamStart.isNull()) |
| 447 return; | 447 return; |
| 448 | 448 |
| 449 int startOffset = m_upstreamStart.deprecatedEditingOffset(); | 449 int startOffset = m_upstreamStart.deprecatedEditingOffset(); |
| 450 Handle<Node> startNode = m_upstreamStart.deprecatedNode(); | 450 Handle<Node> startNode = m_upstreamStart.deprecatedNode(); |
| 451 | 451 |
| 452 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss(); | 452 makeStylingElementsDirectChildrenOfEditableRootToPreventStyleLoss(); |
| 453 | 453 |
| 454 // Never remove the start block unless it's a table, in which case we won't
merge content in. | 454 // Never remove the start block unless it's a table, in which case we won't
merge content in. |
| 455 if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditi
ng(startNode.raw()) && !startNode->hasTagName(tableTag)) { | 455 if (startNode == m_startBlock && startOffset == 0 && canHaveChildrenForEditi
ng(startNode) && !startNode->hasTagName(tableTag)) { |
| 456 startOffset = 0; | 456 startOffset = 0; |
| 457 startNode = NodeTraversal::next(startNode); | 457 startNode = NodeTraversal::next(startNode); |
| 458 if (!startNode) | 458 if (!startNode) |
| 459 return; | 459 return; |
| 460 } | 460 } |
| 461 | 461 |
| 462 if (startOffset >= caretMaxOffset(startNode.raw()) && startNode->isTextNode(
)) { | 462 if (startOffset >= caretMaxOffset(startNode) && startNode->isTextNode()) { |
| 463 Handle<Text> text = toText(startNode); | 463 Handle<Text> text = toText(startNode); |
| 464 if (text->length() > (unsigned)caretMaxOffset(startNode.raw())) | 464 if (text->length() > (unsigned)caretMaxOffset(startNode)) |
| 465 deleteTextFromNode(text, caretMaxOffset(startNode.raw()), text->leng
th() - caretMaxOffset(startNode.raw())); | 465 deleteTextFromNode(text, caretMaxOffset(startNode), text->length() -
caretMaxOffset(startNode)); |
| 466 } | 466 } |
| 467 | 467 |
| 468 if (startOffset >= lastOffsetForEditing(startNode.raw())) { | 468 if (startOffset >= lastOffsetForEditing(startNode)) { |
| 469 startNode = NodeTraversal::nextSkippingChildren(startNode); | 469 startNode = NodeTraversal::nextSkippingChildren(startNode); |
| 470 startOffset = 0; | 470 startOffset = 0; |
| 471 } | 471 } |
| 472 | 472 |
| 473 // Done adjusting the start. See if we're all done. | 473 // Done adjusting the start. See if we're all done. |
| 474 if (!startNode) | 474 if (!startNode) |
| 475 return; | 475 return; |
| 476 | 476 |
| 477 if (startNode == m_downstreamEnd.deprecatedNode()) { | 477 if (startNode == m_downstreamEnd.deprecatedNode()) { |
| 478 if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) { | 478 if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 505 node = startNode->childNode(startOffset); | 505 node = startNode->childNode(startOffset); |
| 506 } | 506 } |
| 507 } else if (startNode == m_upstreamEnd.deprecatedNode() && startNode->isT
extNode()) { | 507 } else if (startNode == m_upstreamEnd.deprecatedNode() && startNode->isT
extNode()) { |
| 508 Handle<Text> text = toText(m_upstreamEnd.deprecatedNode()); | 508 Handle<Text> text = toText(m_upstreamEnd.deprecatedNode()); |
| 509 deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset())
; | 509 deleteTextFromNode(text, 0, m_upstreamEnd.deprecatedEditingOffset())
; |
| 510 } | 510 } |
| 511 | 511 |
| 512 // handle deleting all nodes that are completely selected | 512 // handle deleting all nodes that are completely selected |
| 513 while (node && node != m_downstreamEnd.deprecatedNode()) { | 513 while (node && node != m_downstreamEnd.deprecatedNode()) { |
| 514 HandleScope scope; | 514 HandleScope scope; |
| 515 if (comparePositions(firstPositionInOrBeforeNode(node.raw()), m_down
streamEnd) >= 0) { | 515 if (comparePositions(firstPositionInOrBeforeNode(node), m_downstream
End) >= 0) { |
| 516 // NodeTraversal::nextSkippingChildren just blew past the end po
sition, so stop deleting | 516 // NodeTraversal::nextSkippingChildren just blew past the end po
sition, so stop deleting |
| 517 node = nullptr; | 517 node = nullptr; |
| 518 } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.ra
w())) { | 518 } else if (!m_downstreamEnd.deprecatedNode()->isDescendantOf(node.ra
w())) { |
| 519 Handle<Node> nextNode = NodeTraversal::nextSkippingChildren(node
); | 519 Handle<Node> nextNode = NodeTraversal::nextSkippingChildren(node
); |
| 520 // if we just removed a node from the end container, update end
position so the | 520 // if we just removed a node from the end container, update end
position so the |
| 521 // check above will work | 521 // check above will work |
| 522 updatePositionForNodeRemoval(m_downstreamEnd, node.raw()); | 522 updatePositionForNodeRemoval(m_downstreamEnd, node); |
| 523 removeNode(node); | 523 removeNode(node); |
| 524 node = nextNode; | 524 node = nextNode; |
| 525 } else { | 525 } else { |
| 526 Handle<Node> n = node->lastDescendant(); | 526 Handle<Node> n = node->lastDescendant(); |
| 527 if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.dep
recatedEditingOffset() >= caretMaxOffset(n.raw())) { | 527 if (m_downstreamEnd.deprecatedNode() == n && m_downstreamEnd.dep
recatedEditingOffset() >= caretMaxOffset(n)) { |
| 528 removeNode(node); | 528 removeNode(node); |
| 529 node = nullptr; | 529 node = nullptr; |
| 530 } else | 530 } else |
| 531 node = NodeTraversal::next(node); | 531 node = NodeTraversal::next(node); |
| 532 } | 532 } |
| 533 } | 533 } |
| 534 | 534 |
| 535 if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.de
precatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode().handle().raw())
&& m_downstreamEnd.anchorNode()->inDocument() && m_downstreamEnd.deprecatedEditi
ngOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode().handle().raw())) { | 535 if (m_downstreamEnd.deprecatedNode() != startNode && !m_upstreamStart.de
precatedNode()->isDescendantOf(m_downstreamEnd.deprecatedNode().handle().raw())
&& m_downstreamEnd.anchorNode()->inDocument() && m_downstreamEnd.deprecatedEditi
ngOffset() >= caretMinOffset(m_downstreamEnd.deprecatedNode())) { |
| 536 if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildr
enForEditing(m_downstreamEnd.deprecatedNode().handle().raw())) { | 536 if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildr
enForEditing(m_downstreamEnd.deprecatedNode())) { |
| 537 // The node itself is fully selected, not just its contents. De
lete it. | 537 // The node itself is fully selected, not just its contents. De
lete it. |
| 538 removeNode(m_downstreamEnd.deprecatedNode()); | 538 removeNode(m_downstreamEnd.deprecatedNode()); |
| 539 } else { | 539 } else { |
| 540 if (m_downstreamEnd.deprecatedNode()->isTextNode()) { | 540 if (m_downstreamEnd.deprecatedNode()->isTextNode()) { |
| 541 // in a text node that needs to be trimmed | 541 // in a text node that needs to be trimmed |
| 542 Handle<Text> text = toText(m_downstreamEnd.deprecatedNode())
; | 542 Handle<Text> text = toText(m_downstreamEnd.deprecatedNode())
; |
| 543 if (m_downstreamEnd.deprecatedEditingOffset() > 0) { | 543 if (m_downstreamEnd.deprecatedEditingOffset() > 0) { |
| 544 deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEd
itingOffset()); | 544 deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEd
itingOffset()); |
| 545 } | 545 } |
| 546 // Remove children of m_downstreamEnd.deprecatedNode() that come
after m_upstreamStart. | 546 // Remove children of m_downstreamEnd.deprecatedNode() that come
after m_upstreamStart. |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 612 | 612 |
| 613 // There's nothing to merge. | 613 // There's nothing to merge. |
| 614 if (m_upstreamStart == m_downstreamEnd) | 614 if (m_upstreamStart == m_downstreamEnd) |
| 615 return; | 615 return; |
| 616 | 616 |
| 617 VisiblePosition startOfParagraphToMove(m_downstreamEnd); | 617 VisiblePosition startOfParagraphToMove(m_downstreamEnd); |
| 618 VisiblePosition mergeDestination(m_upstreamStart); | 618 VisiblePosition mergeDestination(m_upstreamStart); |
| 619 | 619 |
| 620 // m_downstreamEnd's block has been emptied out by deletion. There is no co
ntent inside of it to | 620 // m_downstreamEnd's block has been emptied out by deletion. There is no co
ntent inside of it to |
| 621 // move, so just remove it. | 621 // move, so just remove it. |
| 622 Handle<Element> endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode().h
andle().raw()); | 622 Handle<Element> endBlock = enclosingBlock(m_downstreamEnd.deprecatedNode()); |
| 623 if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent()
.deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode())
{ | 623 if (!endBlock || !endBlock->contains(startOfParagraphToMove.deepEquivalent()
.deprecatedNode()) || !startOfParagraphToMove.deepEquivalent().deprecatedNode())
{ |
| 624 removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode().handle().raw(
))); | 624 removeNode(enclosingBlock(m_downstreamEnd.deprecatedNode())); |
| 625 return; | 625 return; |
| 626 } | 626 } |
| 627 | 627 |
| 628 // We need to merge into m_upstreamStart's block, but it's been emptied out
and collapsed by deletion. | 628 // We need to merge into m_upstreamStart's block, but it's been emptied out
and collapsed by deletion. |
| 629 if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination
.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStar
t.containerNode().handle().raw()).handle().raw()) || m_startsAtEmptyLine) { | 629 if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination
.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStar
t.containerNode()).handle().raw()) || m_startsAtEmptyLine) { |
| 630 insertNodeAt(createBreakElement(document()), m_upstreamStart); | 630 insertNodeAt(createBreakElement(document()), m_upstreamStart); |
| 631 mergeDestination = VisiblePosition(m_upstreamStart); | 631 mergeDestination = VisiblePosition(m_upstreamStart); |
| 632 } | 632 } |
| 633 | 633 |
| 634 if (mergeDestination == startOfParagraphToMove) | 634 if (mergeDestination == startOfParagraphToMove) |
| 635 return; | 635 return; |
| 636 | 636 |
| 637 VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove
); | 637 VisiblePosition endOfParagraphToMove = endOfParagraph(startOfParagraphToMove
); |
| 638 | 638 |
| 639 if (mergeDestination == endOfParagraphToMove) | 639 if (mergeDestination == endOfParagraphToMove) |
| 640 return; | 640 return; |
| 641 | 641 |
| 642 // The rule for merging into an empty block is: only do so if its farther to
the right. | 642 // The rule for merging into an empty block is: only do so if its farther to
the right. |
| 643 // FIXME: Consider RTL. | 643 // FIXME: Consider RTL. |
| 644 if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfP
aragraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds(
).x()) { | 644 if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfP
aragraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds(
).x()) { |
| 645 if (mergeDestination.deepEquivalent().downstream().deprecatedNode()->has
TagName(brTag)) { | 645 if (mergeDestination.deepEquivalent().downstream().deprecatedNode()->has
TagName(brTag)) { |
| 646 removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downst
ream().deprecatedNode()); | 646 removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downst
ream().deprecatedNode()); |
| 647 m_endingPosition = startOfParagraphToMove.deepEquivalent(); | 647 m_endingPosition = startOfParagraphToMove.deepEquivalent(); |
| 648 return; | 648 return; |
| 649 } | 649 } |
| 650 } | 650 } |
| 651 | 651 |
| 652 // Block images, tables and horizontal rules cannot be made inline with cont
ent at mergeDestination. If there is | 652 // Block images, tables and horizontal rules cannot be made inline with cont
ent at mergeDestination. If there is |
| 653 // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the c
aret to just before the selection we deleted. | 653 // any (!isStartOfParagraph(mergeDestination)), don't merge, just move the c
aret to just before the selection we deleted. |
| 654 // See https://bugs.webkit.org/show_bug.cgi?id=25439 | 654 // See https://bugs.webkit.org/show_bug.cgi?id=25439 |
| 655 if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalen
t().deprecatedNode().handle().raw()) && !isStartOfParagraph(mergeDestination)) { | 655 if (isRenderedAsNonInlineTableImageOrHR(startOfParagraphToMove.deepEquivalen
t().deprecatedNode()) && !isStartOfParagraph(mergeDestination)) { |
| 656 m_endingPosition = m_upstreamStart; | 656 m_endingPosition = m_upstreamStart; |
| 657 return; | 657 return; |
| 658 } | 658 } |
| 659 | 659 |
| 660 Handle<Range> range = Range::create(document(), startOfParagraphToMove.deepE
quivalent().parentAnchoredEquivalent(), endOfParagraphToMove.deepEquivalent().pa
rentAnchoredEquivalent()); | 660 Handle<Range> range = Range::create(document(), startOfParagraphToMove.deepE
quivalent().parentAnchoredEquivalent(), endOfParagraphToMove.deepEquivalent().pa
rentAnchoredEquivalent()); |
| 661 Handle<Range> rangeToBeReplaced = Range::create(document(), mergeDestination
.deepEquivalent().parentAnchoredEquivalent(), mergeDestination.deepEquivalent().
parentAnchoredEquivalent()); | 661 Handle<Range> rangeToBeReplaced = Range::create(document(), mergeDestination
.deepEquivalent().parentAnchoredEquivalent(), mergeDestination.deepEquivalent().
parentAnchoredEquivalent()); |
| 662 if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(ran
ge, rangeToBeReplaced)) | 662 if (!document()->frame()->editor()->client()->shouldMoveRangeAfterDelete(ran
ge, rangeToBeReplaced)) |
| 663 return; | 663 return; |
| 664 | 664 |
| 665 // moveParagraphs will insert placeholders if it removes blocks that would r
equire their use, don't let block | 665 // moveParagraphs will insert placeholders if it removes blocks that would r
equire their use, don't let block |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 815 EAffinity affinity = m_selectionToDelete.affinity(); | 815 EAffinity affinity = m_selectionToDelete.affinity(); |
| 816 | 816 |
| 817 Position downstreamEnd = m_selectionToDelete.end().downstream(); | 817 Position downstreamEnd = m_selectionToDelete.end().downstream(); |
| 818 m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart(), C
anCrossEditingBoundary) | 818 m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart(), C
anCrossEditingBoundary) |
| 819 && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditin
gBoundary) | 819 && isEndOfParagraph(m_selectionToDelete.visibleEnd(), CanCrossEditin
gBoundary) |
| 820 && !lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd(
)); | 820 && !lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd(
)); |
| 821 if (m_needPlaceholder) { | 821 if (m_needPlaceholder) { |
| 822 // Don't need a placeholder when deleting a selection that starts just b
efore a table | 822 // Don't need a placeholder when deleting a selection that starts just b
efore a table |
| 823 // and ends inside it (we do need placeholders to hold open empty cells,
but that's | 823 // and ends inside it (we do need placeholders to hold open empty cells,
but that's |
| 824 // handled elsewhere). | 824 // handled elsewhere). |
| 825 if (Handle<Node> table = adoptRawResult(isLastPositionBeforeTable(m_sele
ctionToDelete.visibleStart()))) | 825 if (Handle<Node> table = isLastPositionBeforeTable(m_selectionToDelete.v
isibleStart())) |
| 826 if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table
.raw())) | 826 if (m_selectionToDelete.end().deprecatedNode()->isDescendantOf(table
.raw())) |
| 827 m_needPlaceholder = false; | 827 m_needPlaceholder = false; |
| 828 } | 828 } |
| 829 | 829 |
| 830 | 830 |
| 831 // set up our state | 831 // set up our state |
| 832 initializePositionData(); | 832 initializePositionData(); |
| 833 | 833 |
| 834 // Delete any text that may hinder our ability to fixup whitespace after the
delete | 834 // Delete any text that may hinder our ability to fixup whitespace after the
delete |
| 835 deleteInsignificantTextDownstream(m_trailingWhitespace); | 835 deleteInsignificantTextDownstream(m_trailingWhitespace); |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 885 | 885 |
| 886 // Normally deletion doesn't preserve the typing style that was present before i
t. For example, | 886 // Normally deletion doesn't preserve the typing style that was present before i
t. For example, |
| 887 // type a character, Bold, then delete the character and start typing. The Bold
typing style shouldn't | 887 // type a character, Bold, then delete the character and start typing. The Bold
typing style shouldn't |
| 888 // stick around. Deletion should preserve a typing style that *it* sets, howeve
r. | 888 // stick around. Deletion should preserve a typing style that *it* sets, howeve
r. |
| 889 bool DeleteSelectionCommand::preservesTypingStyle() const | 889 bool DeleteSelectionCommand::preservesTypingStyle() const |
| 890 { | 890 { |
| 891 return m_typingStyle; | 891 return m_typingStyle; |
| 892 } | 892 } |
| 893 | 893 |
| 894 } // namespace WebCore | 894 } // namespace WebCore |
| OLD | NEW |