| 1 | /* |
| 2 | * Copyright (C) 2004-2007, 2016 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "Editing.h" |
| 28 | |
| 29 | #include "AXObjectCache.h" |
| 30 | #include "Document.h" |
| 31 | #include "Editor.h" |
| 32 | #include "Frame.h" |
| 33 | #include "HTMLBodyElement.h" |
| 34 | #include "HTMLDListElement.h" |
| 35 | #include "HTMLDivElement.h" |
| 36 | #include "HTMLElementFactory.h" |
| 37 | #include "HTMLInterchange.h" |
| 38 | #include "HTMLLIElement.h" |
| 39 | #include "HTMLNames.h" |
| 40 | #include "HTMLOListElement.h" |
| 41 | #include "HTMLParagraphElement.h" |
| 42 | #include "HTMLSpanElement.h" |
| 43 | #include "HTMLTableElement.h" |
| 44 | #include "HTMLTextFormControlElement.h" |
| 45 | #include "HTMLUListElement.h" |
| 46 | #include "NodeTraversal.h" |
| 47 | #include "PositionIterator.h" |
| 48 | #include "RenderBlock.h" |
| 49 | #include "RenderElement.h" |
| 50 | #include "RenderTableCell.h" |
| 51 | #include "ShadowRoot.h" |
| 52 | #include "Text.h" |
| 53 | #include "TextIterator.h" |
| 54 | #include "VisibleUnits.h" |
| 55 | #include <wtf/Assertions.h> |
| 56 | #include <wtf/StdLibExtras.h> |
| 57 | #include <wtf/text/StringBuilder.h> |
| 58 | #include <wtf/unicode/CharacterNames.h> |
| 59 | |
| 60 | namespace WebCore { |
| 61 | |
| 62 | using namespace HTMLNames; |
| 63 | |
| 64 | static bool isVisiblyAdjacent(const Position&, const Position&); |
| 65 | |
| 66 | bool canHaveChildrenForEditing(const Node& node) |
| 67 | { |
| 68 | return !is<Text>(node) && node.canContainRangeEndPoint(); |
| 69 | } |
| 70 | |
| 71 | // Atomic means that the node has no children, or has children which are ignored for the purposes of editing. |
| 72 | bool isAtomicNode(const Node* node) |
| 73 | { |
| 74 | return node && (!node->hasChildNodes() || editingIgnoresContent(*node)); |
| 75 | } |
| 76 | |
| 77 | // Compare two positions, taking into account the possibility that one or both |
| 78 | // could be inside a shadow tree. Only works for non-null values. |
| 79 | int comparePositions(const Position& a, const Position& b) |
| 80 | { |
| 81 | TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode()); |
| 82 | |
| 83 | if (!commonScope) |
| 84 | return 0; |
| 85 | |
| 86 | Node* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode()); |
| 87 | ASSERT(nodeA); |
| 88 | bool hasDescendentA = nodeA != a.containerNode(); |
| 89 | int offsetA = hasDescendentA ? 0 : a.computeOffsetInContainerNode(); |
| 90 | |
| 91 | Node* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode()); |
| 92 | ASSERT(nodeB); |
| 93 | bool hasDescendentB = nodeB != b.containerNode(); |
| 94 | int offsetB = hasDescendentB ? 0 : b.computeOffsetInContainerNode(); |
| 95 | |
| 96 | int bias = 0; |
| 97 | if (nodeA == nodeB) { |
| 98 | if (hasDescendentA) |
| 99 | bias = -1; |
| 100 | else if (hasDescendentB) |
| 101 | bias = 1; |
| 102 | } |
| 103 | |
| 104 | auto comparisonResult = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB); |
| 105 | if (comparisonResult.hasException()) |
| 106 | return bias; |
| 107 | auto result = comparisonResult.releaseReturnValue(); |
| 108 | return result ? result : bias; |
| 109 | } |
| 110 | |
| 111 | int comparePositions(const VisiblePosition& a, const VisiblePosition& b) |
| 112 | { |
| 113 | return comparePositions(a.deepEquivalent(), b.deepEquivalent()); |
| 114 | } |
| 115 | |
| 116 | ContainerNode* highestEditableRoot(const Position& position, EditableType editableType) |
| 117 | { |
| 118 | ContainerNode* highestEditableRoot = editableRootForPosition(position, editableType); |
| 119 | if (!highestEditableRoot) |
| 120 | return nullptr; |
| 121 | |
| 122 | for (ContainerNode* node = highestEditableRoot; !is<HTMLBodyElement>(*node); ) { |
| 123 | node = node->parentNode(); |
| 124 | if (!node) |
| 125 | break; |
| 126 | // FIXME: Can this ever be a Document or DocumentFragment? If not, this should return Element* instead. |
| 127 | if (hasEditableStyle(*node, editableType)) |
| 128 | highestEditableRoot = node; |
| 129 | } |
| 130 | |
| 131 | return highestEditableRoot; |
| 132 | } |
| 133 | |
| 134 | Element* lowestEditableAncestor(Node* node) |
| 135 | { |
| 136 | for (; node; node = node->parentNode()) { |
| 137 | if (node->hasEditableStyle()) |
| 138 | return node->rootEditableElement(); |
| 139 | if (is<HTMLBodyElement>(*node)) |
| 140 | break; |
| 141 | } |
| 142 | return nullptr; |
| 143 | } |
| 144 | |
| 145 | static bool isEditableToAccessibility(const Node& node) |
| 146 | { |
| 147 | ASSERT(AXObjectCache::accessibilityEnabled()); |
| 148 | ASSERT(node.document().existingAXObjectCache()); |
| 149 | |
| 150 | if (auto* cache = node.document().existingAXObjectCache()) |
| 151 | return cache->rootAXEditableElement(&node); |
| 152 | |
| 153 | return false; |
| 154 | } |
| 155 | |
| 156 | static bool computeEditability(const Node& node, EditableType editableType, Node::ShouldUpdateStyle shouldUpdateStyle) |
| 157 | { |
| 158 | if (node.computeEditability(Node::UserSelectAllIsAlwaysNonEditable, shouldUpdateStyle) != Node::Editability::ReadOnly) |
| 159 | return true; |
| 160 | |
| 161 | switch (editableType) { |
| 162 | case ContentIsEditable: |
| 163 | return false; |
| 164 | case HasEditableAXRole: |
| 165 | return isEditableToAccessibility(node); |
| 166 | } |
| 167 | ASSERT_NOT_REACHED(); |
| 168 | return false; |
| 169 | } |
| 170 | |
| 171 | bool hasEditableStyle(const Node& node, EditableType editableType) |
| 172 | { |
| 173 | return computeEditability(node, editableType, Node::ShouldUpdateStyle::DoNotUpdate); |
| 174 | } |
| 175 | |
| 176 | bool isEditableNode(const Node& node) |
| 177 | { |
| 178 | return computeEditability(node, ContentIsEditable, Node::ShouldUpdateStyle::Update); |
| 179 | } |
| 180 | |
| 181 | bool isEditablePosition(const Position& position, EditableType editableType) |
| 182 | { |
| 183 | Node* node = position.containerNode(); |
| 184 | return node && computeEditability(*node, editableType, Node::ShouldUpdateStyle::Update); |
| 185 | } |
| 186 | |
| 187 | bool isAtUnsplittableElement(const Position& position) |
| 188 | { |
| 189 | Node* node = position.containerNode(); |
| 190 | return node == editableRootForPosition(position) || node == enclosingNodeOfType(position, isTableCell); |
| 191 | } |
| 192 | |
| 193 | bool isRichlyEditablePosition(const Position& position) |
| 194 | { |
| 195 | auto* node = position.containerNode(); |
| 196 | return node && node->hasRichlyEditableStyle(); |
| 197 | } |
| 198 | |
| 199 | Element* editableRootForPosition(const Position& position, EditableType editableType) |
| 200 | { |
| 201 | Node* node = position.containerNode(); |
| 202 | if (!node) |
| 203 | return nullptr; |
| 204 | |
| 205 | switch (editableType) { |
| 206 | case HasEditableAXRole: |
| 207 | if (auto* cache = node->document().existingAXObjectCache()) |
| 208 | return const_cast<Element*>(cache->rootAXEditableElement(node)); |
| 209 | FALLTHROUGH; |
| 210 | case ContentIsEditable: |
| 211 | return node->rootEditableElement(); |
| 212 | } |
| 213 | return nullptr; |
| 214 | } |
| 215 | |
| 216 | // Finds the enclosing element until which the tree can be split. |
| 217 | // When a user hits ENTER, he/she won't expect this element to be split into two. |
| 218 | // You may pass it as the second argument of splitTreeToNode. |
| 219 | Element* unsplittableElementForPosition(const Position& position) |
| 220 | { |
| 221 | // Since enclosingNodeOfType won't search beyond the highest root editable node, |
| 222 | // this code works even if the closest table cell was outside of the root editable node. |
| 223 | if (auto* enclosingCell = downcast<Element>(enclosingNodeOfType(position, &isTableCell))) |
| 224 | return enclosingCell; |
| 225 | return editableRootForPosition(position); |
| 226 | } |
| 227 | |
| 228 | Position nextCandidate(const Position& position) |
| 229 | { |
| 230 | for (PositionIterator nextPosition = position; !nextPosition.atEnd(); ) { |
| 231 | nextPosition.increment(); |
| 232 | if (nextPosition.isCandidate()) |
| 233 | return nextPosition; |
| 234 | } |
| 235 | return { }; |
| 236 | } |
| 237 | |
| 238 | Position nextVisuallyDistinctCandidate(const Position& position) |
| 239 | { |
| 240 | // FIXME: Use PositionIterator instead. |
| 241 | Position nextPosition = position; |
| 242 | Position downstreamStart = nextPosition.downstream(); |
| 243 | while (!nextPosition.atEndOfTree()) { |
| 244 | nextPosition = nextPosition.next(Character); |
| 245 | if (nextPosition.isCandidate() && nextPosition.downstream() != downstreamStart) |
| 246 | return nextPosition; |
| 247 | if (auto* node = nextPosition.containerNode()) { |
| 248 | if (!node->renderer()) |
| 249 | nextPosition = lastPositionInOrAfterNode(node); |
| 250 | } |
| 251 | } |
| 252 | return { }; |
| 253 | } |
| 254 | |
| 255 | Position previousCandidate(const Position& position) |
| 256 | { |
| 257 | PositionIterator previousPosition = position; |
| 258 | while (!previousPosition.atStart()) { |
| 259 | previousPosition.decrement(); |
| 260 | if (previousPosition.isCandidate()) |
| 261 | return previousPosition; |
| 262 | } |
| 263 | return { }; |
| 264 | } |
| 265 | |
| 266 | Position previousVisuallyDistinctCandidate(const Position& position) |
| 267 | { |
| 268 | // FIXME: Use PositionIterator instead. |
| 269 | Position previousPosition = position; |
| 270 | Position downstreamStart = previousPosition.downstream(); |
| 271 | while (!previousPosition.atStartOfTree()) { |
| 272 | previousPosition = previousPosition.previous(Character); |
| 273 | if (previousPosition.isCandidate() && previousPosition.downstream() != downstreamStart) |
| 274 | return previousPosition; |
| 275 | if (auto* node = previousPosition.containerNode()) { |
| 276 | if (!node->renderer()) |
| 277 | previousPosition = firstPositionInOrBeforeNode(node); |
| 278 | } |
| 279 | } |
| 280 | return { }; |
| 281 | } |
| 282 | |
| 283 | Position firstEditablePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot) |
| 284 | { |
| 285 | if (!highestRoot) |
| 286 | return { }; |
| 287 | |
| 288 | // position falls before highestRoot. |
| 289 | if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->hasEditableStyle()) |
| 290 | return firstPositionInNode(highestRoot); |
| 291 | |
| 292 | Position candidate = position; |
| 293 | |
| 294 | if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { |
| 295 | auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); |
| 296 | if (!shadowAncestor) |
| 297 | return { }; |
| 298 | |
| 299 | candidate = positionAfterNode(shadowAncestor); |
| 300 | } |
| 301 | |
| 302 | while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) |
| 303 | candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentAfterNode(candidate.deprecatedNode()) : nextVisuallyDistinctCandidate(candidate); |
| 304 | |
| 305 | if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) |
| 306 | return { }; |
| 307 | |
| 308 | return candidate; |
| 309 | } |
| 310 | |
| 311 | Position lastEditablePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot) |
| 312 | { |
| 313 | if (!highestRoot) |
| 314 | return { }; |
| 315 | |
| 316 | // When position falls after highestRoot, the result is easy to compute. |
| 317 | if (comparePositions(position, lastPositionInNode(highestRoot)) == 1) |
| 318 | return lastPositionInNode(highestRoot); |
| 319 | |
| 320 | Position candidate = position; |
| 321 | |
| 322 | if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { |
| 323 | auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); |
| 324 | if (!shadowAncestor) |
| 325 | return { }; |
| 326 | |
| 327 | candidate = firstPositionInOrBeforeNode(shadowAncestor); |
| 328 | } |
| 329 | |
| 330 | while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) |
| 331 | candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentBeforeNode(candidate.deprecatedNode()) : previousVisuallyDistinctCandidate(candidate); |
| 332 | |
| 333 | if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) |
| 334 | return { }; |
| 335 | |
| 336 | return candidate; |
| 337 | } |
| 338 | |
| 339 | // FIXME: The function name, comment, and code say three different things here! |
| 340 | // Whether or not content before and after this node will collapse onto the same line as it. |
| 341 | bool isBlock(const Node* node) |
| 342 | { |
| 343 | return node && node->renderer() && !node->renderer()->isInline() && !node->renderer()->isRubyText(); |
| 344 | } |
| 345 | |
| 346 | bool isInline(const Node* node) |
| 347 | { |
| 348 | return node && node->renderer() && node->renderer()->isInline(); |
| 349 | } |
| 350 | |
| 351 | // FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used. |
| 352 | // FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the |
| 353 | // block that contains the table and not the table, and this function should be the only one responsible for |
| 354 | // knowing about these kinds of special cases. |
| 355 | Element* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule) |
| 356 | { |
| 357 | Node* enclosingNode = enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule); |
| 358 | return is<Element>(enclosingNode) ? downcast<Element>(enclosingNode) : nullptr; |
| 359 | } |
| 360 | |
| 361 | TextDirection directionOfEnclosingBlock(const Position& position) |
| 362 | { |
| 363 | auto block = enclosingBlock(position.containerNode()); |
| 364 | if (!block) |
| 365 | return TextDirection::LTR; |
| 366 | auto renderer = block->renderer(); |
| 367 | if (!renderer) |
| 368 | return TextDirection::LTR; |
| 369 | return renderer->style().direction(); |
| 370 | } |
| 371 | |
| 372 | // This method is used to create positions in the DOM. It returns the maximum valid offset |
| 373 | // in a node. It returns 1 for some elements even though they do not have children, which |
| 374 | // creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent |
| 375 | // on a Position before using it to create a DOM Range, or an exception will be thrown. |
| 376 | int lastOffsetForEditing(const Node& node) |
| 377 | { |
| 378 | if (node.isCharacterDataNode()) |
| 379 | return node.maxCharacterOffset(); |
| 380 | |
| 381 | if (node.hasChildNodes()) |
| 382 | return node.countChildNodes(); |
| 383 | |
| 384 | // NOTE: This should preempt the countChildNodes() for, e.g., select nodes. |
| 385 | // FIXME: What does the comment above mean? |
| 386 | if (editingIgnoresContent(node)) |
| 387 | return 1; |
| 388 | |
| 389 | return 0; |
| 390 | } |
| 391 | |
| 392 | bool isAmbiguousBoundaryCharacter(UChar character) |
| 393 | { |
| 394 | // These are characters that can behave as word boundaries, but can appear within words. |
| 395 | // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed. |
| 396 | // FIXME: this is required until <rdar://problem/6853027> is fixed and text checking can do this for us. |
| 397 | return character == '\'' || character == '@' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; |
| 398 | } |
| 399 | |
| 400 | String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph) |
| 401 | { |
| 402 | StringBuilder rebalancedString; |
| 403 | |
| 404 | bool previousCharacterWasSpace = false; |
| 405 | unsigned length = string.length(); |
| 406 | for (unsigned i = 0; i < length; ++i) { |
| 407 | auto character = string[i]; |
| 408 | if (!deprecatedIsEditingWhitespace(character)) { |
| 409 | previousCharacterWasSpace = false; |
| 410 | continue; |
| 411 | } |
| 412 | LChar selectedWhitespaceCharacter; |
| 413 | if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i == length - 1 && endIsEndOfParagraph)) { |
| 414 | selectedWhitespaceCharacter = noBreakSpace; |
| 415 | previousCharacterWasSpace = false; |
| 416 | } else { |
| 417 | selectedWhitespaceCharacter = ' '; |
| 418 | previousCharacterWasSpace = true; |
| 419 | } |
| 420 | if (character == selectedWhitespaceCharacter) |
| 421 | continue; |
| 422 | rebalancedString.reserveCapacity(length); |
| 423 | rebalancedString.append(string, rebalancedString.length(), i - rebalancedString.length()); |
| 424 | rebalancedString.append(selectedWhitespaceCharacter); |
| 425 | } |
| 426 | |
| 427 | if (rebalancedString.isEmpty()) |
| 428 | return string; |
| 429 | |
| 430 | rebalancedString.reserveCapacity(length); |
| 431 | rebalancedString.append(string, rebalancedString.length(), length - rebalancedString.length()); |
| 432 | return rebalancedString.toString(); |
| 433 | } |
| 434 | |
| 435 | bool isTableStructureNode(const Node* node) |
| 436 | { |
| 437 | auto* renderer = node->renderer(); |
| 438 | return renderer && (renderer->isTableCell() || renderer->isTableRow() || renderer->isTableSection() || renderer->isRenderTableCol()); |
| 439 | } |
| 440 | |
| 441 | const String& nonBreakingSpaceString() |
| 442 | { |
| 443 | static NeverDestroyed<String> nonBreakingSpaceString(&noBreakSpace, 1); |
| 444 | return nonBreakingSpaceString; |
| 445 | } |
| 446 | |
| 447 | static bool isSpecialHTMLElement(const Node* node) |
| 448 | { |
| 449 | if (!is<HTMLElement>(node)) |
| 450 | return false; |
| 451 | |
| 452 | if (downcast<HTMLElement>(*node).isLink()) |
| 453 | return true; |
| 454 | |
| 455 | auto* renderer = downcast<HTMLElement>(*node).renderer(); |
| 456 | if (!renderer) |
| 457 | return false; |
| 458 | |
| 459 | if (renderer->style().display() == DisplayType::Table || renderer->style().display() == DisplayType::InlineTable) |
| 460 | return true; |
| 461 | |
| 462 | if (renderer->style().isFloating()) |
| 463 | return true; |
| 464 | |
| 465 | if (renderer->style().position() != PositionType::Static) |
| 466 | return true; |
| 467 | |
| 468 | return false; |
| 469 | } |
| 470 | |
| 471 | static HTMLElement* firstInSpecialElement(const Position& position) |
| 472 | { |
| 473 | auto* rootEditableElement = position.containerNode()->rootEditableElement(); |
| 474 | for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { |
| 475 | if (!isSpecialHTMLElement(node)) |
| 476 | continue; |
| 477 | VisiblePosition vPos(position, DOWNSTREAM); |
| 478 | VisiblePosition firstInElement(firstPositionInOrBeforeNode(node), DOWNSTREAM); |
| 479 | if ((isRenderedTable(node) && vPos == firstInElement.next()) || vPos == firstInElement) |
| 480 | return &downcast<HTMLElement>(*node); |
| 481 | } |
| 482 | return nullptr; |
| 483 | } |
| 484 | |
| 485 | static HTMLElement* lastInSpecialElement(const Position& position) |
| 486 | { |
| 487 | auto* rootEditableElement = position.containerNode()->rootEditableElement(); |
| 488 | for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { |
| 489 | if (!isSpecialHTMLElement(node)) |
| 490 | continue; |
| 491 | VisiblePosition vPos(position, DOWNSTREAM); |
| 492 | VisiblePosition lastInElement(lastPositionInOrAfterNode(node), DOWNSTREAM); |
| 493 | if ((isRenderedTable(node) && vPos == lastInElement.previous()) || vPos == lastInElement) |
| 494 | return &downcast<HTMLElement>(*node); |
| 495 | } |
| 496 | return nullptr; |
| 497 | } |
| 498 | |
| 499 | Position positionBeforeContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) |
| 500 | { |
| 501 | auto* element = firstInSpecialElement(position); |
| 502 | if (!element) |
| 503 | return position; |
| 504 | Position result = positionInParentBeforeNode(element); |
| 505 | if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) |
| 506 | return position; |
| 507 | if (containingSpecialElement) |
| 508 | *containingSpecialElement = element; |
| 509 | return result; |
| 510 | } |
| 511 | |
| 512 | Position positionAfterContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) |
| 513 | { |
| 514 | auto* element = lastInSpecialElement(position); |
| 515 | if (!element) |
| 516 | return position; |
| 517 | Position result = positionInParentAfterNode(element); |
| 518 | if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) |
| 519 | return position; |
| 520 | if (containingSpecialElement) |
| 521 | *containingSpecialElement = element; |
| 522 | return result; |
| 523 | } |
| 524 | |
| 525 | Element* isFirstPositionAfterTable(const VisiblePosition& position) |
| 526 | { |
| 527 | Position upstream(position.deepEquivalent().upstream()); |
| 528 | auto* node = upstream.deprecatedNode(); |
| 529 | if (!node) |
| 530 | return nullptr; |
| 531 | auto* renderer = node->renderer(); |
| 532 | if (!renderer || !renderer->isTable() || !upstream.atLastEditingPositionForNode()) |
| 533 | return nullptr; |
| 534 | return &downcast<Element>(*node); |
| 535 | } |
| 536 | |
| 537 | Element* isLastPositionBeforeTable(const VisiblePosition& position) |
| 538 | { |
| 539 | Position downstream(position.deepEquivalent().downstream()); |
| 540 | auto* node = downstream.deprecatedNode(); |
| 541 | if (!node) |
| 542 | return nullptr; |
| 543 | auto* renderer = node->renderer(); |
| 544 | if (!renderer || !renderer->isTable() || !downstream.atFirstEditingPositionForNode()) |
| 545 | return nullptr; |
| 546 | return &downcast<Element>(*node); |
| 547 | } |
| 548 | |
| 549 | // Returns the visible position at the beginning of a node |
| 550 | VisiblePosition visiblePositionBeforeNode(Node& node) |
| 551 | { |
| 552 | if (node.hasChildNodes()) |
| 553 | return VisiblePosition(firstPositionInOrBeforeNode(&node), DOWNSTREAM); |
| 554 | ASSERT(node.parentNode()); |
| 555 | ASSERT(!node.parentNode()->isShadowRoot()); |
| 556 | return positionInParentBeforeNode(&node); |
| 557 | } |
| 558 | |
| 559 | // Returns the visible position at the ending of a node |
| 560 | VisiblePosition visiblePositionAfterNode(Node& node) |
| 561 | { |
| 562 | if (node.hasChildNodes()) |
| 563 | return VisiblePosition(lastPositionInOrAfterNode(&node), DOWNSTREAM); |
| 564 | ASSERT(node.parentNode()); |
| 565 | ASSERT(!node.parentNode()->isShadowRoot()); |
| 566 | return positionInParentAfterNode(&node); |
| 567 | } |
| 568 | |
| 569 | bool isListHTMLElement(Node* node) |
| 570 | { |
| 571 | return node && (is<HTMLUListElement>(*node) || is<HTMLOListElement>(*node) || is<HTMLDListElement>(*node)); |
| 572 | } |
| 573 | |
| 574 | bool isListItem(const Node* node) |
| 575 | { |
| 576 | return node && (isListHTMLElement(node->parentNode()) || (node->renderer() && node->renderer()->isListItem())); |
| 577 | } |
| 578 | |
| 579 | Element* enclosingElementWithTag(const Position& position, const QualifiedName& tagName) |
| 580 | { |
| 581 | auto* root = highestEditableRoot(position); |
| 582 | for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { |
| 583 | if (root && !node->hasEditableStyle()) |
| 584 | continue; |
| 585 | if (!is<Element>(*node)) |
| 586 | continue; |
| 587 | if (downcast<Element>(*node).hasTagName(tagName)) |
| 588 | return &downcast<Element>(*node); |
| 589 | if (node == root) |
| 590 | return nullptr; |
| 591 | } |
| 592 | return nullptr; |
| 593 | } |
| 594 | |
| 595 | Node* enclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) |
| 596 | { |
| 597 | // FIXME: support CanSkipCrossEditingBoundary |
| 598 | ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); |
| 599 | auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; |
| 600 | for (Node* n = position.deprecatedNode(); n; n = n->parentNode()) { |
| 601 | // Don't return a non-editable node if the input position was editable, since |
| 602 | // the callers from editing will no doubt want to perform editing inside the returned node. |
| 603 | if (root && !n->hasEditableStyle()) |
| 604 | continue; |
| 605 | if (nodeIsOfType(n)) |
| 606 | return n; |
| 607 | if (n == root) |
| 608 | return nullptr; |
| 609 | } |
| 610 | return nullptr; |
| 611 | } |
| 612 | |
| 613 | Node* highestEnclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin) |
| 614 | { |
| 615 | Node* highest = nullptr; |
| 616 | auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; |
| 617 | for (Node* n = position.containerNode(); n && n != stayWithin; n = n->parentNode()) { |
| 618 | if (root && !n->hasEditableStyle()) |
| 619 | continue; |
| 620 | if (nodeIsOfType(n)) |
| 621 | highest = n; |
| 622 | if (n == root) |
| 623 | break; |
| 624 | } |
| 625 | return highest; |
| 626 | } |
| 627 | |
| 628 | static bool hasARenderedDescendant(Node* node, Node* excludedNode) |
| 629 | { |
| 630 | for (Node* n = node->firstChild(); n;) { |
| 631 | if (n == excludedNode) { |
| 632 | n = NodeTraversal::nextSkippingChildren(*n, node); |
| 633 | continue; |
| 634 | } |
| 635 | if (n->renderer()) |
| 636 | return true; |
| 637 | n = NodeTraversal::next(*n, node); |
| 638 | } |
| 639 | return false; |
| 640 | } |
| 641 | |
| 642 | Node* highestNodeToRemoveInPruning(Node* node) |
| 643 | { |
| 644 | Node* previousNode = nullptr; |
| 645 | auto* rootEditableElement = node ? node->rootEditableElement() : nullptr; |
| 646 | for (; node; node = node->parentNode()) { |
| 647 | if (auto* renderer = node->renderer()) { |
| 648 | if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node) |
| 649 | return previousNode; |
| 650 | } |
| 651 | previousNode = node; |
| 652 | } |
| 653 | return nullptr; |
| 654 | } |
| 655 | |
| 656 | Element* enclosingTableCell(const Position& position) |
| 657 | { |
| 658 | return downcast<Element>(enclosingNodeOfType(position, isTableCell)); |
| 659 | } |
| 660 | |
| 661 | Element* enclosingAnchorElement(const Position& p) |
| 662 | { |
| 663 | for (Node* node = p.deprecatedNode(); node; node = node->parentNode()) { |
| 664 | if (is<Element>(*node) && node->isLink()) |
| 665 | return downcast<Element>(node); |
| 666 | } |
| 667 | return nullptr; |
| 668 | } |
| 669 | |
| 670 | HTMLElement* enclosingList(Node* node) |
| 671 | { |
| 672 | if (!node) |
| 673 | return nullptr; |
| 674 | |
| 675 | auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); |
| 676 | |
| 677 | for (ContainerNode* ancestor = node->parentNode(); ancestor; ancestor = ancestor->parentNode()) { |
| 678 | if (is<HTMLUListElement>(*ancestor) || is<HTMLOListElement>(*ancestor)) |
| 679 | return downcast<HTMLElement>(ancestor); |
| 680 | if (ancestor == root) |
| 681 | return nullptr; |
| 682 | } |
| 683 | |
| 684 | return nullptr; |
| 685 | } |
| 686 | |
| 687 | Node* enclosingListChild(Node *node) |
| 688 | { |
| 689 | if (!node) |
| 690 | return nullptr; |
| 691 | |
| 692 | // Check for a list item element, or for a node whose parent is a list element. Such a node |
| 693 | // will appear visually as a list item (but without a list marker) |
| 694 | auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); |
| 695 | |
| 696 | // FIXME: This function is inappropriately named since it starts with node instead of node->parentNode() |
| 697 | for (Node* n = node; n && n->parentNode(); n = n->parentNode()) { |
| 698 | if (is<HTMLLIElement>(*n) || (isListHTMLElement(n->parentNode()) && n != root)) |
| 699 | return n; |
| 700 | if (n == root || isTableCell(n)) |
| 701 | return nullptr; |
| 702 | } |
| 703 | |
| 704 | return nullptr; |
| 705 | } |
| 706 | |
| 707 | static HTMLElement* embeddedSublist(Node* listItem) |
| 708 | { |
| 709 | // Check the DOM so that we'll find collapsed sublists without renderers. |
| 710 | for (Node* n = listItem->firstChild(); n; n = n->nextSibling()) { |
| 711 | if (isListHTMLElement(n)) |
| 712 | return downcast<HTMLElement>(n); |
| 713 | } |
| 714 | return nullptr; |
| 715 | } |
| 716 | |
| 717 | static Node* appendedSublist(Node* listItem) |
| 718 | { |
| 719 | // Check the DOM so that we'll find collapsed sublists without renderers. |
| 720 | for (Node* n = listItem->nextSibling(); n; n = n->nextSibling()) { |
| 721 | if (isListHTMLElement(n)) |
| 722 | return downcast<HTMLElement>(n); |
| 723 | if (isListItem(listItem)) |
| 724 | return nullptr; |
| 725 | } |
| 726 | |
| 727 | return nullptr; |
| 728 | } |
| 729 | |
| 730 | // FIXME: This function should not need to call isStartOfParagraph/isEndOfParagraph. |
| 731 | Node* enclosingEmptyListItem(const VisiblePosition& position) |
| 732 | { |
| 733 | // Check that position is on a line by itself inside a list item |
| 734 | auto* listChildNode = enclosingListChild(position.deepEquivalent().deprecatedNode()); |
| 735 | if (!listChildNode || !isStartOfParagraph(position) || !isEndOfParagraph(position)) |
| 736 | return nullptr; |
| 737 | |
| 738 | VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode)); |
| 739 | VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode)); |
| 740 | |
| 741 | if (firstInListChild != position || lastInListChild != position) |
| 742 | return nullptr; |
| 743 | |
| 744 | if (embeddedSublist(listChildNode) || appendedSublist(listChildNode)) |
| 745 | return nullptr; |
| 746 | |
| 747 | return listChildNode; |
| 748 | } |
| 749 | |
| 750 | HTMLElement* outermostEnclosingList(Node* node, Node* rootList) |
| 751 | { |
| 752 | auto* list = enclosingList(node); |
| 753 | if (!list) |
| 754 | return nullptr; |
| 755 | |
| 756 | while (auto* nextList = enclosingList(list)) { |
| 757 | if (nextList == rootList) |
| 758 | break; |
| 759 | list = nextList; |
| 760 | } |
| 761 | |
| 762 | return list; |
| 763 | } |
| 764 | |
| 765 | bool canMergeLists(Element* firstList, Element* secondList) |
| 766 | { |
| 767 | if (!is<HTMLElement>(firstList) || !is<HTMLElement>(secondList)) |
| 768 | return false; |
| 769 | |
| 770 | auto& first = downcast<HTMLElement>(*firstList); |
| 771 | auto& second = downcast<HTMLElement>(*secondList); |
| 772 | |
| 773 | return first.localName() == second.localName() // make sure the list types match (ol vs. ul) |
| 774 | && first.hasEditableStyle() && second.hasEditableStyle() // both lists are editable |
| 775 | && first.rootEditableElement() == second.rootEditableElement() // don't cross editing boundaries |
| 776 | // Make sure there is no visible content between this li and the previous list. |
| 777 | && isVisiblyAdjacent(positionInParentAfterNode(&first), positionInParentBeforeNode(&second)); |
| 778 | } |
| 779 | |
| 780 | static Node* previousNodeConsideringAtomicNodes(const Node* node) |
| 781 | { |
| 782 | if (node->previousSibling()) { |
| 783 | Node* n = node->previousSibling(); |
| 784 | while (!isAtomicNode(n) && n->lastChild()) |
| 785 | n = n->lastChild(); |
| 786 | return n; |
| 787 | } |
| 788 | if (node->parentNode()) |
| 789 | return node->parentNode(); |
| 790 | return nullptr; |
| 791 | } |
| 792 | |
| 793 | static Node* nextNodeConsideringAtomicNodes(const Node* node) |
| 794 | { |
| 795 | if (!isAtomicNode(node) && node->firstChild()) |
| 796 | return node->firstChild(); |
| 797 | if (node->nextSibling()) |
| 798 | return node->nextSibling(); |
| 799 | const Node* n = node; |
| 800 | while (n && !n->nextSibling()) |
| 801 | n = n->parentNode(); |
| 802 | if (n) |
| 803 | return n->nextSibling(); |
| 804 | return nullptr; |
| 805 | } |
| 806 | |
| 807 | Node* previousLeafNode(const Node* node) |
| 808 | { |
| 809 | while ((node = previousNodeConsideringAtomicNodes(node))) { |
| 810 | if (isAtomicNode(node)) |
| 811 | return const_cast<Node*>(node); |
| 812 | } |
| 813 | return nullptr; |
| 814 | } |
| 815 | |
| 816 | Node* nextLeafNode(const Node* node) |
| 817 | { |
| 818 | while ((node = nextNodeConsideringAtomicNodes(node))) { |
| 819 | if (isAtomicNode(node)) |
| 820 | return const_cast<Node*>(node); |
| 821 | } |
| 822 | return nullptr; |
| 823 | } |
| 824 | |
| 825 | // FIXME: Do not require renderer, so that this can be used within fragments. |
| 826 | bool isRenderedTable(const Node* node) |
| 827 | { |
| 828 | if (!is<Element>(node)) |
| 829 | return false; |
| 830 | auto* renderer = downcast<Element>(*node).renderer(); |
| 831 | return renderer && renderer->isTable(); |
| 832 | } |
| 833 | |
| 834 | bool isTableCell(const Node* node) |
| 835 | { |
| 836 | auto* renderer = node->renderer(); |
| 837 | if (!renderer) |
| 838 | return node->hasTagName(tdTag) || node->hasTagName(thTag); |
| 839 | return renderer->isTableCell(); |
| 840 | } |
| 841 | |
| 842 | bool isEmptyTableCell(const Node* node) |
| 843 | { |
| 844 | // Returns true IFF the passed in node is one of: |
| 845 | // .) a table cell with no children, |
| 846 | // .) a table cell with a single BR child, and which has no other child renderers, including :before and :after renderers |
| 847 | // .) the BR child of such a table cell |
| 848 | |
| 849 | // Find rendered node |
| 850 | while (node && !node->renderer()) |
| 851 | node = node->parentNode(); |
| 852 | if (!node) |
| 853 | return false; |
| 854 | |
| 855 | // Make sure the rendered node is a table cell or <br>. |
| 856 | // If it's a <br>, then the parent node has to be a table cell. |
| 857 | auto* renderer = node->renderer(); |
| 858 | if (renderer->isBR()) { |
| 859 | renderer = renderer->parent(); |
| 860 | if (!renderer) |
| 861 | return false; |
| 862 | } |
| 863 | if (!is<RenderTableCell>(*renderer)) |
| 864 | return false; |
| 865 | |
| 866 | // Check that the table cell contains no child renderers except for perhaps a single <br>. |
| 867 | auto* childRenderer = downcast<RenderTableCell>(*renderer).firstChild(); |
| 868 | if (!childRenderer) |
| 869 | return true; |
| 870 | if (!childRenderer->isBR()) |
| 871 | return false; |
| 872 | return !childRenderer->nextSibling(); |
| 873 | } |
| 874 | |
| 875 | Ref<HTMLElement> createDefaultParagraphElement(Document& document) |
| 876 | { |
| 877 | switch (document.frame()->editor().defaultParagraphSeparator()) { |
| 878 | case EditorParagraphSeparatorIsDiv: |
| 879 | return HTMLDivElement::create(document); |
| 880 | case EditorParagraphSeparatorIsP: |
| 881 | break; |
| 882 | } |
| 883 | return HTMLParagraphElement::create(document); |
| 884 | } |
| 885 | |
| 886 | Ref<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) |
| 887 | { |
| 888 | return HTMLElementFactory::createElement(name, document); |
| 889 | } |
| 890 | |
| 891 | Ref<HTMLElement> createHTMLElement(Document& document, const AtomicString& tagName) |
| 892 | { |
| 893 | return createHTMLElement(document, QualifiedName(nullAtom(), tagName, xhtmlNamespaceURI)); |
| 894 | } |
| 895 | |
| 896 | bool isTabSpanNode(const Node* node) |
| 897 | { |
| 898 | return is<HTMLSpanElement>(node) && downcast<HTMLSpanElement>(*node).attributeWithoutSynchronization(classAttr) == AppleTabSpanClass; |
| 899 | } |
| 900 | |
| 901 | bool isTabSpanTextNode(const Node* node) |
| 902 | { |
| 903 | return is<Text>(node) && isTabSpanNode(node->parentNode()); |
| 904 | } |
| 905 | |
| 906 | HTMLSpanElement* tabSpanNode(const Node* node) |
| 907 | { |
| 908 | return isTabSpanTextNode(node) ? downcast<HTMLSpanElement>(node->parentNode()) : nullptr; |
| 909 | } |
| 910 | |
| 911 | static Ref<Element> createTabSpanElement(Document& document, Text& tabTextNode) |
| 912 | { |
| 913 | auto spanElement = HTMLSpanElement::create(document); |
| 914 | |
| 915 | spanElement->setAttributeWithoutSynchronization(classAttr, AppleTabSpanClass); |
| 916 | spanElement->setAttribute(styleAttr, "white-space:pre" ); |
| 917 | |
| 918 | spanElement->appendChild(tabTextNode); |
| 919 | |
| 920 | return spanElement; |
| 921 | } |
| 922 | |
| 923 | Ref<Element> createTabSpanElement(Document& document, const String& tabText) |
| 924 | { |
| 925 | return createTabSpanElement(document, document.createTextNode(tabText)); |
| 926 | } |
| 927 | |
| 928 | Ref<Element> createTabSpanElement(Document& document) |
| 929 | { |
| 930 | return createTabSpanElement(document, document.createEditingTextNode("\t"_s )); |
| 931 | } |
| 932 | |
| 933 | bool isNodeRendered(const Node& node) |
| 934 | { |
| 935 | auto* renderer = node.renderer(); |
| 936 | return renderer && renderer->style().visibility() == Visibility::Visible; |
| 937 | } |
| 938 | |
| 939 | unsigned numEnclosingMailBlockquotes(const Position& position) |
| 940 | { |
| 941 | unsigned count = 0; |
| 942 | for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { |
| 943 | if (isMailBlockquote(node)) |
| 944 | ++count; |
| 945 | } |
| 946 | return count; |
| 947 | } |
| 948 | |
| 949 | void updatePositionForNodeRemoval(Position& position, Node& node) |
| 950 | { |
| 951 | if (position.isNull()) |
| 952 | return; |
| 953 | switch (position.anchorType()) { |
| 954 | case Position::PositionIsBeforeChildren: |
| 955 | if (node.containsIncludingShadowDOM(position.containerNode())) |
| 956 | position = positionInParentBeforeNode(&node); |
| 957 | break; |
| 958 | case Position::PositionIsAfterChildren: |
| 959 | if (node.containsIncludingShadowDOM(position.containerNode())) |
| 960 | position = positionInParentBeforeNode(&node); |
| 961 | break; |
| 962 | case Position::PositionIsOffsetInAnchor: |
| 963 | if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.computeNodeIndex()) |
| 964 | position.moveToOffset(position.offsetInContainerNode() - 1); |
| 965 | else if (node.containsIncludingShadowDOM(position.containerNode())) |
| 966 | position = positionInParentBeforeNode(&node); |
| 967 | break; |
| 968 | case Position::PositionIsAfterAnchor: |
| 969 | if (node.containsIncludingShadowDOM(position.anchorNode())) |
| 970 | position = positionInParentAfterNode(&node); |
| 971 | break; |
| 972 | case Position::PositionIsBeforeAnchor: |
| 973 | if (node.containsIncludingShadowDOM(position.anchorNode())) |
| 974 | position = positionInParentBeforeNode(&node); |
| 975 | break; |
| 976 | } |
| 977 | } |
| 978 | |
| 979 | bool isMailBlockquote(const Node* node) |
| 980 | { |
| 981 | ASSERT(node); |
| 982 | if (!node->hasTagName(blockquoteTag)) |
| 983 | return false; |
| 984 | return downcast<HTMLElement>(*node).attributeWithoutSynchronization(typeAttr) == "cite" ; |
| 985 | } |
| 986 | |
| 987 | int caretMinOffset(const Node& node) |
| 988 | { |
| 989 | auto* renderer = node.renderer(); |
| 990 | ASSERT(!node.isCharacterDataNode() || !renderer || renderer->isText()); |
| 991 | return renderer ? renderer->caretMinOffset() : 0; |
| 992 | } |
| 993 | |
| 994 | // If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise |
| 995 | // return the number of children for container nodes and the length for unrendered text nodes. |
| 996 | int caretMaxOffset(const Node& node) |
| 997 | { |
| 998 | // For rendered text nodes, return the last position that a caret could occupy. |
| 999 | if (is<Text>(node)) { |
| 1000 | if (auto* renderer = downcast<Text>(node).renderer()) |
| 1001 | return renderer->caretMaxOffset(); |
| 1002 | } |
| 1003 | return lastOffsetForEditing(node); |
| 1004 | } |
| 1005 | |
| 1006 | bool lineBreakExistsAtVisiblePosition(const VisiblePosition& position) |
| 1007 | { |
| 1008 | return lineBreakExistsAtPosition(position.deepEquivalent().downstream()); |
| 1009 | } |
| 1010 | |
| 1011 | bool lineBreakExistsAtPosition(const Position& position) |
| 1012 | { |
| 1013 | if (position.isNull()) |
| 1014 | return false; |
| 1015 | |
| 1016 | if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode()) |
| 1017 | return true; |
| 1018 | |
| 1019 | if (!position.anchorNode()->renderer()) |
| 1020 | return false; |
| 1021 | |
| 1022 | if (!is<Text>(*position.anchorNode()) || !position.anchorNode()->renderer()->style().preserveNewline()) |
| 1023 | return false; |
| 1024 | |
| 1025 | Text& textNode = downcast<Text>(*position.anchorNode()); |
| 1026 | unsigned offset = position.offsetInContainerNode(); |
| 1027 | return offset < textNode.length() && textNode.data()[offset] == '\n'; |
| 1028 | } |
| 1029 | |
| 1030 | // Modifies selections that have an end point at the edge of a table |
| 1031 | // that contains the other endpoint so that they don't confuse |
| 1032 | // code that iterates over selected paragraphs. |
| 1033 | VisibleSelection selectionForParagraphIteration(const VisibleSelection& original) |
| 1034 | { |
| 1035 | VisibleSelection newSelection(original); |
| 1036 | VisiblePosition startOfSelection(newSelection.visibleStart()); |
| 1037 | VisiblePosition endOfSelection(newSelection.visibleEnd()); |
| 1038 | |
| 1039 | // If the end of the selection to modify is just after a table, and |
| 1040 | // if the start of the selection is inside that table, then the last paragraph |
| 1041 | // that we'll want modify is the last one inside the table, not the table itself |
| 1042 | // (a table is itself a paragraph). |
| 1043 | if (auto* table = isFirstPositionAfterTable(endOfSelection)) { |
| 1044 | if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) |
| 1045 | newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary)); |
| 1046 | } |
| 1047 | |
| 1048 | // If the start of the selection to modify is just before a table, |
| 1049 | // and if the end of the selection is inside that table, then the first paragraph |
| 1050 | // we'll want to modify is the first one inside the table, not the paragraph |
| 1051 | // containing the table itself. |
| 1052 | if (auto* table = isLastPositionBeforeTable(startOfSelection)) { |
| 1053 | if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) |
| 1054 | newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection); |
| 1055 | } |
| 1056 | |
| 1057 | return newSelection; |
| 1058 | } |
| 1059 | |
| 1060 | // FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between |
| 1061 | // VisiblePositions and indices. But TextIterator iteration using TextIteratorEmitsCharactersBetweenAllVisiblePositions |
| 1062 | // does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing |
| 1063 | // opertion is unreliable. TextIterator's TextIteratorEmitsCharactersBetweenAllVisiblePositions mode needs to be fixed, |
| 1064 | // or these functions need to be changed to iterate using actual VisiblePositions. |
| 1065 | // FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices. |
| 1066 | int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<ContainerNode>& scope) |
| 1067 | { |
| 1068 | if (visiblePosition.isNull()) |
| 1069 | return 0; |
| 1070 | |
| 1071 | auto position = visiblePosition.deepEquivalent(); |
| 1072 | auto& document = *position.document(); |
| 1073 | |
| 1074 | auto* editableRoot = highestEditableRoot(position, AXObjectCache::accessibilityEnabled() ? HasEditableAXRole : ContentIsEditable); |
| 1075 | if (editableRoot && !document.inDesignMode()) |
| 1076 | scope = editableRoot; |
| 1077 | else { |
| 1078 | if (position.containerNode()->isInShadowTree()) |
| 1079 | scope = position.containerNode()->containingShadowRoot(); |
| 1080 | else |
| 1081 | scope = &document; |
| 1082 | } |
| 1083 | |
| 1084 | auto range = Range::create(document, firstPositionInNode(scope.get()), position.parentAnchoredEquivalent()); |
| 1085 | return TextIterator::rangeLength(range.ptr(), true); |
| 1086 | } |
| 1087 | |
| 1088 | // FIXME: Merge this function with the one above. |
| 1089 | int indexForVisiblePosition(Node& node, const VisiblePosition& visiblePosition, bool forSelectionPreservation) |
| 1090 | { |
| 1091 | auto range = Range::create(node.document(), firstPositionInNode(&node), visiblePosition.deepEquivalent().parentAnchoredEquivalent()); |
| 1092 | return TextIterator::rangeLength(range.ptr(), forSelectionPreservation); |
| 1093 | } |
| 1094 | |
| 1095 | VisiblePosition visiblePositionForPositionWithOffset(const VisiblePosition& position, int offset) |
| 1096 | { |
| 1097 | RefPtr<ContainerNode> root; |
| 1098 | unsigned startIndex = indexForVisiblePosition(position, root); |
| 1099 | if (!root) |
| 1100 | return { }; |
| 1101 | |
| 1102 | return visiblePositionForIndex(startIndex + offset, root.get()); |
| 1103 | } |
| 1104 | |
| 1105 | VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope) |
| 1106 | { |
| 1107 | auto range = TextIterator::rangeFromLocationAndLength(scope, index, 0, true); |
| 1108 | // Check for an invalid index. Certain editing operations invalidate indices because |
| 1109 | // of problems with TextIteratorEmitsCharactersBetweenAllVisiblePositions. |
| 1110 | if (!range) |
| 1111 | return { }; |
| 1112 | return { range->startPosition() }; |
| 1113 | } |
| 1114 | |
| 1115 | VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node& node, int index) |
| 1116 | { |
| 1117 | if (index <= 0) |
| 1118 | return { firstPositionInOrBeforeNode(&node), DOWNSTREAM }; |
| 1119 | |
| 1120 | auto range = Range::create(node.document()); |
| 1121 | range->selectNodeContents(node); |
| 1122 | CharacterIterator it(range.get()); |
| 1123 | it.advance(index - 1); |
| 1124 | return { it.atEnd() ? range->endPosition() : it.range()->endPosition(), UPSTREAM }; |
| 1125 | } |
| 1126 | |
| 1127 | // Determines whether two positions are visibly next to each other (first then second) |
| 1128 | // while ignoring whitespaces and unrendered nodes |
| 1129 | static bool isVisiblyAdjacent(const Position& first, const Position& second) |
| 1130 | { |
| 1131 | return VisiblePosition(first) == VisiblePosition(second.upstream()); |
| 1132 | } |
| 1133 | |
| 1134 | // Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range. |
| 1135 | // Call this function to determine whether a node is visibly fit inside selectedRange |
| 1136 | bool isNodeVisiblyContainedWithin(Node& node, const Range& range) |
| 1137 | { |
| 1138 | // If the node is inside the range, then it surely is contained within. |
| 1139 | auto comparisonResult = range.compareNode(node); |
| 1140 | if (!comparisonResult.hasException() && comparisonResult.releaseReturnValue() == Range::NODE_INSIDE) |
| 1141 | return true; |
| 1142 | |
| 1143 | bool startIsVisuallySame = visiblePositionBeforeNode(node) == range.startPosition(); |
| 1144 | if (startIsVisuallySame && comparePositions(positionInParentAfterNode(&node), range.endPosition()) < 0) |
| 1145 | return true; |
| 1146 | |
| 1147 | bool endIsVisuallySame = visiblePositionAfterNode(node) == range.endPosition(); |
| 1148 | if (endIsVisuallySame && comparePositions(range.startPosition(), positionInParentBeforeNode(&node)) < 0) |
| 1149 | return true; |
| 1150 | |
| 1151 | return startIsVisuallySame && endIsVisuallySame; |
| 1152 | } |
| 1153 | |
| 1154 | bool isRenderedAsNonInlineTableImageOrHR(const Node* node) |
| 1155 | { |
| 1156 | if (!node) |
| 1157 | return false; |
| 1158 | RenderObject* renderer = node->renderer(); |
| 1159 | return renderer && ((renderer->isTable() && !renderer->isInline()) || (renderer->isImage() && !renderer->isInline()) || renderer->isHR()); |
| 1160 | } |
| 1161 | |
| 1162 | bool areIdenticalElements(const Node& first, const Node& second) |
| 1163 | { |
| 1164 | if (!is<Element>(first) || !is<Element>(second)) |
| 1165 | return false; |
| 1166 | auto& firstElement = downcast<Element>(first); |
| 1167 | auto& secondElement = downcast<Element>(second); |
| 1168 | return firstElement.hasTagName(secondElement.tagQName()) && firstElement.hasEquivalentAttributes(secondElement); |
| 1169 | } |
| 1170 | |
| 1171 | bool isNonTableCellHTMLBlockElement(const Node* node) |
| 1172 | { |
| 1173 | return node->hasTagName(listingTag) |
| 1174 | || node->hasTagName(olTag) |
| 1175 | || node->hasTagName(preTag) |
| 1176 | || is<HTMLTableElement>(*node) |
| 1177 | || node->hasTagName(ulTag) |
| 1178 | || node->hasTagName(xmpTag) |
| 1179 | || node->hasTagName(h1Tag) |
| 1180 | || node->hasTagName(h2Tag) |
| 1181 | || node->hasTagName(h3Tag) |
| 1182 | || node->hasTagName(h4Tag) |
| 1183 | || node->hasTagName(h5Tag); |
| 1184 | } |
| 1185 | |
| 1186 | Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) |
| 1187 | { |
| 1188 | // This function is used by range style computations to avoid bugs like: |
| 1189 | // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once |
| 1190 | // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up |
| 1191 | // with a spurious "mixed" style. |
| 1192 | |
| 1193 | auto visiblePosition = selection.visibleStart(); |
| 1194 | if (visiblePosition.isNull()) |
| 1195 | return { }; |
| 1196 | |
| 1197 | // if the selection is a caret, just return the position, since the style |
| 1198 | // behind us is relevant |
| 1199 | if (selection.isCaret()) |
| 1200 | return visiblePosition.deepEquivalent(); |
| 1201 | |
| 1202 | // if the selection starts just before a paragraph break, skip over it |
| 1203 | if (isEndOfParagraph(visiblePosition)) |
| 1204 | return visiblePosition.next().deepEquivalent().downstream(); |
| 1205 | |
| 1206 | // otherwise, make sure to be at the start of the first selected node, |
| 1207 | // instead of possibly at the end of the last node before the selection |
| 1208 | return visiblePosition.deepEquivalent().downstream(); |
| 1209 | } |
| 1210 | |
| 1211 | // FIXME: Should this be deprecated like deprecatedEnclosingBlockFlowElement is? |
| 1212 | bool isBlockFlowElement(const Node& node) |
| 1213 | { |
| 1214 | if (!node.isElementNode()) |
| 1215 | return false; |
| 1216 | auto* renderer = downcast<Element>(node).renderer(); |
| 1217 | return renderer && renderer->isRenderBlockFlow(); |
| 1218 | } |
| 1219 | |
| 1220 | Element* deprecatedEnclosingBlockFlowElement(Node* node) |
| 1221 | { |
| 1222 | if (!node) |
| 1223 | return nullptr; |
| 1224 | if (isBlockFlowElement(*node)) |
| 1225 | return downcast<Element>(node); |
| 1226 | while ((node = node->parentNode())) { |
| 1227 | if (isBlockFlowElement(*node) || is<HTMLBodyElement>(*node)) |
| 1228 | return downcast<Element>(node); |
| 1229 | } |
| 1230 | return nullptr; |
| 1231 | } |
| 1232 | |
| 1233 | static inline bool caretRendersInsideNode(Node& node) |
| 1234 | { |
| 1235 | return !isRenderedTable(&node) && !editingIgnoresContent(node); |
| 1236 | } |
| 1237 | |
| 1238 | RenderBlock* rendererForCaretPainting(Node* node) |
| 1239 | { |
| 1240 | if (!node) |
| 1241 | return nullptr; |
| 1242 | |
| 1243 | auto* renderer = node->renderer(); |
| 1244 | if (!renderer) |
| 1245 | return nullptr; |
| 1246 | |
| 1247 | // If caretNode is a block and caret is inside it, then caret should be painted by that block. |
| 1248 | bool paintedByBlock = is<RenderBlockFlow>(*renderer) && caretRendersInsideNode(*node); |
| 1249 | return paintedByBlock ? downcast<RenderBlock>(renderer) : renderer->containingBlock(); |
| 1250 | } |
| 1251 | |
| 1252 | LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition& caretPosition, RenderBlock*& caretPainter) |
| 1253 | { |
| 1254 | if (caretPosition.isNull()) |
| 1255 | return LayoutRect(); |
| 1256 | |
| 1257 | ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer()); |
| 1258 | |
| 1259 | // First compute a rect local to the renderer at the selection start. |
| 1260 | RenderObject* renderer; |
| 1261 | LayoutRect localRect = caretPosition.localCaretRect(renderer); |
| 1262 | |
| 1263 | return localCaretRectInRendererForRect(localRect, caretPosition.deepEquivalent().deprecatedNode(), renderer, caretPainter); |
| 1264 | } |
| 1265 | |
| 1266 | LayoutRect localCaretRectInRendererForRect(LayoutRect& localRect, Node* node, RenderObject* renderer, RenderBlock*& caretPainter) |
| 1267 | { |
| 1268 | // Get the renderer that will be responsible for painting the caret |
| 1269 | // (which is either the renderer we just found, or one of its containers). |
| 1270 | caretPainter = rendererForCaretPainting(node); |
| 1271 | |
| 1272 | // Compute an offset between the renderer and the caretPainter. |
| 1273 | while (renderer != caretPainter) { |
| 1274 | RenderElement* containerObject = renderer->container(); |
| 1275 | if (!containerObject) |
| 1276 | return LayoutRect(); |
| 1277 | localRect.move(renderer->offsetFromContainer(*containerObject, localRect.location())); |
| 1278 | renderer = containerObject; |
| 1279 | } |
| 1280 | |
| 1281 | return localRect; |
| 1282 | } |
| 1283 | |
| 1284 | IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect& rect, bool* insideFixed) |
| 1285 | { |
| 1286 | if (insideFixed) |
| 1287 | *insideFixed = false; |
| 1288 | |
| 1289 | if (!rendererForCaretPainting || rect.isEmpty()) |
| 1290 | return IntRect(); |
| 1291 | |
| 1292 | LayoutRect localRect(rect); |
| 1293 | rendererForCaretPainting->flipForWritingMode(localRect); |
| 1294 | return rendererForCaretPainting->localToAbsoluteQuad(FloatRect(localRect), UseTransforms, insideFixed).enclosingBoundingBox(); |
| 1295 | } |
| 1296 | |
| 1297 | } // namespace WebCore |
| 1298 | |