2 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "htmlediting.h"
29 #include "CharacterNames.h"
31 #include "EditingText.h"
32 #include "HTMLElement.h"
33 #include "HTMLInterchange.h"
34 #include "HTMLNames.h"
35 #include "PositionIterator.h"
36 #include "RenderObject.h"
37 #include "RegularExpression.h"
39 #include "Selection.h"
41 #include "TextIterator.h"
42 #include "VisiblePosition.h"
43 #include "visible_units.h"
49 using namespace HTMLNames
;
51 // Atomic means that the node has no children, or has children which are ignored for the
52 // purposes of editing.
53 bool isAtomicNode(const Node
*node
)
55 return node
&& (!node
->hasChildNodes() || editingIgnoresContent(node
));
58 // Returns true for nodes that either have no content, or have content that is ignored (skipped
59 // over) while editing. There are no VisiblePositions inside these nodes.
60 bool editingIgnoresContent(const Node
* node
)
62 return !canHaveChildrenForEditing(node
) && !node
->isTextNode();
65 bool canHaveChildrenForEditing(const Node
* node
)
67 return !node
->hasTagName(hrTag
) &&
68 !node
->hasTagName(brTag
) &&
69 !node
->hasTagName(imgTag
) &&
70 !node
->hasTagName(buttonTag
) &&
71 !node
->hasTagName(inputTag
) &&
72 !node
->hasTagName(textareaTag
) &&
73 !node
->hasTagName(objectTag
) &&
74 !node
->hasTagName(iframeTag
) &&
75 !node
->hasTagName(embedTag
) &&
76 !node
->hasTagName(appletTag
) &&
77 !node
->hasTagName(selectTag
) &&
81 // Compare two positions, taking into account the possibility that one or both
82 // could be inside a shadow tree. Only works for non-null values.
83 int comparePositions(const Position
& a
, const Position
& b
)
85 Node
* nodeA
= a
.node();
87 Node
* nodeB
= b
.node();
89 int offsetA
= a
.offset();
90 int offsetB
= b
.offset();
92 Node
* shadowAncestorA
= nodeA
->shadowAncestorNode();
93 if (shadowAncestorA
== nodeA
)
95 Node
* shadowAncestorB
= nodeB
->shadowAncestorNode();
96 if (shadowAncestorB
== nodeB
)
100 if (shadowAncestorA
!= shadowAncestorB
) {
101 if (shadowAncestorA
) {
102 nodeA
= shadowAncestorA
;
106 if (shadowAncestorB
) {
107 nodeB
= shadowAncestorB
;
113 int result
= Range::compareBoundaryPoints(nodeA
, offsetA
, nodeB
, offsetB
);
114 return result
? result
: bias
;
117 Node
* highestEditableRoot(const Position
& position
)
119 Node
* node
= position
.node();
123 Node
* highestRoot
= editableRootForPosition(position
);
129 if (node
->isContentEditable())
131 if (node
->hasTagName(bodyTag
))
133 node
= node
->parentNode();
139 Node
* lowestEditableAncestor(Node
* node
)
144 Node
*lowestRoot
= 0;
146 if (node
->isContentEditable())
147 return node
->rootEditableElement();
148 if (node
->hasTagName(bodyTag
))
150 node
= node
->parentNode();
156 bool isEditablePosition(const Position
& p
)
158 Node
* node
= p
.node();
162 if (node
->renderer() && node
->renderer()->isTable())
163 node
= node
->parentNode();
165 return node
->isContentEditable();
168 bool isRichlyEditablePosition(const Position
& p
)
170 Node
* node
= p
.node();
174 if (node
->renderer() && node
->renderer()->isTable())
175 node
= node
->parentNode();
177 return node
->isContentRichlyEditable();
180 Element
* editableRootForPosition(const Position
& p
)
182 Node
* node
= p
.node();
186 if (node
->renderer() && node
->renderer()->isTable())
187 node
= node
->parentNode();
189 return node
->rootEditableElement();
192 bool isContentEditable(const Node
* node
)
194 return node
->isContentEditable();
197 Position
nextCandidate(const Position
& position
)
199 PositionIterator p
= position
;
208 Position
nextVisuallyDistinctCandidate(const Position
& position
)
210 Position p
= position
;
211 Position downstreamStart
= p
.downstream();
213 p
= p
.next(UsingComposedCharacters
);
214 if (p
.isCandidate() && p
.downstream() != downstreamStart
)
220 Position
previousCandidate(const Position
& position
)
222 PositionIterator p
= position
;
223 while (!p
.atStart()) {
231 Position
previousVisuallyDistinctCandidate(const Position
& position
)
233 Position p
= position
;
234 Position downstreamStart
= p
.downstream();
235 while (!p
.atStart()) {
236 p
= p
.previous(UsingComposedCharacters
);
237 if (p
.isCandidate() && p
.downstream() != downstreamStart
)
243 VisiblePosition
firstEditablePositionAfterPositionInRoot(const Position
& position
, Node
* highestRoot
)
245 // position falls before highestRoot.
246 if (comparePositions(position
, Position(highestRoot
, 0)) == -1 && highestRoot
->isContentEditable())
247 return VisiblePosition(Position(highestRoot
, 0));
249 Position p
= position
;
251 if (Node
* shadowAncestor
= p
.node()->shadowAncestorNode())
252 if (shadowAncestor
!= p
.node())
253 p
= Position(shadowAncestor
, maxDeepOffset(shadowAncestor
));
255 while (p
.node() && !isEditablePosition(p
) && p
.node()->isDescendantOf(highestRoot
))
256 p
= isAtomicNode(p
.node()) ? positionAfterNode(p
.node()) : nextVisuallyDistinctCandidate(p
);
258 if (p
.node() && !p
.node()->isDescendantOf(highestRoot
))
259 return VisiblePosition();
261 return VisiblePosition(p
);
264 VisiblePosition
lastEditablePositionBeforePositionInRoot(const Position
& position
, Node
* highestRoot
)
266 // When position falls after highestRoot, the result is easy to compute.
267 if (comparePositions(position
, Position(highestRoot
, maxDeepOffset(highestRoot
))) == 1)
268 return VisiblePosition(Position(highestRoot
, maxDeepOffset(highestRoot
)));
270 Position p
= position
;
272 if (Node
* shadowAncestor
= p
.node()->shadowAncestorNode())
273 if (shadowAncestor
!= p
.node())
274 p
= Position(shadowAncestor
, 0);
276 while (p
.node() && !isEditablePosition(p
) && p
.node()->isDescendantOf(highestRoot
))
277 p
= isAtomicNode(p
.node()) ? positionBeforeNode(p
.node()) : previousVisuallyDistinctCandidate(p
);
279 if (p
.node() && !p
.node()->isDescendantOf(highestRoot
))
280 return VisiblePosition();
282 return VisiblePosition(p
);
285 // Whether or not content before and after this node will collapse onto the same line as it.
286 bool isBlock(const Node
* node
)
288 return node
&& node
->renderer() && !node
->renderer()->isInline();
291 // FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used.
292 // FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the
293 // block that contains the table and not the table, and this function should be the only one responsible for
294 // knowing about these kinds of special cases.
295 Node
* enclosingBlock(Node
* node
)
297 return enclosingNodeOfType(Position(node
, 0), &isBlock
);
300 Position
rangeCompliantEquivalent(const Position
& pos
)
305 Node
*node
= pos
.node();
307 if (pos
.offset() <= 0) {
308 if (node
->parentNode() && (editingIgnoresContent(node
) || isTableElement(node
)))
309 return positionBeforeNode(node
);
310 return Position(node
, 0);
313 if (node
->offsetInCharacters())
314 return Position(node
, min(node
->maxCharacterOffset(), pos
.offset()));
316 int maxCompliantOffset
= node
->childNodeCount();
317 if (pos
.offset() > maxCompliantOffset
) {
318 if (node
->parentNode())
319 return positionAfterNode(node
);
321 // there is no other option at this point than to
322 // use the highest allowed position in the node
323 return Position(node
, maxCompliantOffset
);
326 // Editing should never generate positions like this.
327 if ((pos
.offset() < maxCompliantOffset
) && editingIgnoresContent(node
)) {
328 ASSERT_NOT_REACHED();
329 return node
->parentNode() ? positionBeforeNode(node
) : Position(node
, 0);
332 if (pos
.offset() == maxCompliantOffset
&& (editingIgnoresContent(node
) || isTableElement(node
)))
333 return positionAfterNode(node
);
335 return Position(pos
);
338 Position
rangeCompliantEquivalent(const VisiblePosition
& vpos
)
340 return rangeCompliantEquivalent(vpos
.deepEquivalent());
343 // This method is used to create positions in the DOM. It returns the maximum valid offset
344 // in a node. It returns 1 for some elements even though they do not have children, which
345 // creates technically invalid DOM Positions. Be sure to call rangeCompliantEquivalent
346 // on a Position before using it to create a DOM Range, or an exception will be thrown.
347 int maxDeepOffset(const Node
*node
)
352 if (node
->offsetInCharacters())
353 return node
->maxCharacterOffset();
355 if (node
->hasChildNodes())
356 return node
->childNodeCount();
358 // NOTE: This should preempt the childNodeCount for, e.g., select nodes
359 if (editingIgnoresContent(node
))
365 String
stringWithRebalancedWhitespace(const String
& string
, bool startIsStartOfParagraph
, bool endIsEndOfParagraph
)
367 static String
twoSpaces(" ");
368 static String
nbsp("\xa0");
369 static String
pattern(" \xa0");
371 String rebalancedString
= string
;
373 rebalancedString
.replace(noBreakSpace
, ' ');
374 rebalancedString
.replace('\n', ' ');
375 rebalancedString
.replace('\t', ' ');
377 rebalancedString
.replace(twoSpaces
, pattern
);
379 if (startIsStartOfParagraph
&& rebalancedString
[0] == ' ')
380 rebalancedString
.replace(0, 1, nbsp
);
381 int end
= rebalancedString
.length() - 1;
382 if (endIsEndOfParagraph
&& rebalancedString
[end
] == ' ')
383 rebalancedString
.replace(end
, 1, nbsp
);
385 return rebalancedString
;
388 bool isTableStructureNode(const Node
*node
)
390 RenderObject
*r
= node
->renderer();
391 return (r
&& (r
->isTableCell() || r
->isTableRow() || r
->isTableSection() || r
->isTableCol()));
394 const String
& nonBreakingSpaceString()
396 static String nonBreakingSpaceString
= String(&noBreakSpace
, 1);
397 return nonBreakingSpaceString
;
400 // FIXME: need to dump this
401 bool isSpecialElement(const Node
*n
)
406 if (!n
->isHTMLElement())
412 RenderObject
*renderer
= n
->renderer();
416 if (renderer
->style()->display() == TABLE
|| renderer
->style()->display() == INLINE_TABLE
)
419 if (renderer
->style()->isFloating())
422 if (renderer
->style()->position() != StaticPosition
)
428 // Checks if a string is a valid tag for the FormatBlockCommand function of execCommand. Expects lower case strings.
429 bool validBlockTag(const String
& blockTag
)
431 if (blockTag
== "address" ||
432 blockTag
== "blockquote" ||
449 static Node
* firstInSpecialElement(const Position
& pos
)
451 Node
* rootEditableElement
= pos
.node()->rootEditableElement();
452 for (Node
* n
= pos
.node(); n
&& n
->rootEditableElement() == rootEditableElement
; n
= n
->parentNode())
453 if (isSpecialElement(n
)) {
454 VisiblePosition vPos
= VisiblePosition(pos
, DOWNSTREAM
);
455 VisiblePosition firstInElement
= VisiblePosition(n
, 0, DOWNSTREAM
);
456 if (isTableElement(n
) && vPos
== firstInElement
.next())
458 if (vPos
== firstInElement
)
464 static Node
* lastInSpecialElement(const Position
& pos
)
466 Node
* rootEditableElement
= pos
.node()->rootEditableElement();
467 for (Node
* n
= pos
.node(); n
&& n
->rootEditableElement() == rootEditableElement
; n
= n
->parentNode())
468 if (isSpecialElement(n
)) {
469 VisiblePosition vPos
= VisiblePosition(pos
, DOWNSTREAM
);
470 VisiblePosition lastInElement
= VisiblePosition(n
, n
->childNodeCount(), DOWNSTREAM
);
471 if (isTableElement(n
) && vPos
== lastInElement
.previous())
473 if (vPos
== lastInElement
)
479 bool isFirstVisiblePositionInSpecialElement(const Position
& pos
)
481 return firstInSpecialElement(pos
);
484 Position
positionBeforeContainingSpecialElement(const Position
& pos
, Node
** containingSpecialElement
)
486 Node
* n
= firstInSpecialElement(pos
);
489 Position result
= positionBeforeNode(n
);
490 if (result
.isNull() || result
.node()->rootEditableElement() != pos
.node()->rootEditableElement())
492 if (containingSpecialElement
)
493 *containingSpecialElement
= n
;
497 bool isLastVisiblePositionInSpecialElement(const Position
& pos
)
499 return lastInSpecialElement(pos
);
502 Position
positionAfterContainingSpecialElement(const Position
& pos
, Node
**containingSpecialElement
)
504 Node
* n
= lastInSpecialElement(pos
);
507 Position result
= positionAfterNode(n
);
508 if (result
.isNull() || result
.node()->rootEditableElement() != pos
.node()->rootEditableElement())
510 if (containingSpecialElement
)
511 *containingSpecialElement
= n
;
515 Position
positionOutsideContainingSpecialElement(const Position
&pos
, Node
**containingSpecialElement
)
517 if (isFirstVisiblePositionInSpecialElement(pos
))
518 return positionBeforeContainingSpecialElement(pos
, containingSpecialElement
);
519 if (isLastVisiblePositionInSpecialElement(pos
))
520 return positionAfterContainingSpecialElement(pos
, containingSpecialElement
);
524 Node
* isFirstPositionAfterTable(const VisiblePosition
& visiblePosition
)
526 Position
upstream(visiblePosition
.deepEquivalent().upstream());
527 if (upstream
.node() && upstream
.node()->renderer() && upstream
.node()->renderer()->isTable() && upstream
.offset() == maxDeepOffset(upstream
.node()))
528 return upstream
.node();
533 Node
* isLastPositionBeforeTable(const VisiblePosition
& visiblePosition
)
535 Position
downstream(visiblePosition
.deepEquivalent().downstream());
536 if (downstream
.node() && downstream
.node()->renderer() && downstream
.node()->renderer()->isTable() && downstream
.offset() == 0)
537 return downstream
.node();
542 Position
positionBeforeNode(const Node
*node
)
544 return Position(node
->parentNode(), node
->nodeIndex());
547 Position
positionAfterNode(const Node
*node
)
549 return Position(node
->parentNode(), node
->nodeIndex() + 1);
552 bool isListElement(Node
*n
)
554 return (n
&& (n
->hasTagName(ulTag
) || n
->hasTagName(olTag
) || n
->hasTagName(dlTag
)));
557 Node
* enclosingNodeWithTag(const Position
& p
, const QualifiedName
& tagName
)
562 Node
* root
= highestEditableRoot(p
);
563 for (Node
* n
= p
.node(); n
; n
= n
->parentNode()) {
564 if (root
&& !isContentEditable(n
))
566 if (n
->hasTagName(tagName
))
575 Node
* enclosingNodeOfType(const Position
& p
, bool (*nodeIsOfType
)(const Node
*), bool onlyReturnEditableNodes
)
580 Node
* root
= highestEditableRoot(p
);
581 for (Node
* n
= p
.node(); n
; n
= n
->parentNode()) {
582 // Don't return a non-editable node if the input position was editable, since
583 // the callers from editing will no doubt want to perform editing inside the returned node.
584 if (root
&& !isContentEditable(n
) && onlyReturnEditableNodes
)
586 if ((*nodeIsOfType
)(n
))
595 Node
* enclosingTableCell(const Position
& p
)
597 return enclosingNodeOfType(p
, &isTableCell
);
600 Node
* enclosingAnchorElement(const Position
& p
)
605 Node
* node
= p
.node();
606 while (node
&& !(node
->isElementNode() && node
->isLink()))
607 node
= node
->parentNode();
611 Node
* enclosingList(Node
* node
)
616 Node
* root
= highestEditableRoot(Position(node
, 0));
618 for (Node
* n
= node
->parentNode(); n
; n
= n
->parentNode()) {
619 if (n
->hasTagName(ulTag
) || n
->hasTagName(olTag
))
628 Node
* enclosingListChild(Node
*node
)
632 // Check for a list item element, or for a node whose parent is a list element. Such a node
633 // will appear visually as a list item (but without a list marker)
634 Node
* root
= highestEditableRoot(Position(node
, 0));
636 // FIXME: This function is inappropriately named if it starts with node instead of node->parentNode()
637 for (Node
* n
= node
; n
&& n
->parentNode(); n
= n
->parentNode()) {
638 if (n
->hasTagName(liTag
) || isListElement(n
->parentNode()))
640 if (n
== root
|| isTableCell(n
))
647 static Node
* embeddedSublist(Node
* listItem
)
649 // Check the DOM so that we'll find collapsed sublists without renderers.
650 for (Node
* n
= listItem
->firstChild(); n
; n
= n
->nextSibling()) {
651 if (isListElement(n
))
658 static Node
* appendedSublist(Node
* listItem
)
660 // Check the DOM so that we'll find collapsed sublists without renderers.
661 for (Node
* n
= listItem
->nextSibling(); n
; n
= n
->nextSibling()) {
662 if (isListElement(n
))
664 if (n
->renderer() && n
->renderer()->isListItem())
671 Node
* enclosingEmptyListItem(const VisiblePosition
& visiblePos
)
673 // Check that position is on a line by itself inside a list item
674 Node
* listChildNode
= enclosingListChild(visiblePos
.deepEquivalent().node());
675 if (!listChildNode
|| !isStartOfParagraph(visiblePos
) || !isEndOfParagraph(visiblePos
))
678 VisiblePosition
firstInListChild(Position(listChildNode
, 0));
679 VisiblePosition
lastInListChild(Position(listChildNode
, maxDeepOffset(listChildNode
)));
681 if (firstInListChild
!= visiblePos
|| lastInListChild
!= visiblePos
)
684 if (embeddedSublist(listChildNode
) || appendedSublist(listChildNode
))
687 return listChildNode
;
690 Node
* outermostEnclosingListChild(Node
* node
)
693 Node
* nextNode
= node
;
694 while ((nextNode
= enclosingListChild(nextNode
)))
699 Node
* outermostEnclosingList(Node
* node
)
702 Node
* nextNode
= node
;
703 while ((nextNode
= enclosingList(nextNode
)))
708 Node
* highestAncestor(Node
* node
)
712 while ((node
= node
->parentNode()))
717 // FIXME: do not require renderer, so that this can be used within fragments, or rename to isRenderedTable()
718 bool isTableElement(Node
* n
)
720 if (!n
|| !n
->isElementNode())
723 RenderObject
* renderer
= n
->renderer();
724 return (renderer
&& (renderer
->style()->display() == TABLE
|| renderer
->style()->display() == INLINE_TABLE
));
727 bool isTableCell(const Node
* node
)
729 RenderObject
* r
= node
->renderer();
731 return node
->hasTagName(tdTag
) || node
->hasTagName(thTag
);
733 return r
->isTableCell();
736 PassRefPtr
<Element
> createDefaultParagraphElement(Document
*document
)
738 ExceptionCode ec
= 0;
739 RefPtr
<Element
> element
= document
->createElementNS(xhtmlNamespaceURI
, "div", ec
);
741 return element
.release();
744 PassRefPtr
<Element
> createBreakElement(Document
*document
)
746 ExceptionCode ec
= 0;
747 RefPtr
<Element
> breakNode
= document
->createElementNS(xhtmlNamespaceURI
, "br", ec
);
749 return breakNode
.release();
752 PassRefPtr
<Element
> createOrderedListElement(Document
*document
)
754 ExceptionCode ec
= 0;
755 RefPtr
<Element
> element
= document
->createElementNS(xhtmlNamespaceURI
, "ol", ec
);
757 return element
.release();
760 PassRefPtr
<Element
> createUnorderedListElement(Document
*document
)
762 ExceptionCode ec
= 0;
763 RefPtr
<Element
> element
= document
->createElementNS(xhtmlNamespaceURI
, "ul", ec
);
765 return element
.release();
768 PassRefPtr
<Element
> createListItemElement(Document
*document
)
770 ExceptionCode ec
= 0;
771 RefPtr
<Element
> breakNode
= document
->createElementNS(xhtmlNamespaceURI
, "li", ec
);
773 return breakNode
.release();
776 PassRefPtr
<Element
> createElement(Document
* document
, const String
& tagName
)
778 ExceptionCode ec
= 0;
779 RefPtr
<Element
> breakNode
= document
->createElementNS(xhtmlNamespaceURI
, tagName
, ec
);
781 return breakNode
.release();
784 bool isTabSpanNode(const Node
*node
)
786 return node
&& node
->hasTagName(spanTag
) && node
->isElementNode() && static_cast<const Element
*>(node
)->getAttribute(classAttr
) == AppleTabSpanClass
;
789 bool isTabSpanTextNode(const Node
*node
)
791 return node
&& node
->isTextNode() && node
->parentNode() && isTabSpanNode(node
->parentNode());
794 Node
*tabSpanNode(const Node
*node
)
796 return isTabSpanTextNode(node
) ? node
->parentNode() : 0;
799 Position
positionBeforeTabSpan(const Position
& pos
)
801 Node
*node
= pos
.node();
802 if (isTabSpanTextNode(node
))
803 node
= tabSpanNode(node
);
804 else if (!isTabSpanNode(node
))
807 return positionBeforeNode(node
);
810 PassRefPtr
<Element
> createTabSpanElement(Document
* document
, PassRefPtr
<Node
> tabTextNode
)
812 // make the span to hold the tab
813 ExceptionCode ec
= 0;
814 RefPtr
<Element
> spanElement
= document
->createElementNS(xhtmlNamespaceURI
, "span", ec
);
816 spanElement
->setAttribute(classAttr
, AppleTabSpanClass
);
817 spanElement
->setAttribute(styleAttr
, "white-space:pre");
819 // add tab text to that span
821 tabTextNode
= document
->createEditingTextNode("\t");
822 spanElement
->appendChild(tabTextNode
, ec
);
825 return spanElement
.release();
828 PassRefPtr
<Element
> createTabSpanElement(Document
* document
, const String
& tabText
)
830 return createTabSpanElement(document
, document
->createTextNode(tabText
));
833 PassRefPtr
<Element
> createTabSpanElement(Document
* document
)
835 return createTabSpanElement(document
, PassRefPtr
<Node
>());
838 bool isNodeRendered(const Node
*node
)
843 RenderObject
*renderer
= node
->renderer();
847 return renderer
->style()->visibility() == VISIBLE
;
850 Node
*nearestMailBlockquote(const Node
*node
)
852 for (Node
*n
= const_cast<Node
*>(node
); n
; n
= n
->parentNode()) {
853 if (isMailBlockquote(n
))
859 bool isMailBlockquote(const Node
*node
)
861 if (!node
|| !node
->isElementNode() && !node
->hasTagName(blockquoteTag
))
864 return static_cast<const Element
*>(node
)->getAttribute("type") == "cite";
867 int caretMinOffset(const Node
* n
)
869 RenderObject
* r
= n
->renderer();
870 ASSERT(!n
->isCharacterDataNode() || !r
|| r
->isText()); // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now.
871 return r
? r
->caretMinOffset() : 0;
874 int caretMaxOffset(const Node
* n
)
876 RenderObject
* r
= n
->renderer();
877 ASSERT(!n
->isCharacterDataNode() || !r
|| r
->isText()); // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now.
879 return r
->caretMaxOffset();
881 if (n
->isCharacterDataNode()) {
882 const CharacterData
* c
= static_cast<const CharacterData
*>(n
);
883 return static_cast<int>(c
->length());
888 bool lineBreakExistsAtPosition(const VisiblePosition
& visiblePosition
)
890 if (visiblePosition
.isNull())
893 Position
downstream(visiblePosition
.deepEquivalent().downstream());
894 return downstream
.node()->hasTagName(brTag
) ||
895 downstream
.node()->isTextNode() && downstream
.node()->renderer()->style()->preserveNewline() && visiblePosition
.characterAfter() == '\n';
898 // Modifies selections that have an end point at the edge of a table
899 // that contains the other endpoint so that they don't confuse
900 // code that iterates over selected paragraphs.
901 Selection
selectionForParagraphIteration(const Selection
& original
)
903 Selection
newSelection(original
);
904 VisiblePosition
startOfSelection(newSelection
.visibleStart());
905 VisiblePosition
endOfSelection(newSelection
.visibleEnd());
907 // If the end of the selection to modify is just after a table, and
908 // if the start of the selection is inside that table, then the last paragraph
909 // that we'll want modify is the last one inside the table, not the table itself
910 // (a table is itself a paragraph).
911 if (Node
* table
= isFirstPositionAfterTable(endOfSelection
))
912 if (startOfSelection
.deepEquivalent().node()->isDescendantOf(table
))
913 newSelection
= Selection(startOfSelection
, endOfSelection
.previous(true));
915 // If the start of the selection to modify is just before a table,
916 // and if the end of the selection is inside that table, then the first paragraph
917 // we'll want to modify is the first one inside the table, not the paragraph
918 // containing the table itself.
919 if (Node
* table
= isLastPositionBeforeTable(startOfSelection
))
920 if (endOfSelection
.deepEquivalent().node()->isDescendantOf(table
))
921 newSelection
= Selection(startOfSelection
.next(true), endOfSelection
);
927 int indexForVisiblePosition(VisiblePosition
& visiblePosition
)
929 if (visiblePosition
.isNull())
931 Position
p(visiblePosition
.deepEquivalent());
932 RefPtr
<Range
> range
= Range::create(p
.node()->document(), Position(p
.node()->document(), 0), rangeCompliantEquivalent(p
));
933 return TextIterator::rangeLength(range
.get(), true);
936 PassRefPtr
<Range
> avoidIntersectionWithNode(const Range
* range
, Node
* node
)
941 Document
* document
= range
->ownerDocument();
943 Node
* startContainer
= range
->startContainer();
944 int startOffset
= range
->startOffset();
945 Node
* endContainer
= range
->endContainer();
946 int endOffset
= range
->endOffset();
951 ASSERT(endContainer
);
953 if (startContainer
== node
|| startContainer
->isDescendantOf(node
)) {
954 ASSERT(node
->parentNode());
955 startContainer
= node
->parentNode();
956 startOffset
= node
->nodeIndex();
958 if (endContainer
== node
|| endContainer
->isDescendantOf(node
)) {
959 ASSERT(node
->parentNode());
960 endContainer
= node
->parentNode();
961 endOffset
= node
->nodeIndex();
964 return Range::create(document
, startContainer
, startOffset
, endContainer
, endOffset
);
967 Selection
avoidIntersectionWithNode(const Selection
& selection
, Node
* node
)
969 if (selection
.isNone())
970 return Selection(selection
);
972 Selection
updatedSelection(selection
);
973 Node
* base
= selection
.base().node();
974 Node
* extent
= selection
.extent().node();
978 if (base
== node
|| base
->isDescendantOf(node
)) {
979 ASSERT(node
->parentNode());
980 updatedSelection
.setBase(Position(node
->parentNode(), node
->nodeIndex()));
983 if (extent
== node
|| extent
->isDescendantOf(node
)) {
984 ASSERT(node
->parentNode());
985 updatedSelection
.setExtent(Position(node
->parentNode(), node
->nodeIndex()));
988 return updatedSelection
;
991 } // namespace WebCore