- fixed <rdar://problem/
3947901> REGRESSION (Mail): Pasting paragraph of rich text leaves insertion point before pasted text
- fixed <rdar://problem/
3949790> hitting return after underlined line results in too much or too little underlined
- fixed <rdar://problem/
3981759> nil-deref and crash when pasting just a paragraph break
- fixed a couple problems I discovered while working with bug
3949790
* khtml/editing/htmlediting.cpp:
(khtml::ApplyStyleCommand::applyInlineStyle): Pass StayInBlock to upstream. Without this, we end up going too far
upstream in the test case in bug
3949790.
(khtml::ApplyStyleCommand::removeInlineStyle): Pass StayInBlock to upstream and downstream. Same reason as above.
(khtml::ReplaceSelectionCommand::doApply): Update endPos if inserting a new node and endPos is using that node's
parent and an offset past the node being inserted. That change fixes a problem with the position of the insertion point
after pasting into the top level of a document (from test cases in
3947901 and
3949790). When setting insertionPos, use
code that works when lastNodeInserted is a block rather than a text node. That change fixes a problem where a newline is
not added when pasting an entire paragraph into the end of a document (from test case in
3949790). Added nil check before
checking if lastNodeInserted is a <br> element, which fixes the crash when pasting just a paragraph break.
* khtml/editing/visible_units.h: Filled out the set of calls to add some boolean checks for lines (needed for the
bug fix), and calls for blocks (not yet implemented), and documents. The document checks may need refinement to
properly handle documents with a mix of editable and non-editable content, but for now they just refactor code
and make things a little clearer. Also removed the "include line break" parameter from endOfSentence.
* khtml/editing/visible_units.cpp:
(khtml::rootBoxForLine): Added.
(khtml::startOfLine): Added. Algorithm taken from selectionForLine in selection.cpp.
(khtml::endOfLine): Ditto.
(khtml::inSameLine): Added.
(khtml::isStartOfLine): Added.
(khtml::isEndOfLine): Added.
(khtml::endOfSentence): Removed "include line break" parameter.
(khtml::inSameParagraph): Added a null check.
(khtml::isStartOfParagraph): Ditto.
(khtml::isEndOfParagraph): Ditto.
(khtml::startOfBlock): Added.
(khtml::endOfBlock): Added.
(khtml::inSameBlock): Added.
(khtml::isStartOfBlock): Added.
(khtml::isEndOfBlock): Added.
(khtml::startOfDocument): Added.
(khtml::endOfDocument): Added.
(khtml::inSameDocument): Added.
(khtml::isStartOfDocument): Added.
(khtml::isEndOfDocument): Added.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@8480
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2005-01-31 Darin Adler <darin@apple.com>
+
+ Reviewed by Ken and Harrison.
+
+ - fixed <rdar://problem/3947901> REGRESSION (Mail): Pasting paragraph of rich text leaves insertion point before pasted text
+ - fixed <rdar://problem/3949790> hitting return after underlined line results in too much or too little underlined
+ - fixed <rdar://problem/3981759> nil-deref and crash when pasting just a paragraph break
+ - fixed a couple problems I discovered while working with bug 3949790
+
+ * khtml/editing/htmlediting.cpp:
+ (khtml::ApplyStyleCommand::applyInlineStyle): Pass StayInBlock to upstream. Without this, we end up going too far
+ upstream in the test case in bug 3949790.
+ (khtml::ApplyStyleCommand::removeInlineStyle): Pass StayInBlock to upstream and downstream. Same reason as above.
+ (khtml::ReplaceSelectionCommand::doApply): Update endPos if inserting a new node and endPos is using that node's
+ parent and an offset past the node being inserted. That change fixes a problem with the position of the insertion point
+ after pasting into the top level of a document (from test cases in 3947901 and 3949790). When setting insertionPos, use
+ code that works when lastNodeInserted is a block rather than a text node. That change fixes a problem where a newline is
+ not added when pasting an entire paragraph into the end of a document (from test case in 3949790). Added nil check before
+ checking if lastNodeInserted is a <br> element, which fixes the crash when pasting just a paragraph break.
+
+ * khtml/editing/visible_units.h: Filled out the set of calls to add some boolean checks for lines (needed for the
+ bug fix), and calls for blocks (not yet implemented), and documents. The document checks may need refinement to
+ properly handle documents with a mix of editable and non-editable content, but for now they just refactor code
+ and make things a little clearer. Also removed the "include line break" parameter from endOfSentence.
+ * khtml/editing/visible_units.cpp:
+ (khtml::rootBoxForLine): Added.
+ (khtml::startOfLine): Added. Algorithm taken from selectionForLine in selection.cpp.
+ (khtml::endOfLine): Ditto.
+ (khtml::inSameLine): Added.
+ (khtml::isStartOfLine): Added.
+ (khtml::isEndOfLine): Added.
+ (khtml::endOfSentence): Removed "include line break" parameter.
+ (khtml::inSameParagraph): Added a null check.
+ (khtml::isStartOfParagraph): Ditto.
+ (khtml::isEndOfParagraph): Ditto.
+ (khtml::startOfBlock): Added.
+ (khtml::endOfBlock): Added.
+ (khtml::inSameBlock): Added.
+ (khtml::isStartOfBlock): Added.
+ (khtml::isEndOfBlock): Added.
+ (khtml::startOfDocument): Added.
+ (khtml::endOfDocument): Added.
+ (khtml::inSameDocument): Added.
+ (khtml::isStartOfDocument): Added.
+ (khtml::isEndOfDocument): Added.
+
2005-01-30 Darin Adler <darin@apple.com>
Reviewed by John.
ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
return;
}
- else if (visibleParagraphEnd.next().isNull()) {
+ else if (isEndOfDocument(visibleParagraphEnd)) {
// At the end of the document. We can bail here as well.
return;
}
// This will ensure we remove all traces of the relevant styles from the selection
// and prevent us from adding redundant ones, as described in:
// <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
- removeInlineStyle(style, start.upstream(), end);
+ removeInlineStyle(style, start.upstream(StayInBlock), end);
if (splitStart || splitEnd) {
cleanUpEmptyStyleSpans(start, end);
CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
if (textDecorationSpecialProperty) {
- pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
+ pushDownTextDecorationStyleAtBoundaries(start.downstream(StayInBlock), end.upstream(StayInBlock));
style = style->copy();
style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
}
//
NodeImpl *old = m_startNode;
VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd);
- if (visibleEnd.next().isNull() && !isFirstVisiblePositionOnLine(visibleEnd)) {
+ if (isEndOfDocument(visibleEnd) && !isFirstVisiblePositionOnLine(visibleEnd)) {
m_startNode = m_startBlock->firstChild();
}
else {
}
else if (!mergeEnd && !insertionNodeIsBody && isProbablyBlock(refNode) && isEndOfParagraph(visiblePos))
insertNodeAfter(refNode, insertionPos.node());
- else
+ else {
insertNodeAt(refNode, insertionPos.node(), insertionPos.offset());
+ if (insertionPos.node() == endPos.node() && insertionPos.offset() <= endPos.offset())
+ endPos = Position(endPos.node(), endPos.offset() + 1);
+ }
if (!firstNodeInserted)
firstNodeInserted = refNode;
if (!lastNodeInsertedInMergeEnd)
node = next;
}
document()->updateLayout();
- insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+ if (lastNodeInserted->isTextNode())
+ insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+ else if (lastNodeInserted->childNodeCount() > 0)
+ insertionPos = Position(lastNodeInserted, lastNodeInserted->childNodeCount());
+ else
+ insertionPos = Position(lastNodeInserted->parentNode(), lastNodeInserted->nodeIndex() + 1);
}
// Handle "smart replace" whitespace
// Handle trailing newline
if (m_fragment.hasInterchangeNewline()) {
- if ((startBlock == endBlock) && (VisiblePosition(lastNodeInserted, lastNodeInserted->caretMaxOffset()).next().isNull())) {
-
+ if (startBlock == endBlock && isEndOfDocument(VisiblePosition(insertionPos))) {
setEndingSelection(insertionPos);
insertParagraphSeparator();
endPos = endingSelection().end().downstream();
completeHTMLReplacement(startPos, endPos);
}
else {
- if (lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
+ if (lastNodeInserted && lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
document()->updateLayout();
VisiblePosition pos(Position(lastNodeInserted, 0));
if (isLastVisiblePositionInBlock(pos)) {
}
else {
Selection selection = part->selection();
- if (selection.isCaret() && VisiblePosition(selection.start()).next().isNull()) {
+ if (selection.isCaret() && isEndOfDocument(VisiblePosition(selection.start()))) {
// do nothing for a delete key at the start of an editable element.
}
else {
// ---------
+static RootInlineBox *rootBoxForLine(const VisiblePosition &c, EAffinity affinity)
+{
+ Position p = c.deepEquivalent();
+ NodeImpl *node = p.node();
+ if (!node)
+ return 0;
+
+ RenderObject *renderer = node->renderer();
+ if (!renderer)
+ return 0;
+
+ InlineBox *box = renderer->inlineBox(p.offset(), affinity);
+ if (!box)
+ return 0;
+
+ return box->root();
+}
+
+VisiblePosition startOfLine(const VisiblePosition &c, EAffinity affinity)
+{
+ RootInlineBox *rootBox = rootBoxForLine(c, affinity);
+ if (!rootBox)
+ return VisiblePosition();
+
+ InlineBox *startBox = rootBox->firstChild();
+ if (!startBox)
+ return VisiblePosition();
+
+ RenderObject *startRenderer = startBox->object();
+ if (!startRenderer)
+ return VisiblePosition();
+
+ NodeImpl *startNode = startRenderer->element();
+ if (!startNode)
+ return VisiblePosition();
+
+ long startOffset = 0;
+ if (startBox->isInlineTextBox()) {
+ InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox);
+ startOffset = startTextBox->m_start;
+ }
+ return VisiblePosition(startNode, startOffset);
+}
+
+VisiblePosition endOfLine(const VisiblePosition &c, EAffinity affinity, EIncludeLineBreak includeLineBreak)
+{
+ // FIXME: Need to implement the "include line break" version.
+ assert(includeLineBreak == DoNotIncludeLineBreak);
+
+ RootInlineBox *rootBox = rootBoxForLine(c, affinity);
+ if (!rootBox)
+ return VisiblePosition();
+
+ InlineBox *endBox = rootBox->lastChild();
+ if (!endBox)
+ return VisiblePosition();
+
+ RenderObject *endRenderer = endBox->object();
+ if (!endRenderer)
+ return VisiblePosition();
+
+ NodeImpl *endNode = endRenderer->element();
+ if (!endNode)
+ return VisiblePosition();
+
+ long endOffset = 1;
+ if (endBox->isInlineTextBox()) {
+ InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox);
+ endOffset = endTextBox->m_start + endTextBox->m_len;
+ }
+ return VisiblePosition(endNode, endOffset);
+}
+
+bool inSameLine(const VisiblePosition &a, EAffinity aa, const VisiblePosition &b, EAffinity ab)
+{
+ return a.isNotNull() && startOfLine(a, aa) == startOfLine(b, ab);
+}
+
+bool isStartOfLine(const VisiblePosition &p, EAffinity affinity)
+{
+ return p.isNotNull() && p == startOfLine(p, affinity);
+}
+
+bool isEndOfLine(const VisiblePosition &p, EAffinity affinity)
+{
+ return p.isNotNull() && p == endOfLine(p, affinity, DoNotIncludeLineBreak);
+}
+
VisiblePosition previousLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
{
- Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
- return VisiblePosition(pos.previousLinePosition(x, affinity));
+ Position p = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
+ return VisiblePosition(p.previousLinePosition(x, affinity));
}
VisiblePosition nextLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
{
- Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
- return VisiblePosition(pos.nextLinePosition(x, affinity));
+ Position p = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
+ return VisiblePosition(p.nextLinePosition(x, affinity));
}
// ---------
return end;
}
-VisiblePosition endOfSentence(const VisiblePosition &c, EIncludeLineBreak includeLineBreak)
+VisiblePosition endOfSentence(const VisiblePosition &c)
{
return nextBoundary(c, endSentenceBoundary);
}
bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b)
{
- return a == b || startOfParagraph(a) == startOfParagraph(b);
+ return a.isNotNull() && startOfParagraph(a) == startOfParagraph(b);
}
bool isStartOfParagraph(const VisiblePosition &pos)
{
- return pos == startOfParagraph(pos);
+ return pos.isNotNull() && pos == startOfParagraph(pos);
}
bool isEndOfParagraph(const VisiblePosition &pos)
{
- return pos == endOfParagraph(pos, DoNotIncludeLineBreak);
+ return pos.isNotNull() && pos == endOfParagraph(pos, DoNotIncludeLineBreak);
}
VisiblePosition previousParagraphPosition(const VisiblePosition &p, EAffinity affinity, int x)
return pos;
}
+// ---------
+
+// written, but not yet tested
+VisiblePosition startOfBlock(const VisiblePosition &c)
+{
+ Position p = c.deepEquivalent();
+ NodeImpl *startNode = p.node();
+ if (!startNode)
+ return VisiblePosition();
+
+ NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+
+ NodeImpl *node = startNode;
+ long offset = p.offset();
+
+ for (NodeImpl *n = startNode; n; n = n->traversePreviousNodePostOrder(startBlock)) {
+ RenderObject *r = n->renderer();
+ if (!r)
+ continue;
+ RenderStyle *style = r->style();
+ if (style->visibility() != VISIBLE)
+ continue;
+ if (r->isBlockFlow())
+ break;
+ if (r->isText()) {
+ node = n;
+ offset = 0;
+ } else if (r->isReplaced()) {
+ node = n;
+ offset = 0;
+ }
+ }
+
+ return VisiblePosition(node, offset);
+}
+
+// written, but not yet tested
+VisiblePosition endOfBlock(const VisiblePosition &c, EIncludeLineBreak includeLineBreak)
+{
+ Position p = c.deepEquivalent();
+
+ NodeImpl *startNode = p.node();
+ if (!startNode)
+ return VisiblePosition();
+
+ NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+ NodeImpl *stayInsideBlock = includeLineBreak ? 0 : startBlock;
+
+ NodeImpl *node = startNode;
+ long offset = p.offset();
+
+ for (NodeImpl *n = startNode; n; n = n->traverseNextNode(stayInsideBlock)) {
+ RenderObject *r = n->renderer();
+ if (!r)
+ continue;
+ RenderStyle *style = r->style();
+ if (style->visibility() != VISIBLE)
+ continue;
+ if (r->isBlockFlow()) {
+ if (includeLineBreak)
+ return VisiblePosition(n, 0);
+ break;
+ }
+ if (r->isText()) {
+ if (includeLineBreak && !n->isAncestor(startBlock))
+ return VisiblePosition(n, 0);
+ node = n;
+ offset = static_cast<RenderText *>(r)->length();
+ } else if (r->isReplaced()) {
+ node = n;
+ offset = 1;
+ if (includeLineBreak && !n->isAncestor(startBlock))
+ break;
+ }
+ }
+
+ return VisiblePosition(node, offset);
+}
+
+bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b)
+{
+ return a.isNotNull() && startOfBlock(a) == startOfBlock(b);
+}
+
+bool isStartOfBlock(const VisiblePosition &pos)
+{
+ return pos.isNotNull() && pos == startOfBlock(pos);
+}
+
+bool isEndOfBlock(const VisiblePosition &pos)
+{
+ return pos.isNotNull() && pos == endOfBlock(pos, DoNotIncludeLineBreak);
+}
+
+// ---------
+
+VisiblePosition startOfDocument(const VisiblePosition &c)
+{
+ Position p = c.deepEquivalent();
+ NodeImpl *node = p.node();
+ if (!node)
+ return VisiblePosition();
+
+ DocumentImpl *doc = node->getDocument();
+ if (!doc)
+ return VisiblePosition();
+
+ return VisiblePosition(doc->documentElement(), 0);
+}
+
+VisiblePosition endOfDocument(const VisiblePosition &c)
+{
+ Position p = c.deepEquivalent();
+ NodeImpl *node = p.node();
+ if (!node)
+ return VisiblePosition();
+
+ DocumentImpl *doc = node->getDocument();
+ if (!doc)
+ return VisiblePosition();
+
+ NodeImpl *docElem = doc->documentElement();
+ if (!node)
+ return VisiblePosition();
+
+ return VisiblePosition(docElem, docElem->childNodeCount());
+}
+
+bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b)
+{
+ Position ap = a.deepEquivalent();
+ NodeImpl *an = ap.node();
+ if (!an)
+ return false;
+ Position bp = b.deepEquivalent();
+ NodeImpl *bn = bp.node();
+ if (an == bn)
+ return true;
+
+ return an->getDocument() == bn->getDocument();
+}
+
+bool isStartOfDocument(const VisiblePosition &p)
+{
+ return p.isNotNull() && p.previous().isNull();
+}
+
+bool isEndOfDocument(const VisiblePosition &p)
+{
+ return p.isNotNull() && p.next().isNull();
+}
+
} // namespace khtml
VisiblePosition endOfLine(const VisiblePosition &, EAffinity, EIncludeLineBreak = DoNotIncludeLineBreak);
VisiblePosition previousLinePosition(const VisiblePosition &, EAffinity, int x);
VisiblePosition nextLinePosition(const VisiblePosition &, EAffinity, int x);
+bool inSameLine(const VisiblePosition &, EAffinity, const VisiblePosition &, EAffinity);
+bool isStartOfLine(const VisiblePosition &, EAffinity);
+bool isEndOfLine(const VisiblePosition &, EAffinity);
// sentences
VisiblePosition startOfSentence(const VisiblePosition &);
-VisiblePosition endOfSentence(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
+VisiblePosition endOfSentence(const VisiblePosition &);
VisiblePosition previousSentencePosition(const VisiblePosition &, EAffinity, int x);
VisiblePosition nextSentencePosition(const VisiblePosition &, EAffinity, int x);
-// paragraphs
+// paragraphs (perhaps a misnomer, can be divided by line break elements)
VisiblePosition startOfParagraph(const VisiblePosition &);
VisiblePosition endOfParagraph(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
VisiblePosition previousParagraphPosition(const VisiblePosition &, EAffinity, int x);
bool isStartOfParagraph(const VisiblePosition &);
bool isEndOfParagraph(const VisiblePosition &);
+// blocks (true paragraphs; line break elements don't break blocks)
+VisiblePosition startOfBlock(const VisiblePosition &);
+VisiblePosition endOfBlock(const VisiblePosition &, EIncludeLineBreak = DoNotIncludeLineBreak);
+bool inSameBlock(const VisiblePosition &, const VisiblePosition &);
+bool isStartOfBlock(const VisiblePosition &);
+bool isEndOfBlock(const VisiblePosition &);
+
+// document
+VisiblePosition startOfDocument(const VisiblePosition &);
+VisiblePosition endOfDocument(const VisiblePosition &);
+bool inSameDocument(const VisiblePosition &, const VisiblePosition &);
+bool isStartOfDocument(const VisiblePosition &);
+bool isEndOfDocument(const VisiblePosition &);
+
} // namespace DOM
#endif // KHTML_EDITING_VISIBLE_POSITION_H