| File: | Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/Editing.cpp |
| Warning: | line 1225, column 15 Local variable 'cachedImage' is uncounted and unsafe |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* |
| 2 | * Copyright (C) 2004-2025 Apple Inc. All rights reserved. |
| 3 | * Copyright (C) 2015 Google Inc. All rights reserved. |
| 4 | * |
| 5 | * Redistribution and use in source and binary forms, with or without |
| 6 | * modification, are permitted provided that the following conditions |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | */ |
| 26 | |
| 27 | #include "config.h" |
| 28 | #include "Editing.h" |
| 29 | |
| 30 | #include "AXObjectCache.h" |
| 31 | #include "CachedImage.h" |
| 32 | #include "ContainerNodeInlines.h" |
| 33 | #include "EditingInlines.h" |
| 34 | #include "Editor.h" |
| 35 | #include "ElementChildIteratorInlines.h" |
| 36 | #include "ElementInlines.h" |
| 37 | #include "ElementRareData.h" |
| 38 | #include "FrameDestructionObserverInlines.h" |
| 39 | #include "GraphicsLayer.h" |
| 40 | #include "HTMLBodyElement.h" |
| 41 | #include "HTMLDListElement.h" |
| 42 | #include "HTMLDivElement.h" |
| 43 | #include "HTMLElementFactory.h" |
| 44 | #include "HTMLImageElement.h" |
| 45 | #include "HTMLInterchange.h" |
| 46 | #include "HTMLLIElement.h" |
| 47 | #include "HTMLNames.h" |
| 48 | #include "HTMLOListElement.h" |
| 49 | #include "HTMLParagraphElement.h" |
| 50 | #include "HTMLPictureElement.h" |
| 51 | #include "HTMLSpanElement.h" |
| 52 | #include "HTMLTableElement.h" |
| 53 | #include "HTMLTextFormControlElement.h" |
| 54 | #include "HTMLUListElement.h" |
| 55 | #include "HitTestSource.h" |
| 56 | #include "ImageOverlay.h" |
| 57 | #include "LocalFrame.h" |
| 58 | #include "NodeTraversal.h" |
| 59 | #include "PositionIterator.h" |
| 60 | #include "Range.h" |
| 61 | #include "RenderBlock.h" |
| 62 | #include "RenderElement.h" |
| 63 | #include "RenderLayer.h" |
| 64 | #include "RenderLayerBacking.h" |
| 65 | #include "RenderObjectInlines.h" |
| 66 | #include "RenderStyle+GettersInlines.h" |
| 67 | #include "RenderTableCell.h" |
| 68 | #include "RenderTextControlSingleLine.h" |
| 69 | #include "RenderedPosition.h" |
| 70 | #include "ShadowRoot.h" |
| 71 | #include "Text.h" |
| 72 | #include "TextControlInnerElements.h" |
| 73 | #include "TextIterator.h" |
| 74 | #include "VisibleUnits.h" |
| 75 | #include "WritingMode.h" |
| 76 | #include <wtf/Assertions.h> |
| 77 | #include <wtf/IterationStatus.h> |
| 78 | #include <wtf/StdLibExtras.h> |
| 79 | #include <wtf/text/StringBuilder.h> |
| 80 | #include <wtf/unicode/CharacterNames.h> |
| 81 | |
| 82 | namespace WebCore { |
| 83 | |
| 84 | using namespace HTMLNames; |
| 85 | |
| 86 | static bool isVisiblyAdjacent(const Position&, const Position&); |
| 87 | |
| 88 | bool canHaveChildrenForEditing(const Node& node) |
| 89 | { |
| 90 | return !is<Text>(node) && !is<HTMLPictureElement>(node) && node.canContainRangeEndPoint(); |
| 91 | } |
| 92 | |
| 93 | // Atomic means that the node has no children, or has children which are ignored for the purposes of editing. |
| 94 | bool isAtomicNode(const Node* node) |
| 95 | { |
| 96 | return node && (!node->hasChildNodes() || editingIgnoresContent(*node)); |
| 97 | } |
| 98 | |
| 99 | RefPtr<ContainerNode> highestEditableRoot(const Position& position, EditableType editableType) |
| 100 | { |
| 101 | RefPtr<ContainerNode> highestEditableRoot = editableRootForPosition(position, editableType); |
| 102 | if (!highestEditableRoot) |
| 103 | return nullptr; |
| 104 | |
| 105 | for (RefPtr<ContainerNode> node = highestEditableRoot; !is<HTMLBodyElement>(*node); ) { |
| 106 | node = node->parentNode(); |
| 107 | if (!node) |
| 108 | break; |
| 109 | // FIXME: Can this ever be a Document or DocumentFragment? If not, this should return Element* instead. |
| 110 | if (hasEditableStyle(*node, editableType)) |
| 111 | highestEditableRoot = node; |
| 112 | } |
| 113 | |
| 114 | return highestEditableRoot; |
| 115 | } |
| 116 | |
| 117 | Element* lowestEditableAncestor(Node* node) |
| 118 | { |
| 119 | for (RefPtr currentNode = node; currentNode; currentNode = currentNode->parentNode()) { |
| 120 | if (currentNode->hasEditableStyle()) |
| 121 | return currentNode->rootEditableElement(); |
| 122 | if (is<HTMLBodyElement>(*currentNode)) |
| 123 | break; |
| 124 | } |
| 125 | return nullptr; |
| 126 | } |
| 127 | |
| 128 | static bool isEditableToAccessibility(const Node& node) |
| 129 | { |
| 130 | ASSERT(AXObjectCache::accessibilityEnabled())((void)0); |
| 131 | ASSERT(node.document().existingAXObjectCache())((void)0); |
| 132 | |
| 133 | if (CheckedPtr cache = node.document().existingAXObjectCache()) |
| 134 | return cache->rootAXEditableElement(&node); |
| 135 | |
| 136 | return false; |
| 137 | } |
| 138 | |
| 139 | static bool computeEditability(const Node& node, EditableType editableType, Node::ShouldUpdateStyle shouldUpdateStyle) |
| 140 | { |
| 141 | if (node.computeEditability(Node::UserSelectAllTreatment::NotEditable, shouldUpdateStyle) != Node::Editability::ReadOnly) |
| 142 | return true; |
| 143 | |
| 144 | switch (editableType) { |
| 145 | case ContentIsEditable: |
| 146 | return false; |
| 147 | case HasEditableAXRole: |
| 148 | return isEditableToAccessibility(node); |
| 149 | } |
| 150 | ASSERT_NOT_REACHED()((void)0); |
| 151 | return false; |
| 152 | } |
| 153 | |
| 154 | bool hasEditableStyle(const Node& node, EditableType editableType) |
| 155 | { |
| 156 | return computeEditability(node, editableType, Node::ShouldUpdateStyle::DoNotUpdate); |
| 157 | } |
| 158 | |
| 159 | bool isEditableNode(const Node& node) |
| 160 | { |
| 161 | return computeEditability(node, ContentIsEditable, Node::ShouldUpdateStyle::Update); |
| 162 | } |
| 163 | |
| 164 | bool isEditablePosition(const Position& position, EditableType editableType) |
| 165 | { |
| 166 | RefPtr node = position.containerNode(); |
| 167 | return node && computeEditability(*node, editableType, Node::ShouldUpdateStyle::Update); |
| 168 | } |
| 169 | |
| 170 | bool isAtUnsplittableElement(const Position& position) |
| 171 | { |
| 172 | RefPtr node = position.containerNode(); |
| 173 | return node == editableRootForPosition(position) || node == enclosingNodeOfType(position, isTableCell); |
| 174 | } |
| 175 | |
| 176 | bool isRichlyEditablePosition(const Position& position) |
| 177 | { |
| 178 | RefPtr node = position.containerNode(); |
| 179 | return node && node->hasRichlyEditableStyle(); |
| 180 | } |
| 181 | |
| 182 | Element* editableRootForPosition(const Position& position, EditableType editableType) |
| 183 | { |
| 184 | RefPtr node = position.containerNode(); |
| 185 | if (!node) |
| 186 | return nullptr; |
| 187 | |
| 188 | switch (editableType) { |
| 189 | case HasEditableAXRole: |
| 190 | if (CheckedPtr cache = node->document().existingAXObjectCache()) |
| 191 | return const_cast<Element*>(cache->rootAXEditableElement(node.get())); |
| 192 | [[fallthrough]]; |
| 193 | case ContentIsEditable: |
| 194 | return node->rootEditableElement(); |
| 195 | } |
| 196 | return nullptr; |
| 197 | } |
| 198 | |
| 199 | // Finds the enclosing element until which the tree can be split. |
| 200 | // When a user hits ENTER, he/she won't expect this element to be split into two. |
| 201 | // You may pass it as the second argument of splitTreeToNode. |
| 202 | RefPtr<Element> unsplittableElementForPosition(const Position& position) |
| 203 | { |
| 204 | // Since enclosingNodeOfType won't search beyond the highest root editable node, |
| 205 | // this code works even if the closest table cell was outside of the root editable node. |
| 206 | if (RefPtr enclosingCell = downcast<Element>(enclosingNodeOfType(position, &isTableCell))) |
| 207 | return enclosingCell; |
| 208 | return editableRootForPosition(position); |
| 209 | } |
| 210 | |
| 211 | Position nextCandidate(const Position& position) |
| 212 | { |
| 213 | for (PositionIterator nextPosition = position; !nextPosition.atEnd(); ) { |
| 214 | nextPosition.increment(); |
| 215 | if (nextPosition.isCandidate()) |
| 216 | return nextPosition; |
| 217 | } |
| 218 | return { }; |
| 219 | } |
| 220 | |
| 221 | Position nextVisuallyDistinctCandidate(const Position& position, SkipDisplayContents skipDisplayContents) |
| 222 | { |
| 223 | // FIXME: Use PositionIterator instead. |
| 224 | Position nextPosition = position; |
| 225 | Position downstreamStart = nextPosition.downstream(); |
| 226 | while (!nextPosition.atEndOfTree()) { |
| 227 | nextPosition = nextPosition.next(Character); |
| 228 | if (nextPosition.isCandidate() && nextPosition.downstream() != downstreamStart) |
| 229 | return nextPosition; |
| 230 | if (RefPtr node = nextPosition.containerNode()) { |
| 231 | if (!node->renderer()) { |
| 232 | if (skipDisplayContents == SkipDisplayContents::No) { |
| 233 | if (auto element = dynamicDowncast<Element>(node); element && element->hasDisplayContents()) |
| 234 | continue; |
| 235 | } |
| 236 | nextPosition = lastPositionInOrAfterNode(node.get()); |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | return { }; |
| 241 | } |
| 242 | |
| 243 | Position previousCandidate(const Position& position) |
| 244 | { |
| 245 | PositionIterator previousPosition = position; |
| 246 | while (!previousPosition.atStart()) { |
| 247 | previousPosition.decrement(); |
| 248 | if (previousPosition.isCandidate()) |
| 249 | return previousPosition; |
| 250 | } |
| 251 | return { }; |
| 252 | } |
| 253 | |
| 254 | Position previousVisuallyDistinctCandidate(const Position& position) |
| 255 | { |
| 256 | // FIXME: Use PositionIterator instead. |
| 257 | Position previousPosition = position; |
| 258 | Position downstreamStart = previousPosition.downstream(); |
| 259 | while (!previousPosition.atStartOfTree()) { |
| 260 | previousPosition = previousPosition.previous(Character); |
| 261 | if (previousPosition.isCandidate() && previousPosition.downstream() != downstreamStart) |
| 262 | return previousPosition; |
| 263 | if (RefPtr node = previousPosition.containerNode()) { |
| 264 | if (!node->renderer()) |
| 265 | previousPosition = firstPositionInOrBeforeNode(node.get()); |
| 266 | } |
| 267 | } |
| 268 | return { }; |
| 269 | } |
| 270 | |
| 271 | Position firstEditablePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot) |
| 272 | { |
| 273 | if (!highestRoot) |
| 274 | return { }; |
| 275 | |
| 276 | // position falls before highestRoot. |
| 277 | if (auto result = firstPositionInNode(*highestRoot); position < result && highestRoot->hasEditableStyle()) |
| 278 | return result; |
| 279 | |
| 280 | Position candidate = position; |
| 281 | |
| 282 | if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { |
| 283 | RefPtr shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); |
| 284 | if (!shadowAncestor) |
| 285 | return { }; |
| 286 | |
| 287 | candidate = positionAfterNode(*shadowAncestor); |
| 288 | } |
| 289 | |
| 290 | while (candidate.deprecatedNode() && !isEditablePosition(candidate) && protect(candidate.deprecatedNode())->isDescendantOf(*highestRoot)) |
| 291 | candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentAfterNode(protect(*candidate.deprecatedNode())) : nextVisuallyDistinctCandidate(candidate); |
| 292 | |
| 293 | if (candidate.deprecatedNode() && !protect(candidate.deprecatedNode())->isInclusiveDescendantOf(*highestRoot)) |
| 294 | return { }; |
| 295 | |
| 296 | return candidate; |
| 297 | } |
| 298 | |
| 299 | Position lastEditablePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot) |
| 300 | { |
| 301 | if (!highestRoot) |
| 302 | return { }; |
| 303 | |
| 304 | // When position falls after highestRoot, the result is easy to compute. |
| 305 | if (auto result = lastPositionInNode(*highestRoot); position > result) |
| 306 | return result; |
| 307 | |
| 308 | Position candidate = position; |
| 309 | |
| 310 | if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { |
| 311 | RefPtr shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); |
| 312 | if (!shadowAncestor) |
| 313 | return { }; |
| 314 | |
| 315 | candidate = firstPositionInOrBeforeNode(shadowAncestor.get()); |
| 316 | } |
| 317 | |
| 318 | while (candidate.deprecatedNode() && !isEditablePosition(candidate) && protect(candidate.deprecatedNode())->isDescendantOf(*highestRoot)) |
| 319 | candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentBeforeNode(protect(*candidate.deprecatedNode())) : previousVisuallyDistinctCandidate(candidate); |
| 320 | |
| 321 | if (candidate.deprecatedNode() && !protect(candidate.deprecatedNode())->isInclusiveDescendantOf(*highestRoot)) |
| 322 | return { }; |
| 323 | |
| 324 | return candidate; |
| 325 | } |
| 326 | |
| 327 | // FIXME: The function name, comment, and code say three different things here! |
| 328 | // Whether or not content before and after this node will collapse onto the same line as it. |
| 329 | bool isBlock(const Node& node) |
| 330 | { |
| 331 | return node.renderer() && !node.renderer()->isInline(); |
| 332 | } |
| 333 | |
| 334 | bool isInline(const Node& node) |
| 335 | { |
| 336 | return node.renderer() && node.renderer()->isInline(); |
| 337 | } |
| 338 | |
| 339 | // FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used. |
| 340 | // FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the |
| 341 | // block that contains the table and not the table, and this function should be the only one responsible for |
| 342 | // knowing about these kinds of special cases. |
| 343 | RefPtr<Element> enclosingBlock(RefPtr<Node> node, EditingBoundaryCrossingRule rule) |
| 344 | { |
| 345 | return dynamicDowncast<Element>(enclosingNodeOfType(firstPositionInOrBeforeNode(node.get()), isBlock, rule)); |
| 346 | } |
| 347 | |
| 348 | TextDirection directionOfEnclosingBlock(const Position& position) |
| 349 | { |
| 350 | auto block = enclosingBlock(protect(position.containerNode())); |
| 351 | if (!block) |
| 352 | return TextDirection::LTR; |
| 353 | CheckedPtr renderer = block->renderer(); |
| 354 | if (!renderer) |
| 355 | return TextDirection::LTR; |
| 356 | return renderer->writingMode().bidiDirection(); |
| 357 | } |
| 358 | |
| 359 | // This method is used to create positions in the DOM. It returns the maximum valid offset |
| 360 | // in a node. It returns 1 for some elements even though they do not have children, which |
| 361 | // creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent |
| 362 | // on a Position before using it to create a DOM Range, or an exception will be thrown. |
| 363 | int lastOffsetForEditing(const Node& node) |
| 364 | { |
| 365 | if (node.isCharacterDataNode() || node.hasChildNodes()) |
| 366 | return node.length(); |
| 367 | |
| 368 | // FIXME: Might be more helpful to return 1 for any node where editingIgnoresContent is true, even one that happens to have child nodes, like a select element with option node children. |
| 369 | return editingIgnoresContent(node) ? 1 : 0; |
| 370 | } |
| 371 | |
| 372 | bool isAmbiguousBoundaryCharacter(char16_t character) |
| 373 | { |
| 374 | // These are characters that can behave as word boundaries, but can appear within words. |
| 375 | // 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. |
| 376 | // FIXME: this is required until <rdar://problem/6853027> is fixed and text checking can do this for us. |
| 377 | return character == '\'' || character == '@' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; |
| 378 | } |
| 379 | |
| 380 | String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool shouldEmitNBSPbeforeEnd) |
| 381 | { |
| 382 | StringBuilder rebalancedString; |
| 383 | |
| 384 | bool previousCharacterWasSpace = false; |
| 385 | unsigned length = string.length(); |
| 386 | for (unsigned i = 0; i < length; ++i) { |
| 387 | auto character = string[i]; |
| 388 | if (!deprecatedIsEditingWhitespace(character)) { |
| 389 | previousCharacterWasSpace = false; |
| 390 | continue; |
| 391 | } |
| 392 | Latin1Character selectedWhitespaceCharacter; |
| 393 | // We need to ensure there is no next sibling text node. See https://bugs.webkit.org/show_bug.cgi?id=123163 |
| 394 | if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i == length - 1 && shouldEmitNBSPbeforeEnd)) { |
| 395 | selectedWhitespaceCharacter = noBreakSpace; |
| 396 | previousCharacterWasSpace = false; |
| 397 | } else { |
| 398 | selectedWhitespaceCharacter = ' '; |
| 399 | previousCharacterWasSpace = true; |
| 400 | } |
| 401 | if (character == selectedWhitespaceCharacter) |
| 402 | continue; |
| 403 | rebalancedString.reserveCapacity(length); |
| 404 | rebalancedString.appendSubstring(string, rebalancedString.length(), i - rebalancedString.length()); |
| 405 | rebalancedString.append(selectedWhitespaceCharacter); |
| 406 | } |
| 407 | |
| 408 | if (rebalancedString.isEmpty()) |
| 409 | return string; |
| 410 | |
| 411 | rebalancedString.reserveCapacity(length); |
| 412 | rebalancedString.appendSubstring(string, rebalancedString.length(), length - rebalancedString.length()); |
| 413 | return rebalancedString.toString(); |
| 414 | } |
| 415 | |
| 416 | bool isTableStructureNode(const Node& node) |
| 417 | { |
| 418 | auto* renderer = node.renderer(); |
| 419 | return renderer && (renderer->isRenderTableCell() || renderer->isRenderTableRow() || renderer->isRenderTableSection() || renderer->isRenderTableCol()); |
| 420 | } |
| 421 | |
| 422 | const String& nonBreakingSpaceString() |
| 423 | { |
| 424 | static NeverDestroyed<String> nonBreakingSpaceString(span(noBreakSpace)); |
| 425 | return nonBreakingSpaceString; |
| 426 | } |
| 427 | |
| 428 | RefPtr<Element> isFirstPositionAfterTable(const VisiblePosition& position) |
| 429 | { |
| 430 | Position upstream(position.deepEquivalent().upstream()); |
| 431 | RefPtr node = upstream.deprecatedNode(); |
| 432 | if (!node) |
| 433 | return nullptr; |
| 434 | CheckedPtr renderer = node->renderer(); |
| 435 | if (!renderer || !renderer->isRenderTable() || !upstream.atLastEditingPositionForNode()) |
| 436 | return nullptr; |
| 437 | return downcast<Element>(node.releaseNonNull()); |
| 438 | } |
| 439 | |
| 440 | RefPtr<Element> isLastPositionBeforeTable(const VisiblePosition& position) |
| 441 | { |
| 442 | Position downstream(position.deepEquivalent().downstream()); |
| 443 | RefPtr node = downstream.deprecatedNode(); |
| 444 | if (!node) |
| 445 | return nullptr; |
| 446 | CheckedPtr renderer = node->renderer(); |
| 447 | if (!renderer || !renderer->isRenderTable() || !downstream.atFirstEditingPositionForNode()) |
| 448 | return nullptr; |
| 449 | return downcast<Element>(node.releaseNonNull()); |
| 450 | } |
| 451 | |
| 452 | // Returns the visible position at the beginning of a node |
| 453 | VisiblePosition visiblePositionBeforeNode(Node& node) |
| 454 | { |
| 455 | if (node.hasChildNodes()) |
| 456 | return VisiblePosition(firstPositionInOrBeforeNode(&node)); |
| 457 | ASSERT(node.parentNode())((void)0); |
| 458 | ASSERT(!node.parentNode()->isShadowRoot())((void)0); |
| 459 | return positionInParentBeforeNode(node); |
| 460 | } |
| 461 | |
| 462 | // Returns the visible position at the ending of a node |
| 463 | VisiblePosition visiblePositionAfterNode(Node& node) |
| 464 | { |
| 465 | if (node.hasChildNodes()) |
| 466 | return VisiblePosition(lastPositionInOrAfterNode(&node)); |
| 467 | ASSERT(node.parentNode())((void)0); |
| 468 | ASSERT(!node.parentNode()->isShadowRoot())((void)0); |
| 469 | return positionInParentAfterNode(node); |
| 470 | } |
| 471 | |
| 472 | VisiblePosition closestEditablePositionInElementForAbsolutePoint(const Element& element, const IntPoint& point) |
| 473 | { |
| 474 | if (!element.isConnected() || !element.document().frame()) |
| 475 | return { }; |
| 476 | |
| 477 | Ref<const Element> protectedElement { element }; |
| 478 | protect(element.document())->updateLayoutIgnorePendingStylesheets(); |
| 479 | |
| 480 | CheckedPtr renderer = element.renderer(); |
| 481 | // Look at the inner element of a form control, not the control itself, as it is the editable part. |
| 482 | if (RefPtr formControlElement = dynamicDowncast<HTMLTextFormControlElement>(element)) { |
| 483 | if (!formControlElement->isInnerTextElementEditable()) |
| 484 | return { }; |
| 485 | if (RefPtr innerTextElement = formControlElement->innerTextElement()) |
| 486 | renderer = innerTextElement->renderer(); |
| 487 | } |
| 488 | if (!renderer) |
| 489 | return { }; |
| 490 | auto absoluteBoundingBox = renderer->absoluteBoundingBoxRect(); |
| 491 | auto constrainedAbsolutePoint = point.constrainedBetween(absoluteBoundingBox.minXMinYCorner(), absoluteBoundingBox.maxXMaxYCorner()); |
| 492 | auto localPoint = renderer->absoluteToLocal(constrainedAbsolutePoint, UseTransforms); |
| 493 | auto visiblePosition = renderer->visiblePositionForPoint(flooredLayoutPoint(localPoint), HitTestSource::User); |
| 494 | return isEditablePosition(visiblePosition.deepEquivalent()) ? visiblePosition : VisiblePosition { }; |
| 495 | } |
| 496 | |
| 497 | bool isListHTMLElement(Node* node) |
| 498 | { |
| 499 | return isAnyOf<HTMLUListElement, HTMLOListElement, HTMLDListElement>(node); |
| 500 | } |
| 501 | |
| 502 | bool isListItem(const Node& node) |
| 503 | { |
| 504 | return isListHTMLElement(node.parentNode()) || (node.renderer() && node.renderer()->isRenderListItem()); |
| 505 | } |
| 506 | |
| 507 | Element* enclosingElementWithTag(const Position& position, const QualifiedName& tagName) |
| 508 | { |
| 509 | auto root = highestEditableRoot(position); |
| 510 | for (RefPtr node = position.deprecatedNode(); node; node = node->parentNode()) { |
| 511 | if (root && !node->hasEditableStyle()) |
| 512 | continue; |
| 513 | auto* element = dynamicDowncast<Element>(*node); |
| 514 | if (!element) |
| 515 | continue; |
| 516 | if (element->hasTagName(tagName)) |
| 517 | return element; |
| 518 | if (node == root) |
| 519 | return nullptr; |
| 520 | } |
| 521 | return nullptr; |
| 522 | } |
| 523 | |
| 524 | RefPtr<Node> enclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node&), EditingBoundaryCrossingRule rule) |
| 525 | { |
| 526 | // FIXME: support CanSkipCrossEditingBoundary |
| 527 | ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary)((void)0); |
| 528 | auto root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; |
| 529 | for (RefPtr n = position.deprecatedNode(); n; n = n->parentNode()) { |
| 530 | // Don't return a non-editable node if the input position was editable, since |
| 531 | // the callers from editing will no doubt want to perform editing inside the returned node. |
| 532 | if (root && !n->hasEditableStyle()) |
| 533 | continue; |
| 534 | if (nodeIsOfType(*n)) |
| 535 | return n; |
| 536 | if (n == root) |
| 537 | return nullptr; |
| 538 | } |
| 539 | return nullptr; |
| 540 | } |
| 541 | |
| 542 | RefPtr<Node> highestEnclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node&), EditingBoundaryCrossingRule rule, Node* stayWithin) |
| 543 | { |
| 544 | RefPtr<Node> highest; |
| 545 | RefPtr root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; |
| 546 | for (RefPtr<Node> n = position.containerNode(); n && n != stayWithin; n = n->parentNode()) { |
| 547 | if (root && !n->hasEditableStyle()) |
| 548 | continue; |
| 549 | if (nodeIsOfType(*n)) |
| 550 | highest = n.get(); |
| 551 | if (n == root) |
| 552 | break; |
| 553 | } |
| 554 | return highest; |
| 555 | } |
| 556 | |
| 557 | static bool hasARenderedDescendant(Node* node, Node* excludedNode) |
| 558 | { |
| 559 | for (RefPtr n = node->firstChild(); n;) { |
| 560 | if (n == excludedNode) { |
| 561 | n = NodeTraversal::nextSkippingChildren(*n, node); |
| 562 | continue; |
| 563 | } |
| 564 | if (n->renderer()) |
| 565 | return true; |
| 566 | n = NodeTraversal::next(*n, node); |
| 567 | } |
| 568 | return false; |
| 569 | } |
| 570 | |
| 571 | RefPtr<Node> highestNodeToRemoveInPruning(Node* node) |
| 572 | { |
| 573 | RefPtr<Node> previousNode; |
| 574 | RefPtr rootEditableElement = node ? node->rootEditableElement() : nullptr; |
| 575 | for (RefPtr currentNode = node; currentNode; currentNode = currentNode->parentNode()) { |
| 576 | if (CheckedPtr renderer = currentNode->renderer()) { |
| 577 | if (!renderer->canHaveChildren() || hasARenderedDescendant(currentNode.get(), previousNode.get()) || rootEditableElement == currentNode.get()) |
| 578 | return previousNode; |
| 579 | } |
| 580 | previousNode = currentNode.get(); |
| 581 | } |
| 582 | return nullptr; |
| 583 | } |
| 584 | |
| 585 | RefPtr<Element> enclosingTableCell(const Position& position) |
| 586 | { |
| 587 | return downcast<Element>(enclosingNodeOfType(position, isTableCell)); |
| 588 | } |
| 589 | |
| 590 | RefPtr<Element> enclosingAnchorElement(const Position& p) |
| 591 | { |
| 592 | for (RefPtr node = p.deprecatedNode(); node; node = node->parentNode()) { |
| 593 | if (RefPtr element = dynamicDowncast<Element>(*node); element && element->isLink()) |
| 594 | return element; |
| 595 | } |
| 596 | return nullptr; |
| 597 | } |
| 598 | |
| 599 | RefPtr<HTMLElement> enclosingList(Node* node) |
| 600 | { |
| 601 | if (!node) |
| 602 | return nullptr; |
| 603 | |
| 604 | RefPtr root = highestEditableRoot(firstPositionInOrBeforeNode(node)); |
| 605 | |
| 606 | for (RefPtr ancestor = node->parentNode(); ancestor; ancestor = ancestor->parentNode()) { |
| 607 | auto* htmlElement = dynamicDowncast<HTMLElement>(*ancestor); |
| 608 | if (htmlElement && (isAnyOf<HTMLUListElement, HTMLOListElement>(*htmlElement))) |
| 609 | return htmlElement; |
| 610 | if (ancestor == root) |
| 611 | return nullptr; |
| 612 | } |
| 613 | |
| 614 | return nullptr; |
| 615 | } |
| 616 | |
| 617 | RefPtr<Node> enclosingListChild(Node* node) |
| 618 | { |
| 619 | if (!node) |
| 620 | return nullptr; |
| 621 | |
| 622 | // Check for a list item element, or for a node whose parent is a list element. Such a node |
| 623 | // will appear visually as a list item (but without a list marker) |
| 624 | RefPtr root = highestEditableRoot(firstPositionInOrBeforeNode(node)); |
| 625 | |
| 626 | // FIXME: This function is inappropriately named since it starts with node instead of node->parentNode() |
| 627 | for (RefPtr n = node; n && n->parentNode(); n = n->parentNode()) { |
| 628 | if (is<HTMLLIElement>(*n) || (isListHTMLElement(n->parentNode()) && n != root)) |
| 629 | return n; |
| 630 | if (n == root || isTableCell(*n)) |
| 631 | return nullptr; |
| 632 | } |
| 633 | |
| 634 | return nullptr; |
| 635 | } |
| 636 | |
| 637 | // FIXME: This function should not need to call isStartOfParagraph/isEndOfParagraph. |
| 638 | RefPtr<Node> enclosingEmptyListItem(const VisiblePosition& position) |
| 639 | { |
| 640 | // Check that position is on a line by itself inside a list item |
| 641 | RefPtr listChildNode = enclosingListChild(protect(position.deepEquivalent().deprecatedNode()).get()); |
| 642 | if (!listChildNode || !isStartOfParagraph(position) || !isEndOfParagraph(position)) |
| 643 | return nullptr; |
| 644 | |
| 645 | VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode.get())); |
| 646 | VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode.get())); |
| 647 | |
| 648 | if (firstInListChild != position || lastInListChild != position) |
| 649 | return nullptr; |
| 650 | |
| 651 | return listChildNode; |
| 652 | } |
| 653 | |
| 654 | RefPtr<HTMLElement> outermostEnclosingList(Node* node, Node* rootList) |
| 655 | { |
| 656 | RefPtr list = enclosingList(node); |
| 657 | if (!list) |
| 658 | return nullptr; |
| 659 | |
| 660 | while (RefPtr nextList = enclosingList(list.get())) { |
| 661 | if (nextList == rootList) |
| 662 | break; |
| 663 | list = WTF::move(nextList); |
| 664 | } |
| 665 | |
| 666 | return list; |
| 667 | } |
| 668 | |
| 669 | bool canMergeLists(Element* firstList, Element* secondList) |
| 670 | { |
| 671 | auto* first = dynamicDowncast<HTMLElement>(firstList); |
| 672 | auto* second = dynamicDowncast<HTMLElement>(secondList); |
| 673 | if (!first || !second) |
| 674 | return false; |
| 675 | |
| 676 | return first->localName() == second->localName() // make sure the list types match (ol vs. ul) |
| 677 | && first->hasEditableStyle() && second->hasEditableStyle() // both lists are editable |
| 678 | && first->rootEditableElement() == second->rootEditableElement() // don't cross editing boundaries |
| 679 | // Make sure there is no visible content between this li and the previous list. |
| 680 | && isVisiblyAdjacent(positionInParentAfterNode(*first), positionInParentBeforeNode(*second)); |
| 681 | } |
| 682 | |
| 683 | static Node* previousNodeConsideringAtomicNodes(const Node* node) |
| 684 | { |
| 685 | if (node->previousSibling()) { |
| 686 | SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* n = node->previousSibling(); |
| 687 | while (!isAtomicNode(n) && n->lastChild()) |
| 688 | n = n->lastChild(); |
| 689 | return n; |
| 690 | } |
| 691 | |
| 692 | return node->parentNode(); |
| 693 | } |
| 694 | |
| 695 | static Node* nextNodeConsideringAtomicNodes(const Node* node) |
| 696 | { |
| 697 | if (!isAtomicNode(node) && node->firstChild()) |
| 698 | return node->firstChild(); |
| 699 | if (node->nextSibling()) |
| 700 | return node->nextSibling(); |
| 701 | RefPtr<const Node> n = node; |
| 702 | while (n && !n->nextSibling()) |
| 703 | n = n->parentNode(); |
| 704 | if (n) |
| 705 | return n->nextSibling(); |
| 706 | return nullptr; |
| 707 | } |
| 708 | |
| 709 | Node* previousLeafNode(const Node* nodeArg) |
| 710 | { |
| 711 | SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* node = nodeArg; |
| 712 | while ((node = previousNodeConsideringAtomicNodes(node))) { |
| 713 | if (isAtomicNode(node)) |
| 714 | return const_cast<Node*>(node); |
| 715 | } |
| 716 | return nullptr; |
| 717 | } |
| 718 | |
| 719 | Node* nextLeafNode(const Node* nodeArg) |
| 720 | { |
| 721 | SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* node = nodeArg; |
| 722 | while ((node = nextNodeConsideringAtomicNodes(node))) { |
| 723 | if (isAtomicNode(node)) |
| 724 | return const_cast<Node*>(node); |
| 725 | } |
| 726 | return nullptr; |
| 727 | } |
| 728 | |
| 729 | // FIXME: Do not require renderer, so that this can be used within fragments. |
| 730 | bool isRenderedTable(const Node* node) |
| 731 | { |
| 732 | RefPtr element = dynamicDowncast<HTMLElement>(node); |
| 733 | if (!element) |
| 734 | return false; |
| 735 | auto* renderer = element->renderer(); |
| 736 | return renderer && renderer->isRenderTable(); |
| 737 | } |
| 738 | |
| 739 | bool isTableCell(const Node& node) |
| 740 | { |
| 741 | auto* renderer = node.renderer(); |
| 742 | if (!renderer) |
| 743 | return node.hasTagName(tdTag) || node.hasTagName(thTag); |
| 744 | return renderer->isRenderTableCell(); |
| 745 | } |
| 746 | |
| 747 | bool isEmptyTableCell(const Node* node) |
| 748 | { |
| 749 | // Returns true IFF the passed in node is one of: |
| 750 | // .) a table cell with no children, |
| 751 | // .) a table cell with a single BR child, and which has no other child renderers, including :before and :after renderers |
| 752 | // .) the BR child of such a table cell |
| 753 | |
| 754 | // Find rendered node |
| 755 | while (node && !node->renderer()) |
| 756 | node = node->parentNode(); |
| 757 | if (!node) |
| 758 | return false; |
| 759 | |
| 760 | // Make sure the rendered node is a table cell or <br>. |
| 761 | // If it's a <br>, then the parent node has to be a table cell. |
| 762 | CheckedPtr renderer = node->renderer(); |
| 763 | if (renderer->isBR()) { |
| 764 | renderer = renderer->parent(); |
| 765 | if (!renderer) |
| 766 | return false; |
| 767 | } |
| 768 | CheckedPtr renderTableCell = dynamicDowncast<RenderTableCell>(*renderer); |
| 769 | if (!renderTableCell) |
| 770 | return false; |
| 771 | |
| 772 | // Check that the table cell contains no child renderers except for perhaps a single <br>. |
| 773 | CheckedPtr childRenderer = renderTableCell->firstChild(); |
| 774 | if (!childRenderer) |
| 775 | return true; |
| 776 | if (!childRenderer->isBR()) |
| 777 | return false; |
| 778 | return !childRenderer->nextSibling(); |
| 779 | } |
| 780 | |
| 781 | Ref<HTMLElement> createDefaultParagraphElement(Document& document) |
| 782 | { |
| 783 | switch (document.editor().defaultParagraphSeparator()) { |
| 784 | case EditorParagraphSeparator::div: |
| 785 | return HTMLDivElement::create(document); |
| 786 | case EditorParagraphSeparator::p: |
| 787 | break; |
| 788 | } |
| 789 | return HTMLParagraphElement::create(document); |
| 790 | } |
| 791 | |
| 792 | Ref<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) |
| 793 | { |
| 794 | return HTMLElementFactory::createElement(name, document); |
| 795 | } |
| 796 | |
| 797 | Ref<HTMLElement> createHTMLElement(Document& document, const AtomString& tagName) |
| 798 | { |
| 799 | return createHTMLElement(document, QualifiedName(nullAtom(), tagName, xhtmlNamespaceURI)); |
| 800 | } |
| 801 | |
| 802 | HTMLSpanElement* tabSpanNode(Node* node) |
| 803 | { |
| 804 | if (auto* span = dynamicDowncast<HTMLSpanElement>(node); span && span->attributeWithoutSynchronization(classAttr) == AppleTabSpanClass) |
| 805 | return span; |
| 806 | return nullptr; |
| 807 | } |
| 808 | |
| 809 | HTMLSpanElement* parentTabSpanNode(Node* node) |
| 810 | { |
| 811 | return is<Text>(node) ? tabSpanNode(node->parentNode()) : nullptr; |
| 812 | } |
| 813 | |
| 814 | static Ref<Element> createTabSpanElement(Document& document, Text& tabTextNode) |
| 815 | { |
| 816 | auto spanElement = HTMLSpanElement::create(document); |
| 817 | |
| 818 | spanElement->setAttributeWithoutSynchronization(classAttr, AppleTabSpanClass); |
| 819 | spanElement->setAttribute(styleAttr, "white-space:pre"_s); |
| 820 | |
| 821 | spanElement->appendChild(tabTextNode); |
| 822 | |
| 823 | return spanElement; |
| 824 | } |
| 825 | |
| 826 | Ref<Element> createTabSpanElement(Document& document, String&& tabText) |
| 827 | { |
| 828 | return createTabSpanElement(document, document.createTextNode(WTF::move(tabText))); |
| 829 | } |
| 830 | |
| 831 | Ref<Element> createTabSpanElement(Document& document) |
| 832 | { |
| 833 | return createTabSpanElement(document, document.createEditingTextNode("\t"_s)); |
| 834 | } |
| 835 | |
| 836 | unsigned numEnclosingMailBlockquotes(const Position& position) |
| 837 | { |
| 838 | unsigned count = 0; |
| 839 | for (RefPtr node = position.deprecatedNode(); node; node = node->parentNode()) { |
| 840 | if (isMailBlockquote(*node)) |
| 841 | ++count; |
| 842 | } |
| 843 | return count; |
| 844 | } |
| 845 | |
| 846 | void updatePositionForNodeRemoval(Position& position, Node& node) |
| 847 | { |
| 848 | if (position.isNull()) |
| 849 | return; |
| 850 | switch (position.anchorType()) { |
| 851 | case Position::PositionIsBeforeChildren: |
| 852 | if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode())) |
| 853 | position = positionInParentBeforeNode(node); |
| 854 | break; |
| 855 | case Position::PositionIsAfterChildren: |
| 856 | if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode())) |
| 857 | position = positionInParentBeforeNode(node); |
| 858 | break; |
| 859 | case Position::PositionIsOffsetInAnchor: |
| 860 | if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.computeNodeIndex()) |
| 861 | position.moveToOffset(position.offsetInContainerNode() - 1); |
| 862 | else if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode())) |
| 863 | position = positionInParentBeforeNode(node); |
| 864 | break; |
| 865 | case Position::PositionIsAfterAnchor: |
| 866 | if (node.isShadowIncludingInclusiveAncestorOf(position.anchorNode())) |
| 867 | position = positionInParentAfterNode(node); |
| 868 | break; |
| 869 | case Position::PositionIsBeforeAnchor: |
| 870 | if (node.isShadowIncludingInclusiveAncestorOf(position.anchorNode())) |
| 871 | position = positionInParentBeforeNode(node); |
| 872 | break; |
| 873 | } |
| 874 | } |
| 875 | |
| 876 | bool isMailBlockquote(const Node& node) |
| 877 | { |
| 878 | auto* htmlElement = dynamicDowncast<HTMLElement>(node); |
| 879 | if (!htmlElement || !htmlElement->hasTagName(blockquoteTag)) |
| 880 | return false; |
| 881 | return htmlElement->attributeWithoutSynchronization(typeAttr) == "cite"_s; |
| 882 | } |
| 883 | |
| 884 | int caretMinOffset(const Node& node) |
| 885 | { |
| 886 | CheckedPtr renderer = node.renderer(); |
| 887 | ASSERT(!node.isCharacterDataNode() || !renderer || renderer->isRenderText())((void)0); |
| 888 | |
| 889 | if (renderer && renderer->isRenderText()) |
| 890 | return renderer->caretMinOffset(); |
| 891 | |
| 892 | if (RefPtr pictureElement = dynamicDowncast<HTMLPictureElement>(node)) { |
| 893 | if (RefPtr firstImage = childrenOfType<HTMLImageElement>(*pictureElement).first()) |
| 894 | return firstImage->computeNodeIndex(); |
| 895 | } |
| 896 | |
| 897 | return 0; |
| 898 | } |
| 899 | |
| 900 | // If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise |
| 901 | // return the number of children for container nodes and the length for unrendered text nodes. |
| 902 | int caretMaxOffset(const Node& node) |
| 903 | { |
| 904 | // For rendered text nodes, return the last position that a caret could occupy. |
| 905 | if (auto* text = dynamicDowncast<Text>(node)) { |
| 906 | if (CheckedPtr renderer = text->renderer()) |
| 907 | return renderer->caretMaxOffset(); |
| 908 | } |
| 909 | return lastOffsetForEditing(node); |
| 910 | } |
| 911 | |
| 912 | bool lineBreakExistsAtVisiblePosition(const VisiblePosition& position) |
| 913 | { |
| 914 | return lineBreakExistsAtPosition(position.deepEquivalent().downstream()); |
| 915 | } |
| 916 | |
| 917 | bool lineBreakExistsAtPosition(const Position& position) |
| 918 | { |
| 919 | if (position.isNull()) |
| 920 | return false; |
| 921 | |
| 922 | if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode()) |
| 923 | return true; |
| 924 | |
| 925 | if (!position.anchorNode()->renderer()) |
| 926 | return false; |
| 927 | |
| 928 | RefPtr textNode = dynamicDowncast<Text>(*position.anchorNode()); |
| 929 | if (!textNode || !textNode->renderer()->style().preserveNewline()) |
| 930 | return false; |
| 931 | |
| 932 | unsigned offset = position.offsetInContainerNode(); |
| 933 | return offset < textNode->length() && textNode->data()[offset] == '\n'; |
| 934 | } |
| 935 | |
| 936 | // Modifies selections that have an end point at the edge of a table |
| 937 | // that contains the other endpoint so that they don't confuse |
| 938 | // code that iterates over selected paragraphs. |
| 939 | VisibleSelection selectionForParagraphIteration(const VisibleSelection& original) |
| 940 | { |
| 941 | VisibleSelection newSelection(original); |
| 942 | VisiblePosition startOfSelection(newSelection.visibleStart()); |
| 943 | VisiblePosition endOfSelection(newSelection.visibleEnd()); |
| 944 | |
| 945 | // If the end of the selection to modify is just after a table, and |
| 946 | // if the start of the selection is inside that table, then the last paragraph |
| 947 | // that we'll want modify is the last one inside the table, not the table itself |
| 948 | // (a table is itself a paragraph). |
| 949 | if (RefPtr table = isFirstPositionAfterTable(endOfSelection)) { |
| 950 | if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) |
| 951 | newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary)); |
| 952 | } |
| 953 | |
| 954 | // If the start of the selection to modify is just before a table, |
| 955 | // and if the end of the selection is inside that table, then the first paragraph |
| 956 | // we'll want to modify is the first one inside the table, not the paragraph |
| 957 | // containing the table itself. |
| 958 | if (RefPtr table = isLastPositionBeforeTable(startOfSelection)) { |
| 959 | if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) |
| 960 | newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection); |
| 961 | } |
| 962 | |
| 963 | return newSelection; |
| 964 | } |
| 965 | |
| 966 | // FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between |
| 967 | // VisiblePositions and indices. But TextIterator iteration using TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions |
| 968 | // does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing |
| 969 | // opertion is unreliable. TextIterator's TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions mode needs to be fixed, |
| 970 | // or these functions need to be changed to iterate using actual VisiblePositions. |
| 971 | // FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices. |
| 972 | int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<ContainerNode>& scope) |
| 973 | { |
| 974 | if (visiblePosition.isNull()) |
| 975 | return 0; |
| 976 | |
| 977 | auto position = visiblePosition.deepEquivalent(); |
| 978 | Ref document = *position.document(); |
| 979 | |
| 980 | auto editableRoot = highestEditableRoot(position, AXObjectCache::accessibilityEnabled() ? HasEditableAXRole : ContentIsEditable); |
| 981 | if (editableRoot && !document->inDesignMode()) |
| 982 | scope = editableRoot; |
| 983 | else { |
| 984 | if (position.containerNode()->isInShadowTree()) |
| 985 | scope = position.containerNode()->containingShadowRoot(); |
| 986 | else |
| 987 | scope = WTF::move(document); |
| 988 | } |
| 989 | |
| 990 | auto range = *makeSimpleRange(makeBoundaryPointBeforeNodeContents(*scope), position); |
| 991 | return characterCount(range, TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions); |
| 992 | } |
| 993 | |
| 994 | // FIXME: Merge this function with the one above. |
| 995 | int indexForVisiblePosition(Node& node, const VisiblePosition& visiblePosition, TextIteratorBehaviors behaviors) |
| 996 | { |
| 997 | if (visiblePosition.isNull()) |
| 998 | return 0; |
| 999 | |
| 1000 | auto range = makeSimpleRange(makeBoundaryPointBeforeNodeContents(node), visiblePosition); |
| 1001 | return range ? characterCount(*range, behaviors) : 0; |
| 1002 | } |
| 1003 | |
| 1004 | VisiblePosition visiblePositionForPositionWithOffset(const VisiblePosition& position, int offset) |
| 1005 | { |
| 1006 | RefPtr<ContainerNode> root; |
| 1007 | unsigned startIndex = indexForVisiblePosition(position, root); |
| 1008 | if (!root) |
| 1009 | return { }; |
| 1010 | |
| 1011 | return visiblePositionForIndex(startIndex + offset, root.get()); |
| 1012 | } |
| 1013 | |
| 1014 | VisiblePosition visiblePositionForIndex(int index, Node* scope, TextIteratorBehaviors behaviors) |
| 1015 | { |
| 1016 | if (!scope) |
| 1017 | return { }; |
| 1018 | return { makeDeprecatedLegacyPosition(resolveCharacterLocation(makeRangeSelectingNodeContents(*scope), index, behaviors)) }; |
| 1019 | } |
| 1020 | |
| 1021 | VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node& node, int index) |
| 1022 | { |
| 1023 | if (index <= 0) |
| 1024 | return { firstPositionInOrBeforeNode(&node) }; |
| 1025 | |
| 1026 | auto range = makeRangeSelectingNodeContents(node); |
| 1027 | CharacterIterator it(range); |
| 1028 | if (!it.atEnd()) |
| 1029 | it.advance(index - 1); |
| 1030 | |
| 1031 | if (!it.atEnd() && it.text().length() == 1 && it.text()[0] == '\n') { |
| 1032 | // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines. |
| 1033 | it.advance(1); |
| 1034 | if (!it.atEnd()) |
| 1035 | return { makeDeprecatedLegacyPosition(it.range().start) }; |
| 1036 | } |
| 1037 | |
| 1038 | return { makeDeprecatedLegacyPosition((it.atEnd() ? range : it.range()).end), Affinity::Upstream }; |
| 1039 | } |
| 1040 | |
| 1041 | // Determines whether two positions are visibly next to each other (first then second) |
| 1042 | // while ignoring whitespaces and unrendered nodes |
| 1043 | static bool isVisiblyAdjacent(const Position& first, const Position& second) |
| 1044 | { |
| 1045 | return VisiblePosition(first) == VisiblePosition(second.upstream()); |
| 1046 | } |
| 1047 | |
| 1048 | // Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range. |
| 1049 | // Call this function to determine whether a node is visibly fit inside selectedRange |
| 1050 | bool isNodeVisiblyContainedWithin(Node& node, const SimpleRange& range) |
| 1051 | { |
| 1052 | if (contains<ComposedTree>(range, node)) |
| 1053 | return true; |
| 1054 | |
| 1055 | auto startPosition = makeDeprecatedLegacyPosition(range.start); |
| 1056 | auto endPosition = makeDeprecatedLegacyPosition(range.end); |
| 1057 | |
| 1058 | bool startIsVisuallySame = visiblePositionBeforeNode(node) == startPosition; |
| 1059 | if (startIsVisuallySame && positionInParentAfterNode(node) < endPosition) |
| 1060 | return true; |
| 1061 | |
| 1062 | bool endIsVisuallySame = visiblePositionAfterNode(node) == endPosition; |
| 1063 | if (endIsVisuallySame && startPosition < positionInParentBeforeNode(node)) |
| 1064 | return true; |
| 1065 | |
| 1066 | return startIsVisuallySame && endIsVisuallySame; |
| 1067 | } |
| 1068 | |
| 1069 | bool isRenderedAsNonInlineTableImageOrHR(const Node* node) |
| 1070 | { |
| 1071 | if (!node) |
| 1072 | return false; |
| 1073 | CheckedPtr renderer = node->renderer(); |
| 1074 | return renderer && !renderer->isInline() && (renderer->isRenderTable() || renderer->isImage() || renderer->isHR()); |
| 1075 | } |
| 1076 | |
| 1077 | Element* elementIfEquivalent(const Element& first, Node& second) |
| 1078 | { |
| 1079 | auto* secondElement = dynamicDowncast<Element>(second); |
| 1080 | if (secondElement && first.hasTagName(secondElement->tagQName()) && first.hasEquivalentAttributes(*secondElement)) |
| 1081 | return secondElement; |
| 1082 | return nullptr; |
| 1083 | } |
| 1084 | |
| 1085 | bool isNonTableCellHTMLBlockElement(const Node* node) |
| 1086 | { |
| 1087 | return node->hasTagName(listingTag) |
| 1088 | || node->hasTagName(olTag) |
| 1089 | || node->hasTagName(preTag) |
| 1090 | || is<HTMLTableElement>(*node) |
| 1091 | || node->hasTagName(ulTag) |
| 1092 | || node->hasTagName(xmpTag) |
| 1093 | || node->hasTagName(h1Tag) |
| 1094 | || node->hasTagName(h2Tag) |
| 1095 | || node->hasTagName(h3Tag) |
| 1096 | || node->hasTagName(h4Tag) |
| 1097 | || node->hasTagName(h5Tag); |
| 1098 | } |
| 1099 | |
| 1100 | Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) |
| 1101 | { |
| 1102 | // This function is used by range style computations to avoid bugs like: |
| 1103 | // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once |
| 1104 | // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up |
| 1105 | // with a spurious "mixed" style. |
| 1106 | |
| 1107 | auto visiblePosition = selection.visibleStart(); |
| 1108 | if (visiblePosition.isNull()) |
| 1109 | return { }; |
| 1110 | |
| 1111 | // if the selection is a caret, just return the position, since the style |
| 1112 | // behind us is relevant |
| 1113 | if (selection.isCaret()) |
| 1114 | return visiblePosition.deepEquivalent(); |
| 1115 | |
| 1116 | // if the selection starts just before a paragraph break, skip over it |
| 1117 | if (isEndOfParagraph(visiblePosition)) |
| 1118 | return visiblePosition.next().deepEquivalent().downstream(); |
| 1119 | |
| 1120 | // otherwise, make sure to be at the start of the first selected node, |
| 1121 | // instead of possibly at the end of the last node before the selection |
| 1122 | return visiblePosition.deepEquivalent().downstream(); |
| 1123 | } |
| 1124 | |
| 1125 | // FIXME: Should this be deprecated like deprecatedEnclosingBlockFlowElement is? |
| 1126 | static Element* elementIfBlockFlow(Node& node) |
| 1127 | { |
| 1128 | auto* element = dynamicDowncast<Element>(node); |
| 1129 | if (!element) |
| 1130 | return nullptr; |
| 1131 | auto* renderer = element->renderer(); |
| 1132 | return renderer && renderer->isRenderBlockFlow() ? element : nullptr; |
| 1133 | } |
| 1134 | |
| 1135 | bool isBlockFlowElement(const Node& node) |
| 1136 | { |
| 1137 | return elementIfBlockFlow(const_cast<Node&>(node)); |
| 1138 | } |
| 1139 | |
| 1140 | Element* deprecatedEnclosingBlockFlowElement(Node* node) |
| 1141 | { |
| 1142 | if (!node) |
| 1143 | return nullptr; |
| 1144 | if (auto* element = elementIfBlockFlow(*node)) |
| 1145 | return element; |
| 1146 | while ((node = node->parentNode())) { |
| 1147 | if (auto* element = elementIfBlockFlow(*node)) |
| 1148 | return element; |
| 1149 | if (auto* body = dynamicDowncast<HTMLBodyElement>(*node)) |
| 1150 | return body; |
| 1151 | } |
| 1152 | return nullptr; |
| 1153 | } |
| 1154 | |
| 1155 | static inline bool caretRendersInsideNode(const Node& node) |
| 1156 | { |
| 1157 | return !isRenderedTable(&node) && !editingIgnoresContent(node); |
| 1158 | } |
| 1159 | |
| 1160 | RenderBlock* rendererForCaretPainting(const Node* node) |
| 1161 | { |
| 1162 | if (!node) |
| 1163 | return nullptr; |
| 1164 | |
| 1165 | SUPPRESS_UNCHECKED_LOCAL[[clang::suppress]] auto* renderer = node->renderer(); |
| 1166 | if (!renderer) |
| 1167 | return nullptr; |
| 1168 | |
| 1169 | // If caretNode is a block and caret is inside it, then caret should be painted by that block. |
| 1170 | if (SUPPRESS_UNCHECKED_LOCAL[[clang::suppress]] auto* blockFlow = dynamicDowncast<RenderBlockFlow>(*renderer)) { |
| 1171 | if (caretRendersInsideNode(*node)) |
| 1172 | return blockFlow; |
| 1173 | } |
| 1174 | return renderer->containingBlock(); |
| 1175 | } |
| 1176 | |
| 1177 | LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition& caretPosition, RenderBlock*& caretPainter) |
| 1178 | { |
| 1179 | if (caretPosition.isNull()) |
| 1180 | return LayoutRect(); |
| 1181 | ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer())((void)0); |
| 1182 | auto [localRect, renderer] = caretPosition.localCaretRect(); |
| 1183 | return localCaretRectInRendererForRect(localRect, caretPosition.deepEquivalent().deprecatedNode(), renderer.get(), caretPainter); |
| 1184 | } |
| 1185 | |
| 1186 | LayoutRect localCaretRectInRendererForRect(LayoutRect& localRect, Node* node, RenderObject* renderer, RenderBlock*& caretPainter) |
| 1187 | { |
| 1188 | // Get the renderer that will be responsible for painting the caret |
| 1189 | // (which is either the renderer we just found, or one of its containers). |
| 1190 | caretPainter = rendererForCaretPainting(node); |
| 1191 | |
| 1192 | // Compute an offset between the renderer and the caretPainter. |
| 1193 | while (renderer != caretPainter) { |
| 1194 | CheckedPtr containerObject = renderer->container(); |
| 1195 | if (!containerObject) |
| 1196 | return LayoutRect(); |
| 1197 | localRect.move(renderer->offsetFromContainer(*containerObject, localRect.location())); |
| 1198 | renderer = containerObject.get(); |
| 1199 | } |
| 1200 | |
| 1201 | return localRect; |
| 1202 | } |
| 1203 | |
| 1204 | IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect& rect, bool* insideFixed) |
| 1205 | { |
| 1206 | if (insideFixed) |
| 1207 | *insideFixed = false; |
| 1208 | |
| 1209 | if (!rendererForCaretPainting || rect.isEmpty()) |
| 1210 | return IntRect(); |
| 1211 | |
| 1212 | LayoutRect localRect(rect); |
| 1213 | rendererForCaretPainting->flipForWritingMode(localRect); |
| 1214 | return rendererForCaretPainting->localToAbsoluteQuad(FloatRect(localRect), UseTransforms, insideFixed).enclosingBoundingBox(); |
| 1215 | } |
| 1216 | |
| 1217 | HashSet<Ref<HTMLImageElement>> visibleImageElementsInRangeWithNonLoadedImages(const SimpleRange& range) |
| 1218 | { |
| 1219 | HashSet<Ref<HTMLImageElement>> result; |
| 1220 | for (TextIterator iterator(range); !iterator.atEnd(); iterator.advance()) { |
| 1221 | RefPtr imageElement = dynamicDowncast<HTMLImageElement>(iterator.node()); |
| 1222 | if (!imageElement) |
| 1223 | continue; |
| 1224 | |
| 1225 | auto* cachedImage = imageElement->cachedImage(); |
Local variable 'cachedImage' is uncounted and unsafe | |
| 1226 | if (cachedImage && cachedImage->isLoading()) |
| 1227 | result.add(imageElement.releaseNonNull()); |
| 1228 | } |
| 1229 | return result; |
| 1230 | } |
| 1231 | |
| 1232 | enum class RangeEndpointsToAdjust : uint8_t { |
| 1233 | Start = 1 << 0, |
| 1234 | End = 1 << 1, |
| 1235 | }; |
| 1236 | |
| 1237 | static std::optional<unsigned> visualDistanceOnSameLine(const RenderedPosition& first, const RenderedPosition& second) |
| 1238 | { |
| 1239 | if (first.isNull() || second.isNull()) |
| 1240 | return std::nullopt; |
| 1241 | |
| 1242 | if (first.box() == second.box()) |
| 1243 | return std::max(first.offset(), second.offset()) - std::min(first.offset(), second.offset()); |
| 1244 | |
| 1245 | enum class VisualBoundary : bool { Left, Right }; |
| 1246 | auto distanceFromOffsetToVisualBoundary = [](const RenderedPosition& position, VisualBoundary boundary) { |
| 1247 | auto box = position.box(); |
| 1248 | auto offset = position.offset(); |
| 1249 | return (boundary == VisualBoundary::Left) == (box->direction() == TextDirection::LTR) |
| 1250 | ? std::max(box->minimumCaretOffset(), offset) - box->minimumCaretOffset() |
| 1251 | : std::max(box->maximumCaretOffset(), offset) - offset; |
| 1252 | }; |
| 1253 | |
| 1254 | unsigned distance = 0; |
| 1255 | bool foundFirst = false; |
| 1256 | bool foundSecond = false; |
| 1257 | for (auto box = first.lineBox()->lineLeftmostLeafBox(); box; box = box->nextLineRightwardOnLine()) { |
| 1258 | if (box == first.box()) { |
| 1259 | distance += distanceFromOffsetToVisualBoundary(first, foundSecond ? VisualBoundary::Left : VisualBoundary::Right); |
| 1260 | foundFirst = true; |
| 1261 | } else if (box == second.box()) { |
| 1262 | distance += distanceFromOffsetToVisualBoundary(second, foundFirst ? VisualBoundary::Left : VisualBoundary::Right); |
| 1263 | foundSecond = true; |
| 1264 | } else if (foundFirst || foundSecond) |
| 1265 | distance += box->maximumCaretOffset() - box->minimumCaretOffset(); |
| 1266 | |
| 1267 | if (foundFirst && foundSecond) |
| 1268 | return distance; |
| 1269 | } |
| 1270 | |
| 1271 | return std::nullopt; |
| 1272 | } |
| 1273 | |
| 1274 | static std::optional<BoundaryPoint> findBidiBoundary(const RenderedPosition& position, unsigned bidiLevel, SelectionExtentMovement movement, TextDirection selectionDirection) |
| 1275 | { |
| 1276 | auto leftBoundary = position.leftBoundaryOfBidiRun(bidiLevel); |
| 1277 | auto rightBoundary = position.rightBoundaryOfBidiRun(bidiLevel); |
| 1278 | |
| 1279 | bool moveLeft = [&] { |
| 1280 | switch (movement) { |
| 1281 | case SelectionExtentMovement::Left: |
| 1282 | return true; |
| 1283 | case SelectionExtentMovement::Right: |
| 1284 | return false; |
| 1285 | case SelectionExtentMovement::Closest: { |
| 1286 | auto distanceToLeft = visualDistanceOnSameLine(position, leftBoundary); |
| 1287 | if (!distanceToLeft) |
| 1288 | return false; |
| 1289 | |
| 1290 | auto distanceToRight = visualDistanceOnSameLine(position, rightBoundary); |
| 1291 | if (!distanceToRight) |
| 1292 | return true; |
| 1293 | |
| 1294 | return *distanceToLeft < *distanceToRight; |
| 1295 | } |
| 1296 | } |
| 1297 | ASSERT_NOT_REACHED()((void)0); |
| 1298 | return false; |
| 1299 | }(); |
| 1300 | // This looks unintuitive, but is necessary to ensure that the boundary is moved |
| 1301 | // (visually) to the left or right, respectively, in both LTR and RTL paragraphs. |
| 1302 | return (position.box()->direction() == selectionDirection) == moveLeft ? leftBoundary.boundaryPoint() : rightBoundary.boundaryPoint(); |
| 1303 | } |
| 1304 | |
| 1305 | enum class BoxIterationDirection : bool { SameAsLine, OppositeOfLine }; |
| 1306 | static InlineIterator::LeafBoxIterator advanceInDirection(InlineIterator::LeafBoxIterator box, TextDirection direction, BoxIterationDirection iterationDirection) |
| 1307 | { |
| 1308 | bool shouldMoveRight = (iterationDirection == BoxIterationDirection::SameAsLine) == (direction == TextDirection::LTR); |
| 1309 | return shouldMoveRight ? box->nextLineRightwardOnLine() : box->nextLineLeftwardOnLine(); |
| 1310 | } |
| 1311 | |
| 1312 | static void forEachRenderedBoxBetween(const RenderedPosition& first, const RenderedPosition& second, NOESCAPE[[clang::noescape]] const Function<IterationStatus(InlineIterator::LeafBoxIterator)>& callback) |
| 1313 | { |
| 1314 | if (first.isNull()) { |
| 1315 | ASSERT_NOT_REACHED()((void)0); |
| 1316 | return; |
| 1317 | } |
| 1318 | |
| 1319 | if (second.isNull()) { |
| 1320 | ASSERT_NOT_REACHED()((void)0); |
| 1321 | return; |
| 1322 | } |
| 1323 | |
| 1324 | if (first.lineBox().atEnd()) { |
| 1325 | ASSERT_NOT_REACHED()((void)0); |
| 1326 | return; |
| 1327 | } |
| 1328 | |
| 1329 | if (first.box() == second.box()) { |
| 1330 | callback(first.box()); |
| 1331 | return; |
| 1332 | } |
| 1333 | |
| 1334 | bool foundOneEndpoint = false; |
| 1335 | for (auto box = first.lineBox()->lineLeftmostLeafBox(); box; box = box->nextLineRightwardOnLine()) { |
| 1336 | bool atFirstEndpoint = box == first.box(); |
| 1337 | bool atSecondEndpoint = box == second.box(); |
| 1338 | bool atEndpoint = atFirstEndpoint || atSecondEndpoint; |
| 1339 | bool atLastEndpoint = atEndpoint && foundOneEndpoint; |
| 1340 | if (!atEndpoint && !foundOneEndpoint) |
| 1341 | continue; |
| 1342 | |
| 1343 | foundOneEndpoint = true; |
| 1344 | |
| 1345 | bool shouldSkipBox = [&] { |
| 1346 | if (!atEndpoint) |
| 1347 | return false; |
| 1348 | |
| 1349 | auto& position = (atFirstEndpoint ? first : second); |
| 1350 | return atLastEndpoint ? position.atLeftmostOffsetInBox() : position.atRightmostOffsetInBox(); |
| 1351 | }(); |
| 1352 | |
| 1353 | if (shouldSkipBox) |
| 1354 | continue; |
| 1355 | |
| 1356 | if (callback(box) == IterationStatus::Done) |
| 1357 | return; |
| 1358 | |
| 1359 | if (atLastEndpoint) |
| 1360 | return; |
| 1361 | } |
| 1362 | } |
| 1363 | |
| 1364 | PositionRange positionsForRange(const SimpleRange& range) |
| 1365 | { |
| 1366 | return { |
| 1367 | makeContainerOffsetPosition(range.start).downstream(), |
| 1368 | makeContainerOffsetPosition(range.end).upstream() |
| 1369 | }; |
| 1370 | } |
| 1371 | |
| 1372 | static InlineIterator::LeafBoxIterator boxWithMinimumBidiLevelBetween(const RenderedPosition& start, const RenderedPosition& end) |
| 1373 | { |
| 1374 | InlineIterator::LeafBoxIterator result; |
| 1375 | forEachRenderedBoxBetween(start, end, [&](auto box) { |
| 1376 | if (!result || box->bidiLevel() < result->bidiLevel()) |
| 1377 | result = box; |
| 1378 | return IterationStatus::Continue; |
| 1379 | }); |
| 1380 | return result; |
| 1381 | } |
| 1382 | |
| 1383 | TextDirection primaryDirectionForSingleLineRange(const Position& start, const Position& end) |
| 1384 | { |
| 1385 | ASSERT(inSameLine(start, end))((void)0); |
| 1386 | |
| 1387 | RenderedPosition renderedStart { start, Affinity::Downstream }; |
| 1388 | RenderedPosition renderedEnd { end, Affinity::Upstream }; |
| 1389 | |
| 1390 | auto direction = start.primaryDirection(); |
| 1391 | if (renderedStart.isNull() || renderedEnd.isNull()) |
| 1392 | return direction; |
| 1393 | |
| 1394 | if (auto box = boxWithMinimumBidiLevelBetween(renderedStart, renderedEnd)) |
| 1395 | return box->direction(); |
| 1396 | |
| 1397 | return direction; |
| 1398 | } |
| 1399 | |
| 1400 | static std::optional<SimpleRange> makeVisuallyContiguousIfNeeded(const SimpleRange& range, OptionSet<RangeEndpointsToAdjust> endpoints, SelectionExtentMovement movement) |
| 1401 | { |
| 1402 | if (range.collapsed()) |
| 1403 | return std::nullopt; |
| 1404 | |
| 1405 | auto [start, end] = positionsForRange(range); |
| 1406 | auto firstLineDirection = TextDirection::LTR; |
| 1407 | RenderedPosition renderedStart { start }; |
| 1408 | if (renderedStart.isNull() || renderedStart.lineBox().atEnd()) |
| 1409 | return std::nullopt; |
| 1410 | |
| 1411 | auto lastLineDirection = TextDirection::LTR; |
| 1412 | RenderedPosition renderedEnd { end }; |
| 1413 | if (renderedEnd.isNull() || renderedEnd.lineBox().atEnd()) |
| 1414 | return std::nullopt; |
| 1415 | |
| 1416 | if (renderedStart.box() == renderedEnd.box()) |
| 1417 | return std::nullopt; |
| 1418 | |
| 1419 | auto bidiLevelAtStart = renderedStart.box()->bidiLevel(); |
| 1420 | auto bidiLevelAtEnd = renderedEnd.box()->bidiLevel(); |
| 1421 | auto targetBidiLevelAtStart = bidiLevelAtStart; |
| 1422 | auto targetBidiLevelAtEnd = bidiLevelAtEnd; |
| 1423 | std::optional<BoundaryPoint> adjustedStart; |
| 1424 | std::optional<BoundaryPoint> adjustedEnd; |
| 1425 | if (inSameLine(start, end)) { |
| 1426 | if (auto box = boxWithMinimumBidiLevelBetween(renderedStart, renderedEnd)) { |
| 1427 | targetBidiLevelAtStart = box->bidiLevel(); |
| 1428 | targetBidiLevelAtEnd = targetBidiLevelAtStart; |
| 1429 | firstLineDirection = box->direction(); |
| 1430 | lastLineDirection = firstLineDirection; |
| 1431 | } |
| 1432 | } else { |
| 1433 | bool firstLineOnlyContainsSelectedTextInOppositeDirection = true; |
| 1434 | firstLineDirection = start.primaryDirection(); |
| 1435 | std::optional<BoundaryPoint> firstPositionForSelectedTextInOppositeDirectionOnFirstLine; |
| 1436 | for (auto box = renderedStart.box(); box; box = advanceInDirection(box, firstLineDirection, BoxIterationDirection::SameAsLine)) { |
| 1437 | targetBidiLevelAtStart = std::min(targetBidiLevelAtStart, box->bidiLevel()); |
| 1438 | |
| 1439 | if (box->direction() == firstLineDirection) |
| 1440 | firstLineOnlyContainsSelectedTextInOppositeDirection = false; |
| 1441 | |
| 1442 | if (!firstLineOnlyContainsSelectedTextInOppositeDirection) |
| 1443 | continue; |
| 1444 | |
| 1445 | if (RefPtr node = box->renderer().node()) { |
| 1446 | if (box->isText()) |
| 1447 | firstPositionForSelectedTextInOppositeDirectionOnFirstLine.emplace(node.releaseNonNull(), box->minimumCaretOffset()); |
| 1448 | else |
| 1449 | firstPositionForSelectedTextInOppositeDirectionOnFirstLine = makeBoundaryPointBeforeNode(node.releaseNonNull()); |
| 1450 | } |
| 1451 | } |
| 1452 | |
| 1453 | if (firstLineOnlyContainsSelectedTextInOppositeDirection) |
| 1454 | adjustedStart = WTF::move(firstPositionForSelectedTextInOppositeDirectionOnFirstLine); |
| 1455 | |
| 1456 | bool lastLineOnlyContainsSelectedTextInOppositeDirection = true; |
| 1457 | lastLineDirection = end.primaryDirection(); |
| 1458 | std::optional<BoundaryPoint> lastPositionForSelectedTextInOppositeDirectionOnLastLine; |
| 1459 | for (auto box = renderedEnd.box(); box; box = advanceInDirection(box, lastLineDirection, BoxIterationDirection::OppositeOfLine)) { |
| 1460 | targetBidiLevelAtEnd = std::min(targetBidiLevelAtEnd, box->bidiLevel()); |
| 1461 | |
| 1462 | if (box->direction() == lastLineDirection) |
| 1463 | lastLineOnlyContainsSelectedTextInOppositeDirection = false; |
| 1464 | |
| 1465 | if (!lastLineOnlyContainsSelectedTextInOppositeDirection) |
| 1466 | continue; |
| 1467 | |
| 1468 | if (RefPtr node = box->renderer().node()) { |
| 1469 | if (box->isText()) |
| 1470 | lastPositionForSelectedTextInOppositeDirectionOnLastLine.emplace(node.releaseNonNull(), box->maximumCaretOffset()); |
| 1471 | else |
| 1472 | lastPositionForSelectedTextInOppositeDirectionOnLastLine = makeBoundaryPointAfterNode(node.releaseNonNull()); |
| 1473 | } |
| 1474 | } |
| 1475 | |
| 1476 | if (lastLineOnlyContainsSelectedTextInOppositeDirection) |
| 1477 | adjustedEnd = WTF::move(lastPositionForSelectedTextInOppositeDirectionOnLastLine); |
| 1478 | } |
| 1479 | |
| 1480 | if (!adjustedStart && bidiLevelAtStart > targetBidiLevelAtStart && start != logicalStartOfLine(start) && endpoints.contains(RangeEndpointsToAdjust::Start)) |
| 1481 | adjustedStart = findBidiBoundary(renderedStart, targetBidiLevelAtStart + 1, movement, firstLineDirection); |
| 1482 | |
| 1483 | if (!adjustedEnd && bidiLevelAtEnd > targetBidiLevelAtEnd && end != logicalEndOfLine(end) && endpoints.contains(RangeEndpointsToAdjust::End)) |
| 1484 | adjustedEnd = findBidiBoundary(renderedEnd, targetBidiLevelAtEnd + 1, movement, lastLineDirection); |
| 1485 | |
| 1486 | if (!adjustedStart && !adjustedEnd) |
| 1487 | return std::nullopt; |
| 1488 | |
| 1489 | auto adjustedRange = range; |
| 1490 | if (adjustedStart) |
| 1491 | adjustedRange.start = WTF::move(*adjustedStart); |
| 1492 | |
| 1493 | if (adjustedEnd) |
| 1494 | adjustedRange.end = WTF::move(*adjustedEnd); |
| 1495 | |
| 1496 | if (!is_lt(treeOrder(adjustedRange.start, adjustedRange.end))) |
| 1497 | return std::nullopt; |
| 1498 | |
| 1499 | return adjustedRange; |
| 1500 | } |
| 1501 | |
| 1502 | SimpleRange adjustToVisuallyContiguousRange(const SimpleRange& range) |
| 1503 | { |
| 1504 | return makeVisuallyContiguousIfNeeded(range, { |
| 1505 | RangeEndpointsToAdjust::Start, |
| 1506 | RangeEndpointsToAdjust::End |
| 1507 | }, SelectionExtentMovement::Closest).value_or(range); |
| 1508 | } |
| 1509 | |
| 1510 | EnclosingLayerInfomation computeEnclosingLayer(const SimpleRange& range) |
| 1511 | { |
| 1512 | auto [start, end] = positionsForRange(range); |
| 1513 | |
| 1514 | if (start.isOrphan() || end.isOrphan()) |
| 1515 | return { }; |
| 1516 | |
| 1517 | if (!isEditablePosition(start) && range.collapsed()) |
| 1518 | return { }; |
| 1519 | |
| 1520 | auto findEnclosingLayer = [](const Position& position) -> RenderLayer* { |
| 1521 | RefPtr container = position.containerNode(); |
| 1522 | if (!container) |
| 1523 | return nullptr; |
| 1524 | |
| 1525 | CheckedPtr renderer = container->renderer(); |
| 1526 | if (!renderer) |
| 1527 | return nullptr; |
| 1528 | |
| 1529 | return renderer->enclosingLayer(); |
| 1530 | }; |
| 1531 | |
| 1532 | auto [startLayer, endLayer] = [&] -> std::pair<CheckedPtr<RenderLayer>, CheckedPtr<RenderLayer>> { |
| 1533 | if (RefPtr container = start.containerNode(); container && ImageOverlay::isInsideOverlay(*container)) { |
| 1534 | RefPtr host = container->shadowHost(); |
| 1535 | if (!host) { |
| 1536 | ASSERT_NOT_REACHED()((void)0); |
| 1537 | return { }; |
| 1538 | } |
| 1539 | |
| 1540 | CheckedPtr renderer = host->renderer(); |
| 1541 | if (!renderer) |
| 1542 | return { }; |
| 1543 | |
| 1544 | CheckedPtr enclosingLayer = renderer->enclosingLayer(); |
| 1545 | return { enclosingLayer, enclosingLayer }; |
| 1546 | } |
| 1547 | |
| 1548 | return { findEnclosingLayer(start), findEnclosingLayer(end) }; |
| 1549 | }(); |
| 1550 | |
| 1551 | if (!startLayer) |
| 1552 | return { }; |
| 1553 | |
| 1554 | if (!endLayer) |
| 1555 | return { }; |
| 1556 | |
| 1557 | for (CheckedPtr layer = startLayer->commonAncestorWithLayer(*endLayer); layer; layer = layer->enclosingContainingBlockLayer(CrossFrameBoundaries::Yes)) { |
| 1558 | if (!layer->isComposited()) |
| 1559 | continue; |
| 1560 | |
| 1561 | RefPtr graphicsLayer = [layer] -> RefPtr<GraphicsLayer> { |
| 1562 | auto* backing = layer->backing(); |
| 1563 | if (RefPtr scrolledContentsLayer = backing->scrolledContentsLayer()) |
| 1564 | return scrolledContentsLayer; |
| 1565 | |
| 1566 | if (RefPtr foregroundLayer = backing->foregroundLayer()) |
| 1567 | return foregroundLayer; |
| 1568 | |
| 1569 | if (backing->isFrameLayerWithTiledBacking()) |
| 1570 | return backing->parentForSublayers(); |
| 1571 | |
| 1572 | return backing->graphicsLayer(); |
| 1573 | }(); |
| 1574 | |
| 1575 | if (!graphicsLayer) |
| 1576 | continue; |
| 1577 | |
| 1578 | auto identifier = graphicsLayer->layerIDIgnoringStructuralLayer(); |
| 1579 | if (!identifier) |
| 1580 | continue; |
| 1581 | |
| 1582 | return { WTF::move(startLayer), WTF::move(endLayer), WTF::move(layer), WTF::move(graphicsLayer), WTF::move(identifier) }; |
| 1583 | } |
| 1584 | return { }; |
| 1585 | } |
| 1586 | |
| 1587 | void adjustVisibleExtentPreservingVisualContiguity(const VisiblePosition& base, VisiblePosition& extent, SelectionExtentMovement movement) |
| 1588 | { |
| 1589 | auto start = makeBoundaryPoint(base.deepEquivalent()); |
| 1590 | auto end = makeBoundaryPoint(extent.deepEquivalent()); |
| 1591 | if (!start || !end) |
| 1592 | return; |
| 1593 | |
| 1594 | OptionSet<RangeEndpointsToAdjust> endpoints; |
| 1595 | auto baseExtentOrder = treeOrder<ComposedTree>(*start, *end); |
| 1596 | bool startIsMoving = is_gt(baseExtentOrder); |
| 1597 | bool endIsMoving = is_lt(baseExtentOrder); |
| 1598 | if (startIsMoving) { |
| 1599 | std::swap(start, end); |
| 1600 | endpoints.add(RangeEndpointsToAdjust::Start); |
| 1601 | } else if (endIsMoving) |
| 1602 | endpoints.add(RangeEndpointsToAdjust::End); |
| 1603 | else |
| 1604 | return; |
| 1605 | |
| 1606 | auto adjustedRange = makeVisuallyContiguousIfNeeded({ WTF::move(*start), WTF::move(*end) }, endpoints, movement); |
| 1607 | if (!adjustedRange) |
| 1608 | return; |
| 1609 | |
| 1610 | extent = { makeContainerOffsetPosition(startIsMoving ? adjustedRange->start : adjustedRange->end) }; |
| 1611 | } |
| 1612 | |
| 1613 | bool crossesBidiTextBoundaryInSameLine(const VisiblePosition& position, const VisiblePosition& other) |
| 1614 | { |
| 1615 | if (!inSameLine(position, other)) |
| 1616 | return false; |
| 1617 | |
| 1618 | std::optional<unsigned char> currentLevel; |
| 1619 | bool foundDifferentBidiLevel = false; |
| 1620 | forEachRenderedBoxBetween(RenderedPosition { position }, RenderedPosition { other }, [&](auto box) { |
| 1621 | auto bidiLevel = box->bidiLevel(); |
| 1622 | if (!currentLevel) { |
| 1623 | currentLevel = bidiLevel; |
| 1624 | return IterationStatus::Continue; |
| 1625 | } |
| 1626 | |
| 1627 | if (currentLevel == bidiLevel) |
| 1628 | return IterationStatus::Continue; |
| 1629 | |
| 1630 | foundDifferentBidiLevel = true; |
| 1631 | return IterationStatus::Done; |
| 1632 | }); |
| 1633 | |
| 1634 | return foundDifferentBidiLevel; |
| 1635 | } |
| 1636 | |
| 1637 | } // namespace WebCore |