diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing/htmlediting.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/editing/htmlediting.cpp')
-rw-r--r-- | Source/WebCore/editing/htmlediting.cpp | 1116 |
1 files changed, 570 insertions, 546 deletions
diff --git a/Source/WebCore/editing/htmlediting.cpp b/Source/WebCore/editing/htmlediting.cpp index 9e721c6da..f40341c9e 100644 --- a/Source/WebCore/editing/htmlediting.cpp +++ b/Source/WebCore/editing/htmlediting.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2004-2007, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR @@ -29,39 +29,49 @@ #include "AXObjectCache.h" #include "Document.h" #include "Editor.h" -#include "ExceptionCodePlaceholder.h" #include "Frame.h" -#include "HTMLBRElement.h" +#include "HTMLBodyElement.h" +#include "HTMLDListElement.h" #include "HTMLDivElement.h" #include "HTMLElementFactory.h" #include "HTMLInterchange.h" #include "HTMLLIElement.h" #include "HTMLNames.h" #include "HTMLOListElement.h" -#include "HTMLObjectElement.h" #include "HTMLParagraphElement.h" +#include "HTMLSpanElement.h" #include "HTMLTableElement.h" #include "HTMLTextFormControlElement.h" #include "HTMLUListElement.h" #include "NodeTraversal.h" #include "PositionIterator.h" +#include "RenderBlock.h" #include "RenderElement.h" +#include "RenderTableCell.h" #include "ShadowRoot.h" #include "Text.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include <wtf/Assertions.h> #include <wtf/StdLibExtras.h> +#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { using namespace HTMLNames; -// Atomic means that the node has no children, or has children which are ignored for the -// purposes of editing. -bool isAtomicNode(const Node *node) +static bool isVisiblyAdjacent(const Position&, const Position&); + +bool canHaveChildrenForEditing(const Node& node) { - return node && (!node->hasChildNodes() || editingIgnoresContent(node)); + return !is<Text>(node) && node.canContainRangeEndPoint(); +} + +// Atomic means that the node has no children, or has children which are ignored for the purposes of editing. +bool isAtomicNode(const Node* node) +{ + return node && (!node->hasChildNodes() || editingIgnoresContent(*node)); } // Compare two positions, taking into account the possibility that one or both @@ -70,16 +80,15 @@ int comparePositions(const Position& a, const Position& b) { TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode()); - ASSERT(commonScope); if (!commonScope) return 0; - Node* nodeA = commonScope->ancestorInThisScope(a.containerNode()); + Node* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode()); ASSERT(nodeA); bool hasDescendentA = nodeA != a.containerNode(); int offsetA = hasDescendentA ? 0 : a.computeOffsetInContainerNode(); - Node* nodeB = commonScope->ancestorInThisScope(b.containerNode()); + Node* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode()); ASSERT(nodeB); bool hasDescendentB = nodeB != b.containerNode(); int offsetB = hasDescendentB ? 0 : b.computeOffsetInContainerNode(); @@ -92,7 +101,10 @@ int comparePositions(const Position& a, const Position& b) bias = 1; } - int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB, IGNORE_EXCEPTION); + auto comparisonResult = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB); + if (comparisonResult.hasException()) + return bias; + auto result = comparisonResult.releaseReturnValue(); return result ? result : bias; } @@ -101,202 +113,230 @@ int comparePositions(const VisiblePosition& a, const VisiblePosition& b) return comparePositions(a.deepEquivalent(), b.deepEquivalent()); } -Node* highestEditableRoot(const Position& position, EditableType editableType) +ContainerNode* highestEditableRoot(const Position& position, EditableType editableType) { - Node* node = position.deprecatedNode(); - if (!node) - return 0; - - Node* highestEditableRoot = editableRootForPosition(position, editableType); + ContainerNode* highestEditableRoot = editableRootForPosition(position, editableType); if (!highestEditableRoot) - return 0; + return nullptr; - node = highestEditableRoot; - while (!node->hasTagName(bodyTag)) { + for (ContainerNode* node = highestEditableRoot; !is<HTMLBodyElement>(*node); ) { node = node->parentNode(); if (!node) break; - if (node->hasEditableStyle(editableType)) + // FIXME: Can this ever be a Document or DocumentFragment? If not, this should return Element* instead. + if (hasEditableStyle(*node, editableType)) highestEditableRoot = node; } return highestEditableRoot; } -Node* lowestEditableAncestor(Node* node) +Element* lowestEditableAncestor(Node* node) { - if (!node) - return 0; - - while (node) { + for (; node; node = node->parentNode()) { if (node->hasEditableStyle()) return node->rootEditableElement(); - if (node->hasTagName(bodyTag)) + if (is<HTMLBodyElement>(*node)) break; - node = node->parentNode(); } - - return 0; + return nullptr; } -bool isEditablePosition(const Position& p, EditableType editableType, EUpdateStyle updateStyle) +static bool isEditableToAccessibility(const Node& node) { - Node* node = p.deprecatedNode(); - if (!node) + ASSERT(AXObjectCache::accessibilityEnabled()); + ASSERT(node.document().existingAXObjectCache()); + + if (auto* cache = node.document().existingAXObjectCache()) + return cache->rootAXEditableElement(&node); + + return false; +} + +static bool computeEditability(const Node& node, EditableType editableType, Node::ShouldUpdateStyle shouldUpdateStyle) +{ + if (node.computeEditability(Node::UserSelectAllIsAlwaysNonEditable, shouldUpdateStyle) != Node::Editability::ReadOnly) + return true; + + switch (editableType) { + case ContentIsEditable: return false; - if (updateStyle == UpdateStyle) - node->document().updateLayoutIgnorePendingStylesheets(); - else - ASSERT(updateStyle == DoNotUpdateStyle); + case HasEditableAXRole: + return isEditableToAccessibility(node); + } + ASSERT_NOT_REACHED(); + return false; +} - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->hasEditableStyle(editableType); +bool hasEditableStyle(const Node& node, EditableType editableType) +{ + return computeEditability(node, editableType, Node::ShouldUpdateStyle::DoNotUpdate); } -bool isAtUnsplittableElement(const Position& pos) +bool isEditableNode(const Node& node) { - Node* node = pos.deprecatedNode(); - return (node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell)); + return computeEditability(node, ContentIsEditable, Node::ShouldUpdateStyle::Update); } - - -bool isRichlyEditablePosition(const Position& p, EditableType editableType) + +bool isEditablePosition(const Position& position, EditableType editableType) { - Node* node = p.deprecatedNode(); - if (!node) - return false; - - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->hasRichlyEditableStyle(editableType); + Node* node = position.containerNode(); + return node && computeEditability(*node, editableType, Node::ShouldUpdateStyle::Update); } -Element* editableRootForPosition(const Position& p, EditableType editableType) +bool isAtUnsplittableElement(const Position& position) { - Node* node = p.containerNode(); + Node* node = position.containerNode(); + return node == editableRootForPosition(position) || node == enclosingNodeOfType(position, isTableCell); +} + +bool isRichlyEditablePosition(const Position& position) +{ + auto* node = position.containerNode(); + return node && node->hasRichlyEditableStyle(); +} + +Element* editableRootForPosition(const Position& position, EditableType editableType) +{ + Node* node = position.containerNode(); if (!node) - return 0; - - if (node->renderer() && node->renderer()->isTable()) - node = node->parentNode(); - - return node->rootEditableElement(editableType); + return nullptr; + + switch (editableType) { + case HasEditableAXRole: + if (auto* cache = node->document().existingAXObjectCache()) + return const_cast<Element*>(cache->rootAXEditableElement(node)); + FALLTHROUGH; + case ContentIsEditable: + return node->rootEditableElement(); + } + return nullptr; } // Finds the enclosing element until which the tree can be split. // When a user hits ENTER, he/she won't expect this element to be split into two. // You may pass it as the second argument of splitTreeToNode. -Element* unsplittableElementForPosition(const Position& p) +Element* unsplittableElementForPosition(const Position& position) { // Since enclosingNodeOfType won't search beyond the highest root editable node, // this code works even if the closest table cell was outside of the root editable node. - Element* enclosingCell = toElement(enclosingNodeOfType(p, &isTableCell)); - if (enclosingCell) + if (auto* enclosingCell = downcast<Element>(enclosingNodeOfType(position, &isTableCell))) return enclosingCell; - - return editableRootForPosition(p); + return editableRootForPosition(position); } Position nextCandidate(const Position& position) { - PositionIterator p = position; - while (!p.atEnd()) { - p.increment(); - if (p.isCandidate()) - return p; + for (PositionIterator nextPosition = position; !nextPosition.atEnd(); ) { + nextPosition.increment(); + if (nextPosition.isCandidate()) + return nextPosition; } - return Position(); + return { }; } Position nextVisuallyDistinctCandidate(const Position& position) { - Position p = position; - Position downstreamStart = p.downstream(); - while (!p.atEndOfTree()) { - p = p.next(Character); - if (p.isCandidate() && p.downstream() != downstreamStart) - return p; + // FIXME: Use PositionIterator instead. + Position nextPosition = position; + Position downstreamStart = nextPosition.downstream(); + while (!nextPosition.atEndOfTree()) { + nextPosition = nextPosition.next(Character); + if (nextPosition.isCandidate() && nextPosition.downstream() != downstreamStart) + return nextPosition; + if (auto* node = nextPosition.containerNode()) { + if (!node->renderer()) + nextPosition = lastPositionInOrAfterNode(node); + } } - return Position(); + return { }; } Position previousCandidate(const Position& position) { - PositionIterator p = position; - while (!p.atStart()) { - p.decrement(); - if (p.isCandidate()) - return p; + PositionIterator previousPosition = position; + while (!previousPosition.atStart()) { + previousPosition.decrement(); + if (previousPosition.isCandidate()) + return previousPosition; } - return Position(); + return { }; } Position previousVisuallyDistinctCandidate(const Position& position) { - Position p = position; - Position downstreamStart = p.downstream(); - while (!p.atStartOfTree()) { - p = p.previous(Character); - if (p.isCandidate() && p.downstream() != downstreamStart) - return p; + // FIXME: Use PositionIterator instead. + Position previousPosition = position; + Position downstreamStart = previousPosition.downstream(); + while (!previousPosition.atStartOfTree()) { + previousPosition = previousPosition.previous(Character); + if (previousPosition.isCandidate() && previousPosition.downstream() != downstreamStart) + return previousPosition; + if (auto* node = previousPosition.containerNode()) { + if (!node->renderer()) + previousPosition = firstPositionInOrBeforeNode(node); + } } - return Position(); + return { }; } -VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot) +Position firstEditablePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot) { + if (!highestRoot) + return { }; + // position falls before highestRoot. if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->hasEditableStyle()) return firstPositionInNode(highestRoot); - Position p = position; + Position candidate = position; if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { - Node* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(p.deprecatedNode()); + auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); if (!shadowAncestor) - return VisiblePosition(); + return { }; - p = positionAfterNode(shadowAncestor); + candidate = positionAfterNode(shadowAncestor); } - while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) - p = isAtomicNode(p.deprecatedNode()) ? positionInParentAfterNode(p.deprecatedNode()) : nextVisuallyDistinctCandidate(p); - - if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot)) - return VisiblePosition(); - - return VisiblePosition(p); + while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentAfterNode(candidate.deprecatedNode()) : nextVisuallyDistinctCandidate(candidate); + + if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + return { }; + + return candidate; } -VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot) +Position lastEditablePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot) { + if (!highestRoot) + return { }; + // When position falls after highestRoot, the result is easy to compute. if (comparePositions(position, lastPositionInNode(highestRoot)) == 1) return lastPositionInNode(highestRoot); - Position p = position; + Position candidate = position; if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) { - Node* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(p.deprecatedNode()); + auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode()); if (!shadowAncestor) - return VisiblePosition(); + return { }; - p = firstPositionInOrBeforeNode(shadowAncestor); + candidate = firstPositionInOrBeforeNode(shadowAncestor); } + + while (candidate.deprecatedNode() && !isEditablePosition(candidate) && candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentBeforeNode(candidate.deprecatedNode()) : previousVisuallyDistinctCandidate(candidate); - while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) - p = isAtomicNode(p.deprecatedNode()) ? positionInParentBeforeNode(p.deprecatedNode()) : previousVisuallyDistinctCandidate(p); - - if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot)) - return VisiblePosition(); + if (candidate.deprecatedNode() && candidate.deprecatedNode() != highestRoot && !candidate.deprecatedNode()->isDescendantOf(*highestRoot)) + return { }; - return VisiblePosition(p); + return candidate; } -// FIXME: The method name, comment, and code say three different things here! +// FIXME: The function name, comment, and code say three different things here! // Whether or not content before and after this node will collapse onto the same line as it. bool isBlock(const Node* node) { @@ -315,7 +355,7 @@ bool isInline(const Node* node) Element* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule) { Node* enclosingNode = enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule); - return enclosingNode && enclosingNode->isElementNode() ? toElement(enclosingNode) : 0; + return is<Element>(enclosingNode) ? downcast<Element>(enclosingNode) : nullptr; } TextDirection directionOfEnclosingBlock(const Position& position) @@ -333,77 +373,89 @@ TextDirection directionOfEnclosingBlock(const Position& position) // in a node. It returns 1 for some elements even though they do not have children, which // creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent // on a Position before using it to create a DOM Range, or an exception will be thrown. -int lastOffsetForEditing(const Node* node) +int lastOffsetForEditing(const Node& node) { - ASSERT(node); - if (!node) - return 0; - if (node->offsetInCharacters()) - return node->maxCharacterOffset(); + if (node.offsetInCharacters()) + return node.maxCharacterOffset(); - if (node->hasChildNodes()) - return node->childNodeCount(); + if (node.hasChildNodes()) + return node.countChildNodes(); - // NOTE: This should preempt the childNodeCount for, e.g., select nodes + // NOTE: This should preempt the countChildNodes() for, e.g., select nodes. + // FIXME: What does the comment above mean? if (editingIgnoresContent(node)) return 1; return 0; } +bool isAmbiguousBoundaryCharacter(UChar character) +{ + // These are characters that can behave as word boundaries, but can appear within words. + // 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. + // FIXME: this is required until <rdar://problem/6853027> is fixed and text checking can do this for us. + return character == '\'' || character == '@' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim; +} + String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph) { - Vector<UChar> rebalancedString; - append(rebalancedString, string); + StringBuilder rebalancedString; bool previousCharacterWasSpace = false; - for (size_t i = 0; i < rebalancedString.size(); i++) { - if (!isWhitespace(rebalancedString[i])) { + unsigned length = string.length(); + for (unsigned i = 0; i < length; ++i) { + auto character = string[i]; + if (!deprecatedIsEditingWhitespace(character)) { previousCharacterWasSpace = false; continue; } - - if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i + 1 == rebalancedString.size() && endIsEndOfParagraph)) { - rebalancedString[i] = noBreakSpace; + LChar selectedWhitespaceCharacter; + if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i == length - 1 && endIsEndOfParagraph)) { + selectedWhitespaceCharacter = noBreakSpace; previousCharacterWasSpace = false; } else { - rebalancedString[i] = ' '; + selectedWhitespaceCharacter = ' '; previousCharacterWasSpace = true; } - + if (character == selectedWhitespaceCharacter) + continue; + rebalancedString.reserveCapacity(length); + rebalancedString.append(string, rebalancedString.length(), i - rebalancedString.length()); + rebalancedString.append(selectedWhitespaceCharacter); } - return String::adopt(rebalancedString); + if (rebalancedString.isEmpty()) + return string; + + rebalancedString.reserveCapacity(length); + rebalancedString.append(string, rebalancedString.length(), length - rebalancedString.length()); + return rebalancedString.toString(); } -bool isTableStructureNode(const Node *node) +bool isTableStructureNode(const Node* node) { - RenderObject* renderer = node->renderer(); - return (renderer && (renderer->isTableCell() || renderer->isTableRow() || renderer->isTableSection() || renderer->isRenderTableCol())); + auto* renderer = node->renderer(); + return renderer && (renderer->isTableCell() || renderer->isTableRow() || renderer->isTableSection() || renderer->isRenderTableCol()); } const String& nonBreakingSpaceString() { - DEFINE_STATIC_LOCAL(String, nonBreakingSpaceString, (&noBreakSpace, 1)); + static NeverDestroyed<String> nonBreakingSpaceString(&noBreakSpace, 1); return nonBreakingSpaceString; } -// FIXME: need to dump this -bool isSpecialElement(const Node *n) +static bool isSpecialHTMLElement(const Node* node) { - if (!n) - return false; - - if (!n->isHTMLElement()) + if (!is<HTMLElement>(node)) return false; - if (n->isLink()) + if (downcast<HTMLElement>(*node).isLink()) return true; - RenderObject* renderer = n->renderer(); + auto* renderer = downcast<HTMLElement>(*node).renderer(); if (!renderer) return false; - + if (renderer->style().display() == TABLE || renderer->style().display() == INLINE_TABLE) return true; @@ -412,162 +464,140 @@ bool isSpecialElement(const Node *n) if (renderer->style().position() != StaticPosition) return true; - - return false; -} -static Node* firstInSpecialElement(const Position& pos) -{ - Node* rootEditableElement = pos.containerNode()->rootEditableElement(); - for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) - if (isSpecialElement(n)) { - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - VisiblePosition firstInElement = VisiblePosition(firstPositionInOrBeforeNode(n), DOWNSTREAM); - if (isTableElement(n) && vPos == firstInElement.next()) - return n; - if (vPos == firstInElement) - return n; - } - return 0; + return false; } -static Node* lastInSpecialElement(const Position& pos) +static HTMLElement* firstInSpecialElement(const Position& position) { - Node* rootEditableElement = pos.containerNode()->rootEditableElement(); - for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) - if (isSpecialElement(n)) { - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - VisiblePosition lastInElement = VisiblePosition(lastPositionInOrAfterNode(n), DOWNSTREAM); - if (isTableElement(n) && vPos == lastInElement.previous()) - return n; - if (vPos == lastInElement) - return n; - } - return 0; + auto* rootEditableElement = position.containerNode()->rootEditableElement(); + for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { + if (!isSpecialHTMLElement(node)) + continue; + VisiblePosition vPos(position, DOWNSTREAM); + VisiblePosition firstInElement(firstPositionInOrBeforeNode(node), DOWNSTREAM); + if ((isRenderedTable(node) && vPos == firstInElement.next()) || vPos == firstInElement) + return &downcast<HTMLElement>(*node); + } + return nullptr; } -bool isFirstVisiblePositionInSpecialElement(const Position& pos) +static HTMLElement* lastInSpecialElement(const Position& position) { - return firstInSpecialElement(pos); + auto* rootEditableElement = position.containerNode()->rootEditableElement(); + for (Node* node = position.deprecatedNode(); node && node->rootEditableElement() == rootEditableElement; node = node->parentNode()) { + if (!isSpecialHTMLElement(node)) + continue; + VisiblePosition vPos(position, DOWNSTREAM); + VisiblePosition lastInElement(lastPositionInOrAfterNode(node), DOWNSTREAM); + if ((isRenderedTable(node) && vPos == lastInElement.previous()) || vPos == lastInElement) + return &downcast<HTMLElement>(*node); + } + return nullptr; } -Position positionBeforeContainingSpecialElement(const Position& pos, Node** containingSpecialElement) +Position positionBeforeContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) { - Node* n = firstInSpecialElement(pos); - if (!n) - return pos; - Position result = positionInParentBeforeNode(n); - if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement()) - return pos; + auto* element = firstInSpecialElement(position); + if (!element) + return position; + Position result = positionInParentBeforeNode(element); + if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) + return position; if (containingSpecialElement) - *containingSpecialElement = n; + *containingSpecialElement = element; return result; } -bool isLastVisiblePositionInSpecialElement(const Position& pos) -{ - return lastInSpecialElement(pos); -} - -Position positionAfterContainingSpecialElement(const Position& pos, Node **containingSpecialElement) +Position positionAfterContainingSpecialElement(const Position& position, HTMLElement** containingSpecialElement) { - Node* n = lastInSpecialElement(pos); - if (!n) - return pos; - Position result = positionInParentAfterNode(n); - if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement()) - return pos; + auto* element = lastInSpecialElement(position); + if (!element) + return position; + Position result = positionInParentAfterNode(element); + if (result.isNull() || result.deprecatedNode()->rootEditableElement() != position.deprecatedNode()->rootEditableElement()) + return position; if (containingSpecialElement) - *containingSpecialElement = n; + *containingSpecialElement = element; return result; } -Position positionOutsideContainingSpecialElement(const Position &pos, Node **containingSpecialElement) -{ - if (isFirstVisiblePositionInSpecialElement(pos)) - return positionBeforeContainingSpecialElement(pos, containingSpecialElement); - if (isLastVisiblePositionInSpecialElement(pos)) - return positionAfterContainingSpecialElement(pos, containingSpecialElement); - return pos; -} - -Node* isFirstPositionAfterTable(const VisiblePosition& visiblePosition) +Element* isFirstPositionAfterTable(const VisiblePosition& position) { - Position upstream(visiblePosition.deepEquivalent().upstream()); - if (upstream.deprecatedNode() && upstream.deprecatedNode()->renderer() && upstream.deprecatedNode()->renderer()->isTable() && upstream.atLastEditingPositionForNode()) - return upstream.deprecatedNode(); - - return 0; + Position upstream(position.deepEquivalent().upstream()); + auto* node = upstream.deprecatedNode(); + if (!node) + return nullptr; + auto* renderer = node->renderer(); + if (!renderer || !renderer->isTable() || !upstream.atLastEditingPositionForNode()) + return nullptr; + return &downcast<Element>(*node); } -Node* isLastPositionBeforeTable(const VisiblePosition& visiblePosition) +Element* isLastPositionBeforeTable(const VisiblePosition& position) { - Position downstream(visiblePosition.deepEquivalent().downstream()); - if (downstream.deprecatedNode() && downstream.deprecatedNode()->renderer() && downstream.deprecatedNode()->renderer()->isTable() && downstream.atFirstEditingPositionForNode()) - return downstream.deprecatedNode(); - - return 0; + Position downstream(position.deepEquivalent().downstream()); + auto* node = downstream.deprecatedNode(); + if (!node) + return nullptr; + auto* renderer = node->renderer(); + if (!renderer || !renderer->isTable() || !downstream.atFirstEditingPositionForNode()) + return nullptr; + return &downcast<Element>(*node); } // Returns the visible position at the beginning of a node -VisiblePosition visiblePositionBeforeNode(Node* node) +VisiblePosition visiblePositionBeforeNode(Node& node) { - ASSERT(node); - if (node->childNodeCount()) - return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); - ASSERT(node->parentNode()); - ASSERT(!node->parentNode()->isShadowRoot()); - return positionInParentBeforeNode(node); + if (node.hasChildNodes()) + return VisiblePosition(firstPositionInOrBeforeNode(&node), DOWNSTREAM); + ASSERT(node.parentNode()); + ASSERT(!node.parentNode()->isShadowRoot()); + return positionInParentBeforeNode(&node); } // Returns the visible position at the ending of a node -VisiblePosition visiblePositionAfterNode(Node* node) +VisiblePosition visiblePositionAfterNode(Node& node) { - ASSERT(node); - if (node->childNodeCount()) - return VisiblePosition(lastPositionInOrAfterNode(node), DOWNSTREAM); - ASSERT(node->parentNode()); - ASSERT(!node->parentNode()->isShadowRoot()); - return positionInParentAfterNode(node); + if (node.hasChildNodes()) + return VisiblePosition(lastPositionInOrAfterNode(&node), DOWNSTREAM); + ASSERT(node.parentNode()); + ASSERT(!node.parentNode()->isShadowRoot()); + return positionInParentAfterNode(&node); } -bool isListElement(Node *n) +bool isListHTMLElement(Node* node) { - return (n && (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag))); + return node && (is<HTMLUListElement>(*node) || is<HTMLOListElement>(*node) || is<HTMLDListElement>(*node)); } -bool isListItem(const Node *n) +bool isListItem(const Node* node) { - return n && (isListElement(n->parentNode()) || (n->renderer() && n->renderer()->isListItem())); + return node && (isListHTMLElement(node->parentNode()) || (node->renderer() && node->renderer()->isListItem())); } -Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName) +Element* enclosingElementWithTag(const Position& position, const QualifiedName& tagName) { - if (p.isNull()) - return 0; - - Node* root = highestEditableRoot(p); - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { - if (root && !n->hasEditableStyle()) + auto* root = highestEditableRoot(position); + for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { + if (root && !node->hasEditableStyle()) continue; - if (n->hasTagName(tagName)) - return n; - if (n == root) - return 0; + if (!is<Element>(*node)) + continue; + if (downcast<Element>(*node).hasTagName(tagName)) + return &downcast<Element>(*node); + if (node == root) + return nullptr; } - - return 0; + return nullptr; } -Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) +Node* enclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) { // FIXME: support CanSkipCrossEditingBoundary ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); - if (p.isNull()) - return 0; - - Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { + auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; + for (Node* n = position.deprecatedNode(); n; n = n->parentNode()) { // Don't return a non-editable node if the input position was editable, since // the callers from editing will no doubt want to perform editing inside the returned node. if (root && !n->hasEditableStyle()) @@ -575,17 +605,16 @@ Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), if (nodeIsOfType(n)) return n; if (n == root) - return 0; + return nullptr; } - - return 0; + return nullptr; } -Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin) +Node* highestEnclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin) { - Node* highest = 0; - Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; - for (Node* n = p.containerNode(); n && n != stayWithin; n = n->parentNode()) { + Node* highest = nullptr; + auto* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr; + for (Node* n = position.containerNode(); n && n != stayWithin; n = n->parentNode()) { if (root && !n->hasEditableStyle()) continue; if (nodeIsOfType(n)) @@ -593,7 +622,6 @@ Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const N if (n == root) break; } - return highest; } @@ -601,43 +629,40 @@ static bool hasARenderedDescendant(Node* node, Node* excludedNode) { for (Node* n = node->firstChild(); n;) { if (n == excludedNode) { - n = NodeTraversal::nextSkippingChildren(n, node); + n = NodeTraversal::nextSkippingChildren(*n, node); continue; } if (n->renderer()) return true; - n = NodeTraversal::next(n, node); + n = NodeTraversal::next(*n, node); } return false; } Node* highestNodeToRemoveInPruning(Node* node) { - Node* previousNode = 0; - Node* rootEditableElement = node ? node->rootEditableElement() : 0; + Node* previousNode = nullptr; + auto* rootEditableElement = node ? node->rootEditableElement() : nullptr; for (; node; node = node->parentNode()) { - if (RenderObject* renderer = node->renderer()) { + if (auto* renderer = node->renderer()) { if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node) return previousNode; } previousNode = node; } - return 0; + return nullptr; } -Node* enclosingTableCell(const Position& p) +Element* enclosingTableCell(const Position& position) { - return toElement(enclosingNodeOfType(p, isTableCell)); + return downcast<Element>(enclosingNodeOfType(position, isTableCell)); } Element* enclosingAnchorElement(const Position& p) { - if (p.isNull()) - return nullptr; - for (Node* node = p.deprecatedNode(); node; node = node->parentNode()) { - if (node->isElementNode() && node->isLink()) - return toElement(node); + if (is<Element>(*node) && node->isLink()) + return downcast<Element>(node); } return nullptr; } @@ -645,90 +670,90 @@ Element* enclosingAnchorElement(const Position& p) HTMLElement* enclosingList(Node* node) { if (!node) - return 0; + return nullptr; - Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); + auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); - for (ContainerNode* n = node->parentNode(); n; n = n->parentNode()) { - if (n->hasTagName(ulTag) || n->hasTagName(olTag)) - return toHTMLElement(n); - if (n == root) - return 0; + for (ContainerNode* ancestor = node->parentNode(); ancestor; ancestor = ancestor->parentNode()) { + if (is<HTMLUListElement>(*ancestor) || is<HTMLOListElement>(*ancestor)) + return downcast<HTMLElement>(ancestor); + if (ancestor == root) + return nullptr; } - return 0; + return nullptr; } Node* enclosingListChild(Node *node) { if (!node) - return 0; + return nullptr; + // Check for a list item element, or for a node whose parent is a list element. Such a node // will appear visually as a list item (but without a list marker) - Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); + auto* root = highestEditableRoot(firstPositionInOrBeforeNode(node)); - // FIXME: This function is inappropriately named if it starts with node instead of node->parentNode() + // FIXME: This function is inappropriately named since it starts with node instead of node->parentNode() for (Node* n = node; n && n->parentNode(); n = n->parentNode()) { - if (n->hasTagName(liTag) || (isListElement(n->parentNode()) && n != root)) + if (is<HTMLLIElement>(*n) || (isListHTMLElement(n->parentNode()) && n != root)) return n; if (n == root || isTableCell(n)) - return 0; + return nullptr; } - - return 0; + + return nullptr; } static HTMLElement* embeddedSublist(Node* listItem) { // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->firstChild(); n; n = n->nextSibling()) { - if (isListElement(n)) - return toHTMLElement(n); + if (isListHTMLElement(n)) + return downcast<HTMLElement>(n); } - - return 0; + return nullptr; } static Node* appendedSublist(Node* listItem) { // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->nextSibling(); n; n = n->nextSibling()) { - if (isListElement(n)) - return toHTMLElement(n); + if (isListHTMLElement(n)) + return downcast<HTMLElement>(n); if (isListItem(listItem)) - return 0; + return nullptr; } - return 0; + return nullptr; } -// FIXME: This method should not need to call isStartOfParagraph/isEndOfParagraph -Node* enclosingEmptyListItem(const VisiblePosition& visiblePos) +// FIXME: This function should not need to call isStartOfParagraph/isEndOfParagraph. +Node* enclosingEmptyListItem(const VisiblePosition& position) { // Check that position is on a line by itself inside a list item - Node* listChildNode = enclosingListChild(visiblePos.deepEquivalent().deprecatedNode()); - if (!listChildNode || !isStartOfParagraph(visiblePos) || !isEndOfParagraph(visiblePos)) - return 0; + auto* listChildNode = enclosingListChild(position.deepEquivalent().deprecatedNode()); + if (!listChildNode || !isStartOfParagraph(position) || !isEndOfParagraph(position)) + return nullptr; VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode)); VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode)); - if (firstInListChild != visiblePos || lastInListChild != visiblePos) - return 0; - + if (firstInListChild != position || lastInListChild != position) + return nullptr; + if (embeddedSublist(listChildNode) || appendedSublist(listChildNode)) - return 0; - + return nullptr; + return listChildNode; } HTMLElement* outermostEnclosingList(Node* node, Node* rootList) { - HTMLElement* list = enclosingList(node); + auto* list = enclosingList(node); if (!list) - return 0; + return nullptr; - while (HTMLElement* nextList = enclosingList(list)) { + while (auto* nextList = enclosingList(list)) { if (nextList == rootList) break; list = nextList; @@ -739,23 +764,17 @@ HTMLElement* outermostEnclosingList(Node* node, Node* rootList) bool canMergeLists(Element* firstList, Element* secondList) { - if (!firstList || !secondList || !firstList->isHTMLElement() || !secondList->isHTMLElement()) + if (!is<HTMLElement>(firstList) || !is<HTMLElement>(secondList)) return false; - return firstList->hasTagName(secondList->tagQName()) // make sure the list types match (ol vs. ul) - && firstList->hasEditableStyle() && secondList->hasEditableStyle() // both lists are editable - && firstList->rootEditableElement() == secondList->rootEditableElement() // don't cross editing boundaries - && isVisiblyAdjacent(positionInParentAfterNode(firstList), positionInParentBeforeNode(secondList)); - // Make sure there is no visible content between this li and the previous list -} + auto& first = downcast<HTMLElement>(*firstList); + auto& second = downcast<HTMLElement>(*secondList); -Node* highestAncestor(Node* node) -{ - ASSERT(node); - Node* parent = node; - while ((node = node->parentNode())) - parent = node; - return parent; + return first.localName() == second.localName() // make sure the list types match (ol vs. ul) + && first.hasEditableStyle() && second.hasEditableStyle() // both lists are editable + && first.rootEditableElement() == second.rootEditableElement() // don't cross editing boundaries + // Make sure there is no visible content between this li and the previous list. + && isVisiblyAdjacent(positionInParentAfterNode(&first), positionInParentBeforeNode(&second)); } static Node* previousNodeConsideringAtomicNodes(const Node* node) @@ -768,7 +787,7 @@ static Node* previousNodeConsideringAtomicNodes(const Node* node) } if (node->parentNode()) return node->parentNode(); - return 0; + return nullptr; } static Node* nextNodeConsideringAtomicNodes(const Node* node) @@ -782,48 +801,42 @@ static Node* nextNodeConsideringAtomicNodes(const Node* node) n = n->parentNode(); if (n) return n->nextSibling(); - return 0; + return nullptr; } Node* previousLeafNode(const Node* node) { - Node* n = previousNodeConsideringAtomicNodes(node); - while (n) { - if (isAtomicNode(n)) - return n; - n = previousNodeConsideringAtomicNodes(n); + while ((node = previousNodeConsideringAtomicNodes(node))) { + if (isAtomicNode(node)) + return const_cast<Node*>(node); } - return 0; + return nullptr; } Node* nextLeafNode(const Node* node) { - Node* n = nextNodeConsideringAtomicNodes(node); - while (n) { - if (isAtomicNode(n)) - return n; - n = nextNodeConsideringAtomicNodes(n); + while ((node = nextNodeConsideringAtomicNodes(node))) { + if (isAtomicNode(node)) + return const_cast<Node*>(node); } - return 0; + return nullptr; } -// FIXME: do not require renderer, so that this can be used within fragments, or rename to isRenderedTable() -bool isTableElement(Node* n) +// FIXME: Do not require renderer, so that this can be used within fragments. +bool isRenderedTable(const Node* node) { - if (!n || !n->isElementNode()) + if (!is<Element>(node)) return false; - - RenderObject* renderer = n->renderer(); - return (renderer && (renderer->style().display() == TABLE || renderer->style().display() == INLINE_TABLE)); + auto* renderer = downcast<Element>(*node).renderer(); + return renderer && renderer->isTable(); } bool isTableCell(const Node* node) { - RenderObject* r = node->renderer(); - if (!r) + auto* renderer = node->renderer(); + if (!renderer) return node->hasTagName(tdTag) || node->hasTagName(thTag); - - return r->isTableCell(); + return renderer->isTableCell(); } bool isEmptyTableCell(const Node* node) @@ -841,17 +854,17 @@ bool isEmptyTableCell(const Node* node) // Make sure the rendered node is a table cell or <br>. // If it's a <br>, then the parent node has to be a table cell. - RenderObject* renderer = node->renderer(); + auto* renderer = node->renderer(); if (renderer->isBR()) { renderer = renderer->parent(); if (!renderer) return false; } - if (!renderer->isTableCell()) + if (!is<RenderTableCell>(*renderer)) return false; // Check that the table cell contains no child renderers except for perhaps a single <br>. - RenderObject* childRenderer = toRenderElement(renderer)->firstChild(); + auto* childRenderer = downcast<RenderTableCell>(*renderer).firstChild(); if (!childRenderer) return true; if (!childRenderer->isBR()) @@ -859,206 +872,159 @@ bool isEmptyTableCell(const Node* node) return !childRenderer->nextSibling(); } -PassRefPtr<HTMLElement> createDefaultParagraphElement(Document& document) +Ref<HTMLElement> createDefaultParagraphElement(Document& document) { switch (document.frame()->editor().defaultParagraphSeparator()) { case EditorParagraphSeparatorIsDiv: return HTMLDivElement::create(document); case EditorParagraphSeparatorIsP: - return HTMLParagraphElement::create(document); + break; } - - ASSERT_NOT_REACHED(); - return 0; + return HTMLParagraphElement::create(document); } -PassRefPtr<HTMLElement> createBreakElement(Document& document) -{ - return HTMLBRElement::create(document); -} - -PassRefPtr<HTMLElement> createOrderedListElement(Document& document) -{ - return HTMLOListElement::create(document); -} - -PassRefPtr<HTMLElement> createUnorderedListElement(Document& document) -{ - return HTMLUListElement::create(document); -} - -PassRefPtr<HTMLElement> createListItemElement(Document& document) -{ - return HTMLLIElement::create(document); -} - -PassRefPtr<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) +Ref<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name) { return HTMLElementFactory::createElement(name, document); } -PassRefPtr<HTMLElement> createHTMLElement(Document& document, const AtomicString& tagName) +Ref<HTMLElement> createHTMLElement(Document& document, const AtomicString& tagName) { return createHTMLElement(document, QualifiedName(nullAtom, tagName, xhtmlNamespaceURI)); } -bool isTabSpanNode(const Node *node) +bool isTabSpanNode(const Node* node) { - return node && node->hasTagName(spanTag) && node->isElementNode() && static_cast<const Element *>(node)->getAttribute(classAttr) == AppleTabSpanClass; + return is<HTMLSpanElement>(node) && downcast<HTMLSpanElement>(*node).attributeWithoutSynchronization(classAttr) == AppleTabSpanClass; } -bool isTabSpanTextNode(const Node *node) +bool isTabSpanTextNode(const Node* node) { - return node && node->isTextNode() && node->parentNode() && isTabSpanNode(node->parentNode()); + return is<Text>(node) && isTabSpanNode(node->parentNode()); } -Node* tabSpanNode(const Node *node) -{ - return isTabSpanTextNode(node) ? node->parentNode() : 0; -} - -Position positionOutsideTabSpan(const Position& pos) +HTMLSpanElement* tabSpanNode(const Node* node) { - Node* node = pos.containerNode(); - if (isTabSpanTextNode(node)) - node = tabSpanNode(node); - else if (!isTabSpanNode(node)) - return pos; - - if (node && VisiblePosition(pos) == lastPositionInNode(node)) - return positionInParentAfterNode(node); - - return positionInParentBeforeNode(node); + return isTabSpanTextNode(node) ? downcast<HTMLSpanElement>(node->parentNode()) : nullptr; } -PassRefPtr<Element> createTabSpanElement(Document& document, PassRefPtr<Node> prpTabTextNode) +static Ref<Element> createTabSpanElement(Document& document, Text& tabTextNode) { - RefPtr<Node> tabTextNode = prpTabTextNode; + auto spanElement = HTMLSpanElement::create(document); - // Make the span to hold the tab. - RefPtr<Element> spanElement = document.createElement(spanTag, false); - spanElement->setAttribute(classAttr, AppleTabSpanClass); + spanElement->setAttributeWithoutSynchronization(classAttr, AppleTabSpanClass); spanElement->setAttribute(styleAttr, "white-space:pre"); - // Add tab text to that span. - if (!tabTextNode) - tabTextNode = document.createEditingTextNode("\t"); + spanElement->appendChild(tabTextNode); - spanElement->appendChild(tabTextNode.release(), ASSERT_NO_EXCEPTION); - - return spanElement.release(); + return WTFMove(spanElement); } -PassRefPtr<Element> createTabSpanElement(Document& document, const String& tabText) +Ref<Element> createTabSpanElement(Document& document, const String& tabText) { return createTabSpanElement(document, document.createTextNode(tabText)); } -PassRefPtr<Element> createTabSpanElement(Document& document) +Ref<Element> createTabSpanElement(Document& document) { - return createTabSpanElement(document, PassRefPtr<Node>()); + return createTabSpanElement(document, document.createEditingTextNode(ASCIILiteral("\t"))); } -bool isNodeRendered(const Node* node) +bool isNodeRendered(const Node& node) { - if (!node) - return false; - - RenderObject* renderer = node->renderer(); - if (!renderer) - return false; - - return renderer->style().visibility() == VISIBLE; + auto* renderer = node.renderer(); + return renderer && renderer->style().visibility() == VISIBLE; } -unsigned numEnclosingMailBlockquotes(const Position& p) +unsigned numEnclosingMailBlockquotes(const Position& position) { - unsigned num = 0; - for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) - if (isMailBlockquote(n)) - num++; - - return num; + unsigned count = 0; + for (Node* node = position.deprecatedNode(); node; node = node->parentNode()) { + if (isMailBlockquote(node)) + ++count; + } + return count; } -void updatePositionForNodeRemoval(Position& position, Node* node) +void updatePositionForNodeRemoval(Position& position, Node& node) { if (position.isNull()) return; switch (position.anchorType()) { case Position::PositionIsBeforeChildren: - if (position.containerNode() == node) - position = positionInParentBeforeNode(node); + if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsAfterChildren: - if (position.containerNode() == node) - position = positionInParentAfterNode(node); + if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsOffsetInAnchor: - if (position.containerNode() == node->parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node->nodeIndex()) + if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.computeNodeIndex()) position.moveToOffset(position.offsetInContainerNode() - 1); - else if (node->containsIncludingShadowDOM(position.containerNode())) - position = positionInParentBeforeNode(node); + else if (node.containsIncludingShadowDOM(position.containerNode())) + position = positionInParentBeforeNode(&node); break; case Position::PositionIsAfterAnchor: - if (node->containsIncludingShadowDOM(position.anchorNode())) - position = positionInParentAfterNode(node); + if (node.containsIncludingShadowDOM(position.anchorNode())) + position = positionInParentAfterNode(&node); break; case Position::PositionIsBeforeAnchor: - if (node->containsIncludingShadowDOM(position.anchorNode())) - position = positionInParentBeforeNode(node); + if (node.containsIncludingShadowDOM(position.anchorNode())) + position = positionInParentBeforeNode(&node); break; } } -bool isMailBlockquote(const Node *node) +bool isMailBlockquote(const Node* node) { - if (!node || !node->hasTagName(blockquoteTag)) + ASSERT(node); + if (!node->hasTagName(blockquoteTag)) return false; - - return static_cast<const Element *>(node)->getAttribute("type") == "cite"; + return downcast<HTMLElement>(*node).attributeWithoutSynchronization(typeAttr) == "cite"; } -int caretMinOffset(const Node* n) +int caretMinOffset(const Node& node) { - RenderObject* r = n->renderer(); - ASSERT(!n->isCharacterDataNode() || !r || r->isText()); // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now. - return r ? r->caretMinOffset() : 0; + auto* renderer = node.renderer(); + ASSERT(!node.isCharacterDataNode() || !renderer || renderer->isText()); + return renderer ? renderer->caretMinOffset() : 0; } // If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise // return the number of children for container nodes and the length for unrendered text nodes. -int caretMaxOffset(const Node* n) +int caretMaxOffset(const Node& node) { // For rendered text nodes, return the last position that a caret could occupy. - if (n->isTextNode() && n->renderer()) - return n->renderer()->caretMaxOffset(); - // For containers return the number of children. For others do the same as above. - return lastOffsetForEditing(n); + if (is<Text>(node)) { + if (auto* renderer = downcast<Text>(node).renderer()) + return renderer->caretMaxOffset(); + } + return lastOffsetForEditing(node); } -bool lineBreakExistsAtVisiblePosition(const VisiblePosition& visiblePosition) +bool lineBreakExistsAtVisiblePosition(const VisiblePosition& position) { - return lineBreakExistsAtPosition(visiblePosition.deepEquivalent().downstream()); + return lineBreakExistsAtPosition(position.deepEquivalent().downstream()); } bool lineBreakExistsAtPosition(const Position& position) { if (position.isNull()) return false; - + if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode()) return true; - + if (!position.anchorNode()->renderer()) return false; - - if (!position.anchorNode()->isTextNode() || !position.anchorNode()->renderer()->style().preserveNewline()) + + if (!is<Text>(*position.anchorNode()) || !position.anchorNode()->renderer()->style().preserveNewline()) return false; - Text* textNode = toText(position.anchorNode()); + Text& textNode = downcast<Text>(*position.anchorNode()); unsigned offset = position.offsetInContainerNode(); - return offset < textNode->length() && textNode->data()[offset] == '\n'; + return offset < textNode.length() && textNode.data()[offset] == '\n'; } // Modifies selections that have an end point at the edge of a table @@ -1074,17 +1040,19 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection& original // if the start of the selection is inside that table, then the last paragraph // that we'll want modify is the last one inside the table, not the table itself // (a table is itself a paragraph). - if (Node* table = isFirstPositionAfterTable(endOfSelection)) - if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) + if (auto* table = isFirstPositionAfterTable(endOfSelection)) { + if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary)); + } // If the start of the selection to modify is just before a table, // and if the end of the selection is inside that table, then the first paragraph // we'll want to modify is the first one inside the table, not the paragraph // containing the table itself. - if (Node* table = isLastPositionBeforeTable(startOfSelection)) - if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) + if (auto* table = isLastPositionBeforeTable(startOfSelection)) { + if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table)) newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection); + } return newSelection; } @@ -1100,74 +1068,74 @@ int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<Conta if (visiblePosition.isNull()) return 0; - Position p(visiblePosition.deepEquivalent()); - Document& document = p.anchorNode()->document(); - ShadowRoot* shadowRoot = p.anchorNode()->containingShadowRoot(); - - if (shadowRoot) - scope = shadowRoot; - else - scope = document.documentElement(); + auto position = visiblePosition.deepEquivalent(); + auto& document = *position.document(); + + auto* editableRoot = highestEditableRoot(position, AXObjectCache::accessibilityEnabled() ? HasEditableAXRole : ContentIsEditable); + if (editableRoot && !document.inDesignMode()) + scope = editableRoot; + else { + if (position.containerNode()->isInShadowTree()) + scope = position.containerNode()->containingShadowRoot(); + else + scope = &document; + } - RefPtr<Range> range = Range::create(document, firstPositionInNode(scope.get()), p.parentAnchoredEquivalent()); - return TextIterator::rangeLength(range.get(), true); + auto range = Range::create(document, firstPositionInNode(scope.get()), position.parentAnchoredEquivalent()); + return TextIterator::rangeLength(range.ptr(), true); } -// FIXME: Merge these two functions. -int indexForVisiblePosition(Node* node, const VisiblePosition& visiblePosition, bool forSelectionPreservation) +// FIXME: Merge this function with the one above. +int indexForVisiblePosition(Node& node, const VisiblePosition& visiblePosition, bool forSelectionPreservation) { - ASSERT(node); - RefPtr<Range> range = Range::create(node->document(), firstPositionInNode(node), visiblePosition.deepEquivalent().parentAnchoredEquivalent()); - return TextIterator::rangeLength(range.get(), forSelectionPreservation); + auto range = Range::create(node.document(), firstPositionInNode(&node), visiblePosition.deepEquivalent().parentAnchoredEquivalent()); + return TextIterator::rangeLength(range.ptr(), forSelectionPreservation); } VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope) { - RefPtr<Range> range = TextIterator::rangeFromLocationAndLength(scope, index, 0, true); + auto range = TextIterator::rangeFromLocationAndLength(scope, index, 0, true); // Check for an invalid index. Certain editing operations invalidate indices because // of problems with TextIteratorEmitsCharactersBetweenAllVisiblePositions. if (!range) - return VisiblePosition(); - return VisiblePosition(range->startPosition()); + return { }; + return { range->startPosition() }; } -VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node* node, int index) +VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node& node, int index) { - ASSERT(node); if (index <= 0) - return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); + return { firstPositionInOrBeforeNode(&node), DOWNSTREAM }; - RefPtr<Range> range = Range::create(node->document()); - range->selectNodeContents(node, IGNORE_EXCEPTION); - CharacterIterator it(range.get()); + RefPtr<Range> range = Range::create(node.document()); + range->selectNodeContents(node); + CharacterIterator it(*range); it.advance(index - 1); - - return VisiblePosition(Position(it.range()->endContainer(), it.range()->endOffset(), Position::PositionIsOffsetInAnchor), UPSTREAM); + return { it.atEnd() ? range->endPosition() : it.range()->endPosition(), UPSTREAM }; } // Determines whether two positions are visibly next to each other (first then second) // while ignoring whitespaces and unrendered nodes -bool isVisiblyAdjacent(const Position& first, const Position& second) +static bool isVisiblyAdjacent(const Position& first, const Position& second) { return VisiblePosition(first) == VisiblePosition(second.upstream()); } // Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range. // Call this function to determine whether a node is visibly fit inside selectedRange -bool isNodeVisiblyContainedWithin(Node* node, const Range* selectedRange) +bool isNodeVisiblyContainedWithin(Node& node, const Range& range) { - ASSERT(node); - ASSERT(selectedRange); - // If the node is inside the range, then it surely is contained within - if (selectedRange->compareNode(node, IGNORE_EXCEPTION) == Range::NODE_INSIDE) + // If the node is inside the range, then it surely is contained within. + auto comparisonResult = range.compareNode(node); + if (!comparisonResult.hasException() && comparisonResult.releaseReturnValue() == Range::NODE_INSIDE) return true; - bool startIsVisuallySame = visiblePositionBeforeNode(node) == selectedRange->startPosition(); - if (startIsVisuallySame && comparePositions(positionInParentAfterNode(node), selectedRange->endPosition()) < 0) + bool startIsVisuallySame = visiblePositionBeforeNode(node) == range.startPosition(); + if (startIsVisuallySame && comparePositions(positionInParentAfterNode(&node), range.endPosition()) < 0) return true; - bool endIsVisuallySame = visiblePositionAfterNode(node) == selectedRange->endPosition(); - if (endIsVisuallySame && comparePositions(selectedRange->startPosition(), positionInParentBeforeNode(node)) < 0) + bool endIsVisuallySame = visiblePositionAfterNode(node) == range.endPosition(); + if (endIsVisuallySame && comparePositions(range.startPosition(), positionInParentBeforeNode(&node)) < 0) return true; return startIsVisuallySame && endIsVisuallySame; @@ -1181,36 +1149,28 @@ bool isRenderedAsNonInlineTableImageOrHR(const Node* node) return renderer && ((renderer->isTable() && !renderer->isInline()) || (renderer->isImage() && !renderer->isInline()) || renderer->isHR()); } -bool areIdenticalElements(const Node* first, const Node* second) +bool areIdenticalElements(const Node& first, const Node& second) { - if (!first->isElementNode() || !second->isElementNode()) - return false; - - const Element* firstElement = toElement(first); - const Element* secondElement = toElement(second); - if (!firstElement->hasTagName(secondElement->tagQName())) + if (!is<Element>(first) || !is<Element>(second)) return false; - - return firstElement->hasEquivalentAttributes(secondElement); + auto& firstElement = downcast<Element>(first); + auto& secondElement = downcast<Element>(second); + return firstElement.hasTagName(secondElement.tagQName()) && firstElement.hasEquivalentAttributes(&secondElement); } bool isNonTableCellHTMLBlockElement(const Node* node) { - if (!node->isElementNode()) - return false; - - const Element* element = toElement(node); - return element->hasTagName(listingTag) - || element->hasTagName(olTag) - || element->hasTagName(preTag) - || isHTMLTableElement(element) - || element->hasTagName(ulTag) - || element->hasTagName(xmpTag) - || element->hasTagName(h1Tag) - || element->hasTagName(h2Tag) - || element->hasTagName(h3Tag) - || element->hasTagName(h4Tag) - || element->hasTagName(h5Tag); + return node->hasTagName(listingTag) + || node->hasTagName(olTag) + || node->hasTagName(preTag) + || is<HTMLTableElement>(*node) + || node->hasTagName(ulTag) + || node->hasTagName(xmpTag) + || node->hasTagName(h1Tag) + || node->hasTagName(h2Tag) + || node->hasTagName(h3Tag) + || node->hasTagName(h4Tag) + || node->hasTagName(h5Tag); } Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection) @@ -1220,9 +1180,9 @@ Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selec // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up // with a spurious "mixed" style. - VisiblePosition visiblePosition = selection.start(); + auto visiblePosition = selection.visibleStart(); if (visiblePosition.isNull()) - return Position(); + return { }; // if the selection is a caret, just return the position, since the style // behind us is relevant @@ -1239,25 +1199,89 @@ Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selec } // FIXME: Should this be deprecated like deprecatedEnclosingBlockFlowElement is? -bool isBlockFlowElement(const Node* node) +bool isBlockFlowElement(const Node& node) { - if (!node->isElementNode()) + if (!node.isElementNode()) return false; - RenderObject* renderer = node->renderer(); + auto* renderer = downcast<Element>(node).renderer(); return renderer && renderer->isRenderBlockFlow(); } Element* deprecatedEnclosingBlockFlowElement(Node* node) { if (!node) - return 0; - if (isBlockFlowElement(node)) - return toElement(node); + return nullptr; + if (isBlockFlowElement(*node)) + return downcast<Element>(node); while ((node = node->parentNode())) { - if (isBlockFlowElement(node) || node->hasTagName(bodyTag)) - return toElement(node); + if (isBlockFlowElement(*node) || is<HTMLBodyElement>(*node)) + return downcast<Element>(node); } - return 0; + return nullptr; +} + +static inline bool caretRendersInsideNode(Node& node) +{ + return !isRenderedTable(&node) && !editingIgnoresContent(node); +} + +RenderBlock* rendererForCaretPainting(Node* node) +{ + if (!node) + return nullptr; + + auto* renderer = node->renderer(); + if (!renderer) + return nullptr; + + // If caretNode is a block and caret is inside it, then caret should be painted by that block. + bool paintedByBlock = is<RenderBlockFlow>(*renderer) && caretRendersInsideNode(*node); + return paintedByBlock ? downcast<RenderBlock>(renderer) : renderer->containingBlock(); +} + +LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition& caretPosition, RenderBlock*& caretPainter) +{ + if (caretPosition.isNull()) + return LayoutRect(); + + ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer()); + + // First compute a rect local to the renderer at the selection start. + RenderObject* renderer; + LayoutRect localRect = caretPosition.localCaretRect(renderer); + + return localCaretRectInRendererForRect(localRect, caretPosition.deepEquivalent().deprecatedNode(), renderer, caretPainter); +} + +LayoutRect localCaretRectInRendererForRect(LayoutRect& localRect, Node* node, RenderObject* renderer, RenderBlock*& caretPainter) +{ + // Get the renderer that will be responsible for painting the caret + // (which is either the renderer we just found, or one of its containers). + caretPainter = rendererForCaretPainting(node); + + // Compute an offset between the renderer and the caretPainter. + while (renderer != caretPainter) { + RenderElement* containerObject = renderer->container(); + if (!containerObject) + return LayoutRect(); + localRect.move(renderer->offsetFromContainer(*containerObject, localRect.location())); + renderer = containerObject; + } + + return localRect; +} + +IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect& rect, bool* insideFixed) +{ + if (insideFixed) + *insideFixed = false; + + if (!rendererForCaretPainting || rect.isEmpty()) + return IntRect(); + + LayoutRect localRect(rect); + rendererForCaretPainting->flipForWritingMode(localRect); + return rendererForCaretPainting->localToAbsoluteQuad(FloatRect(localRect), UseTransforms, insideFixed).enclosingBoundingBox(); } } // namespace WebCore |