summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
authorFeng Qian <fqian@google.com>2009-06-17 12:12:20 -0700
committerFeng Qian <fqian@google.com>2009-06-17 12:12:20 -0700
commit5f1ab04193ad0130ca8204aadaceae083aca9881 (patch)
tree5a92cd389e2cfe7fb67197ce14b38469462379f8 /WebCore/editing
parent194315e5a908cc8ed67d597010544803eef1ac59 (diff)
downloadexternal_webkit-5f1ab04193ad0130ca8204aadaceae083aca9881.zip
external_webkit-5f1ab04193ad0130ca8204aadaceae083aca9881.tar.gz
external_webkit-5f1ab04193ad0130ca8204aadaceae083aca9881.tar.bz2
Get WebKit r44544.
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp153
-rw-r--r--WebCore/editing/ApplyStyleCommand.h4
-rw-r--r--WebCore/editing/BreakBlockquoteCommand.cpp51
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp101
-rw-r--r--WebCore/editing/CompositeEditCommand.h4
-rw-r--r--WebCore/editing/DeleteButtonController.cpp61
-rw-r--r--WebCore/editing/DeleteSelectionCommand.cpp76
-rw-r--r--WebCore/editing/DeleteSelectionCommand.h2
-rw-r--r--WebCore/editing/Editor.cpp433
-rw-r--r--WebCore/editing/Editor.h22
-rw-r--r--WebCore/editing/EditorCommand.cpp1
-rw-r--r--WebCore/editing/FormatBlockCommand.cpp2
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp18
-rw-r--r--WebCore/editing/InsertLineBreakCommand.cpp10
-rw-r--r--WebCore/editing/InsertListCommand.cpp12
-rw-r--r--WebCore/editing/InsertParagraphSeparatorCommand.cpp37
-rw-r--r--WebCore/editing/InsertTextCommand.cpp35
-rw-r--r--WebCore/editing/ModifySelectionListLevel.cpp2
-rw-r--r--WebCore/editing/MoveSelectionCommand.cpp6
-rw-r--r--WebCore/editing/RemoveCSSPropertyCommand.h1
-rw-r--r--WebCore/editing/RemoveFormatCommand.cpp1
-rw-r--r--WebCore/editing/RemoveNodePreservingChildrenCommand.cpp3
-rw-r--r--WebCore/editing/ReplaceNodeWithSpanCommand.cpp87
-rw-r--r--WebCore/editing/ReplaceNodeWithSpanCommand.h62
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp81
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.h5
-rw-r--r--WebCore/editing/SelectionController.cpp155
-rw-r--r--WebCore/editing/SelectionController.h16
-rw-r--r--WebCore/editing/TextAffinity.h14
-rw-r--r--WebCore/editing/TextIterator.cpp16
-rw-r--r--WebCore/editing/TypingCommand.cpp64
-rw-r--r--WebCore/editing/TypingCommand.h1
-rw-r--r--WebCore/editing/VisiblePosition.cpp23
-rw-r--r--WebCore/editing/VisiblePosition.h8
-rw-r--r--WebCore/editing/VisibleSelection.cpp17
-rw-r--r--WebCore/editing/VisibleSelection.h4
-rw-r--r--WebCore/editing/gtk/SelectionControllerGtk.cpp45
-rw-r--r--WebCore/editing/htmlediting.cpp42
-rw-r--r--WebCore/editing/htmlediting.h6
-rw-r--r--WebCore/editing/mac/SelectionControllerMac.mm2
-rw-r--r--WebCore/editing/markup.cpp71
-rw-r--r--WebCore/editing/visible_units.cpp327
-rw-r--r--WebCore/editing/visible_units.h3
43 files changed, 1628 insertions, 456 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp
index d43cc81..8d0312b 100644
--- a/WebCore/editing/ApplyStyleCommand.cpp
+++ b/WebCore/editing/ApplyStyleCommand.cpp
@@ -27,6 +27,7 @@
#include "ApplyStyleCommand.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSParser.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
@@ -228,7 +229,11 @@ bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *prop
{
ASSERT(pos.isNotNull());
RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
- RefPtr<CSSValue> value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
+ RefPtr<CSSValue> value;
+ if (property->id() == CSSPropertyFontSize)
+ value = style->getFontSizeCSSValuePreferringKeyword();
+ else
+ value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
if (!value)
return false;
return equalIgnoringCase(value->cssText(), property->value()->cssText());
@@ -256,7 +261,7 @@ static bool isUnstyledStyleSpan(const Node* node)
const HTMLElement* elem = static_cast<const HTMLElement*>(node);
CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
- return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(classAttr) == styleSpanClassString();
+ return (!inlineStyleDecl || inlineStyleDecl->isEmpty()) && elem->getAttribute(classAttr) == styleSpanClassString();
}
static bool isSpanWithoutAttributesOrUnstyleStyleSpan(const Node* node)
@@ -265,8 +270,8 @@ static bool isSpanWithoutAttributesOrUnstyleStyleSpan(const Node* node)
return false;
const HTMLElement* elem = static_cast<const HTMLElement*>(node);
- NamedAttrMap* attributes = elem->attributes(true); // readonly
- if (attributes->length() == 0)
+ NamedNodeMap* attributes = elem->attributes(true); // readonly
+ if (attributes->isEmpty())
return true;
return isUnstyledStyleSpan(node);
@@ -278,7 +283,7 @@ static bool isEmptyFontTag(const Node *node)
return false;
const Element *elem = static_cast<const Element *>(node);
- NamedAttrMap *map = elem->attributes(true); // true for read-only
+ NamedNodeMap *map = elem->attributes(true); // true for read-only
return (!map || map->length() == 1) && elem->getAttribute(classAttr) == styleSpanClassString();
}
@@ -337,7 +342,7 @@ ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnl
void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
{
- ASSERT(Range::compareBoundaryPoints(newEnd, newStart) >= 0);
+ ASSERT(comparePositions(newEnd, newStart) >= 0);
if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
m_useEndingSelection = true;
@@ -403,7 +408,7 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style)
// get positions we want to use for applying style
Position start = startPosition();
Position end = endPosition();
- if (Range::compareBoundaryPoints(end, start) < 0) {
+ if (comparePositions(end, start) < 0) {
Position swap = start;
start = end;
end = swap;
@@ -426,7 +431,7 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style)
VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
StyleChange styleChange(style, paragraphStart.deepEquivalent());
- if (styleChange.cssStyle().length() > 0 || m_removeOnly) {
+ if (styleChange.cssStyle().length() || m_removeOnly) {
RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node());
RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
if (newBlock)
@@ -479,7 +484,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration
Position start = startPosition();
Position end = endPosition();
- if (Range::compareBoundaryPoints(end, start) < 0) {
+ if (comparePositions(end, start) < 0) {
Position swap = start;
start = end;
end = swap;
@@ -520,7 +525,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration
start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
Node *startNode = start.node();
- if (startNode->isTextNode() && start.m_offset >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
+ if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
startNode = startNode->traverseNextNode();
// Store away font size before making any changes to the document.
@@ -564,7 +569,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration
inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false);
setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText());
}
- if (inlineStyleDecl->length() == 0) {
+ if (inlineStyleDecl->isEmpty()) {
removeNodeAttribute(element.get(), styleAttr);
// FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test.
if (isUnstyledStyleSpan(element.get()))
@@ -714,7 +719,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
// adjust to the positions we want to use for applying style
Position start = startPosition();
Position end = endPosition();
- if (Range::compareBoundaryPoints(end, start) < 0) {
+ if (comparePositions(end, start) < 0) {
Position swap = start;
start = end;
end = swap;
@@ -775,7 +780,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
- if (Range::compareBoundaryPoints(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
+ if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
@@ -868,17 +873,17 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl
bool rangeIsEmpty = false;
- if (start.m_offset >= caretMaxOffset(start.node())) {
+ if (start.deprecatedEditingOffset() >= caretMaxOffset(start.node())) {
node = node->traverseNextNode();
Position newStart = Position(node, 0);
- if (!node || Range::compareBoundaryPoints(end, newStart) < 0)
+ if (!node || comparePositions(end, newStart) < 0)
rangeIsEmpty = true;
}
if (!rangeIsEmpty) {
// pastEndNode is the node after the last fully selected node.
Node* pastEndNode = end.node();
- if (end.m_offset >= caretMaxOffset(end.node()))
+ if (end.deprecatedEditingOffset() >= caretMaxOffset(end.node()))
pastEndNode = end.node()->traverseNextSibling();
// FIXME: Callers should perform this operation on a Range that includes the br
// if they want style applied to the empty line.
@@ -896,7 +901,7 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl
// This is a plaintext-only region. Only proceed if it's fully selected.
// pastEndNode is the node after the last fully selected node, so if it's inside node then
// node isn't fully selected.
- if (pastEndNode->isDescendantOf(node))
+ if (pastEndNode && pastEndNode->isDescendantOf(node))
break;
// Add to this element's inline style and skip over its contents.
HTMLElement* element = static_cast<HTMLElement*>(node);
@@ -935,11 +940,14 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl
// This function maps from styling tags to CSS styles. Used for knowing which
// styling tags should be removed when toggling styles.
-bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration* style, HTMLElement* elem)
+bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style)
{
CSSMutableStyleDeclaration::const_iterator end = style->end();
for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
- switch ((*it).id()) {
+ const CSSProperty& property = *it;
+ // FIXME: This should probably be re-written to lookup the tagname in a
+ // hash and match against an expected property/value pair.
+ switch (property.id()) {
case CSSPropertyFontWeight:
// IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational
if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag))
@@ -953,20 +961,34 @@ bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration* style, HTMLE
// IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational
if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag))
return true;
+ break;
}
}
-
return false;
}
-void ApplyStyleCommand::removeHTMLStyleNode(HTMLElement *elem)
+void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
{
- // This node can be removed.
- // EDIT FIXME: This does not handle the case where the node
- // has attributes. But how often do people add attributes to <B> tags?
- // Not so often I think.
- ASSERT(elem);
- removeNodePreservingChildren(elem);
+ bool removeNode = false;
+
+ // Similar to isSpanWithoutAttributesOrUnstyleStyleSpan, but does not look for Apple-style-span.
+ NamedNodeMap* attributes = elem->attributes(true); // readonly
+ if (!attributes || attributes->isEmpty())
+ removeNode = true;
+ else if (attributes->length() == 1 && elem->hasAttribute(styleAttr)) {
+ // Remove the element even if it has just style='' (this might be redundantly checked later too)
+ CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
+ if (!inlineStyleDecl || inlineStyleDecl->isEmpty())
+ removeNode = true;
+ }
+
+ if (removeNode)
+ removeNodePreservingChildren(elem);
+ else {
+ HTMLElement* newSpanElement = replaceNodeWithSpanPreservingChildrenAndAttributes(elem);
+ ASSERT(newSpanElement && newSpanElement->inDocument());
+ elem = newSpanElement;
+ }
}
void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
@@ -1035,7 +1057,7 @@ void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLEl
}
// No need to serialize <foo style=""> if we just removed the last css property
- if (decl->length() == 0)
+ if (decl->isEmpty())
removeNodeAttribute(elem, styleAttr);
if (isSpanWithoutAttributesOrUnstyleStyleSpan(elem))
@@ -1116,7 +1138,7 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl
{
ASSERT(node);
- if (!style || !style->cssText().length())
+ if (!style || style->cssText().isEmpty())
return;
if (node->isTextNode()) {
@@ -1131,7 +1153,7 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl
HTMLElement *element = static_cast<HTMLElement *>(node);
StyleChange styleChange(style, Position(element, 0));
- if (styleChange.cssStyle().length() > 0) {
+ if (styleChange.cssStyle().length()) {
String cssText = styleChange.cssStyle();
CSSMutableStyleDeclaration *decl = element->inlineStyleDecl();
if (decl)
@@ -1203,7 +1225,7 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
ASSERT(end.isNotNull());
ASSERT(start.node()->inDocument());
ASSERT(end.node()->inDocument());
- ASSERT(Range::compareBoundaryPoints(start, end) <= 0);
+ ASSERT(comparePositions(start, end) <= 0);
RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
@@ -1228,9 +1250,13 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
Node* next = elem->traverseNextNode();
if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName()))
removeNodePreservingChildren(elem);
- if (isHTMLStyleNode(style.get(), elem))
- removeHTMLStyleNode(elem);
- else {
+
+ if (implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(elem, style.get()))
+ replaceWithSpanOrRemoveIfWithoutAttributes(elem);
+
+ // If the node was converted to a span, the span may still contain relevant
+ // styles which must be removed (e.g. <b style='font-weight: bold'>)
+ if (elem->inDocument()) {
removeHTMLFontStyle(style.get(), elem);
removeHTMLBidiEmbeddingStyle(style.get(), elem);
removeCSSStyle(style.get(), elem);
@@ -1239,14 +1265,14 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
if (s.node() == elem) {
// Since elem must have been fully selected, and it is at the start
// of the selection, it is clear we can set the new s offset to 0.
- ASSERT(s.m_offset <= caretMinOffset(s.node()));
+ ASSERT(s.deprecatedEditingOffset() <= caretMinOffset(s.node()));
s = Position(next, 0);
}
if (e.node() == elem) {
// Since elem must have been fully selected, and it is at the end
// of the selection, it is clear we can set the new e offset to
// the max range offset of prev.
- ASSERT(e.m_offset >= maxRangeOffset(e.node()));
+ ASSERT(e.deprecatedEditingOffset() >= maxRangeOffset(e.node()));
e = Position(prev, maxRangeOffset(prev));
}
}
@@ -1267,8 +1293,7 @@ bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, con
ASSERT(node->isElementNode());
Position pos = Position(node, node->childNodeCount()).upstream();
- return Range::compareBoundaryPoints(node, 0, start.node(), start.m_offset) >= 0 &&
- Range::compareBoundaryPoints(pos, end) <= 0;
+ return comparePositions(Position(node, 0), start) >= 0 && comparePositions(pos, end) <= 0;
}
bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
@@ -1277,8 +1302,8 @@ bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, c
ASSERT(node->isElementNode());
Position pos = Position(node, node->childNodeCount()).upstream();
- bool isFullyBeforeStart = Range::compareBoundaryPoints(pos, start) < 0;
- bool isFullyAfterEnd = Range::compareBoundaryPoints(node, 0, end.node(), end.m_offset) > 0;
+ bool isFullyBeforeStart = comparePositions(pos, start) < 0;
+ bool isFullyAfterEnd = comparePositions(Position(node, 0), end) > 0;
return isFullyBeforeStart || isFullyAfterEnd;
}
@@ -1286,11 +1311,11 @@ bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, c
bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
{
- if (start.node()->isTextNode() && start.m_offset > caretMinOffset(start.node()) && start.m_offset < caretMaxOffset(start.node())) {
- int endOffsetAdjustment = start.node() == end.node() ? start.m_offset : 0;
+ if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
+ int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
Text *text = static_cast<Text *>(start.node());
- splitTextNode(text, start.m_offset);
- updateStartEnd(Position(start.node(), 0), Position(end.node(), end.m_offset - endOffsetAdjustment));
+ splitTextNode(text, start.deprecatedEditingOffset());
+ updateStartEnd(Position(start.node(), 0), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
return true;
}
return false;
@@ -1298,15 +1323,15 @@ bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Po
bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
{
- if (end.node()->isTextNode() && end.m_offset > caretMinOffset(end.node()) && end.m_offset < caretMaxOffset(end.node())) {
+ if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
Text *text = static_cast<Text *>(end.node());
- splitTextNode(text, end.m_offset);
+ splitTextNode(text, end.deprecatedEditingOffset());
Node *prevNode = text->previousSibling();
ASSERT(prevNode);
Node *startNode = start.node() == end.node() ? prevNode : start.node();
ASSERT(startNode);
- updateStartEnd(Position(startNode, start.m_offset), Position(prevNode, caretMaxOffset(prevNode)));
+ updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode, caretMaxOffset(prevNode)));
return true;
}
return false;
@@ -1314,12 +1339,12 @@ bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Posi
bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
{
- if (start.node()->isTextNode() && start.m_offset > caretMinOffset(start.node()) && start.m_offset < caretMaxOffset(start.node())) {
- int endOffsetAdjustment = start.node() == end.node() ? start.m_offset : 0;
+ if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
+ int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
Text *text = static_cast<Text *>(start.node());
- splitTextNodeContainingElement(text, start.m_offset);
+ splitTextNodeContainingElement(text, start.deprecatedEditingOffset());
- updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.m_offset - endOffsetAdjustment));
+ updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
return true;
}
return false;
@@ -1327,15 +1352,15 @@ bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, c
bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
{
- if (end.node()->isTextNode() && end.m_offset > caretMinOffset(end.node()) && end.m_offset < caretMaxOffset(end.node())) {
+ if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
Text *text = static_cast<Text *>(end.node());
- splitTextNodeContainingElement(text, end.m_offset);
+ splitTextNodeContainingElement(text, end.deprecatedEditingOffset());
Node *prevNode = text->parent()->previousSibling()->lastChild();
ASSERT(prevNode);
Node *startNode = start.node() == end.node() ? prevNode : start.node();
ASSERT(startNode);
- updateStartEnd(Position(startNode, start.m_offset), Position(prevNode->parent(), prevNode->nodeIndex() + 1));
+ updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1));
return true;
}
return false;
@@ -1357,8 +1382,8 @@ static bool areIdenticalElements(Node *first, Node *second)
if (!firstElement->tagQName().matches(secondElement->tagQName()))
return false;
- NamedAttrMap *firstMap = firstElement->attributes();
- NamedAttrMap *secondMap = secondElement->attributes();
+ NamedNodeMap *firstMap = firstElement->attributes();
+ NamedNodeMap *secondMap = secondElement->attributes();
unsigned firstLength = firstMap->length();
@@ -1379,10 +1404,10 @@ static bool areIdenticalElements(Node *first, Node *second)
bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
{
Node *startNode = start.node();
- int startOffset = start.m_offset;
+ int startOffset = start.deprecatedEditingOffset();
if (isAtomicNode(start.node())) {
- if (start.m_offset != 0)
+ if (start.deprecatedEditingOffset() != 0)
return false;
// note: prior siblings could be unrendered elements. it's silly to miss the
@@ -1411,7 +1436,7 @@ bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start,
int startOffsetAdjustment = startChild->nodeIndex();
int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
- updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.m_offset + endOffsetAdjustment));
+ updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.deprecatedEditingOffset() + endOffsetAdjustment));
return true;
}
@@ -1421,7 +1446,7 @@ bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start,
bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
{
Node *endNode = end.node();
- int endOffset = end.m_offset;
+ int endOffset = end.deprecatedEditingOffset();
if (isAtomicNode(endNode)) {
if (endOffset < caretMaxOffset(endNode))
@@ -1451,7 +1476,7 @@ bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const
ASSERT(startNode);
int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
- updateStartEnd(Position(startNode, start.m_offset), Position(nextElement, endOffset));
+ updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(nextElement, endOffset));
return true;
}
@@ -1554,7 +1579,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style
}
}
- if (styleChange.cssStyle().length() > 0) {
+ if (styleChange.cssStyle().length()) {
RefPtr<Element> styleElement = createStyleSpanElement(document());
styleElement->setAttribute(styleAttr, styleChange.cssStyle());
surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
@@ -1607,9 +1632,9 @@ void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, co
Text *childText = static_cast<Text *>(child);
Text *nextText = static_cast<Text *>(next);
if (next == start.node())
- newStart = Position(childText, childText->length() + start.m_offset);
+ newStart = Position(childText, childText->length() + start.deprecatedEditingOffset());
if (next == end.node())
- newEnd = Position(childText, childText->length() + end.m_offset);
+ newEnd = Position(childText, childText->length() + end.deprecatedEditingOffset());
String textToMove = nextText->data();
insertTextIntoNode(childText, childText->length(), textToMove);
removeNode(next);
diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h
index a42225d..74fe605 100644
--- a/WebCore/editing/ApplyStyleCommand.h
+++ b/WebCore/editing/ApplyStyleCommand.h
@@ -62,8 +62,8 @@ private:
CSSMutableStyleDeclaration* style() const { return m_style.get(); }
// style-removal helpers
- bool isHTMLStyleNode(CSSMutableStyleDeclaration*, HTMLElement*);
- void removeHTMLStyleNode(HTMLElement*);
+ bool implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement*, CSSMutableStyleDeclaration*);
+ void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&);
void removeHTMLFontStyle(CSSMutableStyleDeclaration*, HTMLElement*);
void removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration*, HTMLElement*);
void removeCSSStyle(CSSMutableStyleDeclaration*, HTMLElement*);
diff --git a/WebCore/editing/BreakBlockquoteCommand.cpp b/WebCore/editing/BreakBlockquoteCommand.cpp
index 2a513a5..e3d66ba 100644
--- a/WebCore/editing/BreakBlockquoteCommand.cpp
+++ b/WebCore/editing/BreakBlockquoteCommand.cpp
@@ -26,12 +26,12 @@
#include "config.h"
#include "BreakBlockquoteCommand.h"
-#include "Element.h"
+#include "HTMLElement.h"
#include "HTMLNames.h"
+#include "RenderListItem.h"
#include "Text.h"
#include "VisiblePosition.h"
#include "htmlediting.h"
-#include "RenderListItem.h"
namespace WebCore {
@@ -56,42 +56,59 @@ void BreakBlockquoteCommand::doApply()
// be in the first node that we need to move (there are a few exceptions to this, see below).
Position pos = endingSelection().start().downstream();
- // startNode is the first node that we need to move to the new blockquote.
- Node* startNode = pos.node();
// Find the top-most blockquote from the start.
Element* topBlockquote = 0;
- for (Node *node = startNode->parentNode(); node; node = node->parentNode()) {
+ for (Node *node = pos.node()->parentNode(); node; node = node->parentNode()) {
if (isMailBlockquote(node))
topBlockquote = static_cast<Element*>(node);
}
if (!topBlockquote || !topBlockquote->parentNode())
return;
- // Insert a break after the top blockquote.
RefPtr<Element> breakNode = createBreakElement(document());
+
+ // If the position is at the beginning of the top quoted content, we don't need to break the quote.
+ // Instead, insert the break before the blockquote.
+ if (isFirstVisiblePositionInNode(visiblePos, topBlockquote)) {
+ insertNodeBefore(breakNode.get(), topBlockquote);
+ setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM));
+ rebalanceWhitespace();
+ return;
+ }
+
+ // Insert a break after the top blockquote.
insertNodeAfter(breakNode.get(), topBlockquote);
+ // If we're inserting the break at the end of the quoted content, we don't need to break the quote.
if (isLastVisiblePositionInNode(visiblePos, topBlockquote)) {
setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM));
- rebalanceWhitespace();
+ rebalanceWhitespace();
return;
}
// Don't move a line break just after the caret. Doing so would create an extra, empty paragraph
// in the new blockquote.
- if (lineBreakExistsAtPosition(visiblePos))
+ if (lineBreakExistsAtVisiblePosition(visiblePos))
pos = pos.next();
+ // Adjust the position so we don't split at the beginning of a quote.
+ while (isFirstVisiblePositionInNode(VisiblePosition(pos), nearestMailBlockquote(pos.node())))
+ pos = pos.previous();
+
+ // startNode is the first node that we need to move to the new blockquote.
+ Node* startNode = pos.node();
+
// Split at pos if in the middle of a text node.
if (startNode->isTextNode()) {
Text* textNode = static_cast<Text*>(startNode);
- if ((unsigned)pos.m_offset >= textNode->length()) {
+ if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) {
startNode = startNode->traverseNextNode();
ASSERT(startNode);
- } else if (pos.m_offset > 0)
- splitTextNode(textNode, pos.m_offset);
- } else if (pos.m_offset > 0) {
- startNode = startNode->traverseNextNode();
+ } else if (pos.deprecatedEditingOffset() > 0)
+ splitTextNode(textNode, pos.deprecatedEditingOffset());
+ } else if (pos.deprecatedEditingOffset() > 0) {
+ Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset());
+ startNode = childAtOffset ? childAtOffset : startNode->traverseNextNode();
ASSERT(startNode);
}
@@ -141,10 +158,7 @@ void BreakBlockquoteCommand::doApply()
moveNode = next;
}
- // Hold open startNode's original parent if we emptied it
if (!ancestors.isEmpty()) {
- addBlockPlaceholderIfNeeded(ancestors.first());
-
// Split the tree up the ancestor chain until the topBlockquote
// Throughout this loop, clonedParent is the clone of ancestor's parent.
// This is so we can clone ancestor's siblings and place the clones
@@ -162,6 +176,11 @@ void BreakBlockquoteCommand::doApply()
moveNode = next;
}
}
+
+ // If the startNode's original parent is now empty, remove it
+ Node* originalParent = ancestors.first();
+ if (!originalParent->hasChildNodes())
+ removeNode(originalParent);
}
// Make sure the cloned block quote renders.
diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp
index 9052582..89a0f8a 100644
--- a/WebCore/editing/CompositeEditCommand.cpp
+++ b/WebCore/editing/CompositeEditCommand.cpp
@@ -36,7 +36,7 @@
#include "Document.h"
#include "DocumentFragment.h"
#include "EditorInsertAction.h"
-#include "Element.h"
+#include "HTMLElement.h"
#include "HTMLNames.h"
#include "InlineTextBox.h"
#include "InsertIntoTextNodeCommand.h"
@@ -50,6 +50,7 @@
#include "RemoveCSSPropertyCommand.h"
#include "RemoveNodeCommand.h"
#include "RemoveNodePreservingChildrenCommand.h"
+#include "ReplaceNodeWithSpanCommand.h"
#include "ReplaceSelectionCommand.h"
#include "RenderBlock.h"
#include "RenderText.h"
@@ -157,7 +158,7 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi
// likewise for replaced elements, brs, etc.
Position p = rangeCompliantEquivalent(editingPosition);
Node* refChild = p.node();
- int offset = p.m_offset;
+ int offset = p.deprecatedEditingOffset();
if (canHaveChildrenForEditing(refChild)) {
Node* child = refChild->firstChild();
@@ -215,6 +216,20 @@ void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node)
prune(parent.release());
}
+HTMLElement* CompositeEditCommand::replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node> node)
+{
+ // It would also be possible to implement all of ReplaceNodeWithSpanCommand
+ // as a series of existing smaller edit commands. Someone who wanted to
+ // reduce the number of edit commands could do so here.
+ RefPtr<ReplaceNodeWithSpanCommand> command = ReplaceNodeWithSpanCommand::create(node);
+ applyCommandToComposite(command);
+ // Returning a raw pointer here is OK because the command is retained by
+ // applyCommandToComposite (thus retaining the span), and the span is also
+ // in the DOM tree, and thus alive whie it has a parent.
+ ASSERT(command->spanElement()->inDocument());
+ return command->spanElement();
+}
+
static bool hasARenderedDescendant(Node* node)
{
Node* n = node->firstChild();
@@ -327,13 +342,13 @@ Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
Node* tabSpan = tabSpanNode(pos.node());
- if (pos.m_offset <= caretMinOffset(pos.node()))
+ if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.node()))
return positionBeforeNode(tabSpan);
- if (pos.m_offset >= caretMaxOffset(pos.node()))
+ if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node()))
return positionAfterNode(tabSpan);
- splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.m_offset);
+ splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.deprecatedEditingOffset());
return positionBeforeNode(tabSpan);
}
@@ -392,7 +407,7 @@ void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position)
String text = textNode->data();
ASSERT(!text.isEmpty());
- int offset = position.m_offset;
+ int offset = position.deprecatedEditingOffset();
// If neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing.
if (!isWhitespace(text[offset])) {
offset--;
@@ -449,9 +464,9 @@ void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& positio
Position previous(previousVisiblePos.deepEquivalent());
if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.node()->isTextNode() && !previous.node()->hasTagName(brTag))
- replaceTextInNode(static_cast<Text*>(previous.node()), previous.m_offset, 1, nonBreakingSpaceString());
+ replaceTextInNode(static_cast<Text*>(previous.node()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.node()->isTextNode() && !position.node()->hasTagName(brTag))
- replaceTextInNode(static_cast<Text*>(position.node()), position.m_offset, 1, nonBreakingSpaceString());
+ replaceTextInNode(static_cast<Text*>(position.node()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
}
void CompositeEditCommand::rebalanceWhitespace()
@@ -534,7 +549,7 @@ void CompositeEditCommand::deleteInsignificantText(const Position& start, const
if (start.isNull() || end.isNull())
return;
- if (Range::compareBoundaryPoints(start, end) >= 0)
+ if (comparePositions(start, end) >= 0)
return;
Node* next;
@@ -542,8 +557,8 @@ void CompositeEditCommand::deleteInsignificantText(const Position& start, const
next = node->traverseNextNode();
if (node->isTextNode()) {
Text* textNode = static_cast<Text*>(node);
- int startOffset = node == start.node() ? start.m_offset : 0;
- int endOffset = node == end.node() ? end.m_offset : textNode->length();
+ int startOffset = node == start.node() ? start.deprecatedEditingOffset() : 0;
+ int endOffset = node == end.node() ? end.deprecatedEditingOffset() : textNode->length();
deleteInsignificantText(textNode, startOffset, endOffset);
}
if (node == end.node())
@@ -603,24 +618,18 @@ PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* cont
return 0;
}
-// Removes '\n's and brs that will collapse when content is inserted just before them.
-// FIXME: We shouldn't really have to remove placeholders, but removing them is a workaround for 9661.
-void CompositeEditCommand::removePlaceholderAt(const VisiblePosition& visiblePosition)
+// Assumes that the position is at a placeholder and does the removal without much checking.
+void CompositeEditCommand::removePlaceholderAt(const Position& p)
{
- if (visiblePosition.isNull())
+ ASSERT(lineBreakExistsAtPosition(p));
+
+ // We are certain that the position is at a line break, but it may be a br or a preserved newline.
+ if (p.anchorNode()->hasTagName(brTag)) {
+ removeNode(p.anchorNode());
return;
-
- Position p = visiblePosition.deepEquivalent().downstream();
- // If a br or '\n' is at the end of a block and not at the start of a paragraph,
- // then it is superfluous, so adding content before a br or '\n' that is at
- // the start of a paragraph will render it superfluous.
- // FIXME: This doesn't remove placeholders at the end of anonymous blocks.
- if (isEndOfBlock(visiblePosition) && isStartOfParagraph(visiblePosition)) {
- if (p.node()->hasTagName(brTag) && p.m_offset == 0)
- removeNode(p.node());
- else if (lineBreakExistsAtPosition(visiblePosition))
- deleteTextFromNode(static_cast<Text*>(p.node()), p.m_offset, 1);
}
+
+ deleteTextFromNode(static_cast<Text*>(p.anchorNode()), p.offsetInContainerNode(), 1);
}
PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position)
@@ -654,7 +663,7 @@ PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessar
// If there are no VisiblePositions in the same block as pos then
// upstreamStart will be outside the paragraph
- if (Range::compareBoundaryPoints(pos, upstreamStart) < 0)
+ if (comparePositions(pos, upstreamStart) < 0)
return 0;
// Perform some checks to see if we need to perform work in this function.
@@ -662,9 +671,9 @@ PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessar
// If the block is the root editable element, always move content to a new block,
// since it is illegal to modify attributes on the root editable element for editing.
if (upstreamStart.node() == editableRootForPosition(upstreamStart)) {
- // If the block is the root editable element and there is nothing insde of it, create a new
- // block but don't try and move content into it, since there's nothing to move.
- if (upstreamStart == upstreamEnd)
+ // If the block is the root editable element and it contains no visible content, create a new
+ // block but don't try and move content into it, since there's nothing for moveParagraphs to move.
+ if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.node()->renderer()))
return insertNewDefaultParagraphElementAt(upstreamStart);
} else if (isBlock(upstreamEnd.node())) {
if (!upstreamEnd.node()->isDescendantOf(upstreamStart.node())) {
@@ -750,12 +759,12 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap
VisiblePosition visibleStart = endingSelection().visibleStart();
VisiblePosition visibleEnd = endingSelection().visibleEnd();
- bool startAfterParagraph = Range::compareBoundaryPoints(visibleStart.deepEquivalent(), endOfParagraphToMove.deepEquivalent()) > 0;
- bool endBeforeParagraph = Range::compareBoundaryPoints(visibleEnd.deepEquivalent(), startOfParagraphToMove.deepEquivalent()) < 0;
+ bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
+ bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
if (!startAfterParagraph && !endBeforeParagraph) {
- bool startInParagraph = Range::compareBoundaryPoints(visibleStart.deepEquivalent(), startOfParagraphToMove.deepEquivalent()) >= 0;
- bool endInParagraph = Range::compareBoundaryPoints(visibleEnd.deepEquivalent(), endOfParagraphToMove.deepEquivalent()) <= 0;
+ bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
+ bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
startIndex = 0;
if (startInParagraph) {
@@ -782,7 +791,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap
// start and end can't be used directly to create a Range; they are "editing positions"
Position startRangeCompliant = rangeCompliantEquivalent(start);
Position endRangeCompliant = rangeCompliantEquivalent(end);
- RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.m_offset, endRangeCompliant.node(), endRangeCompliant.m_offset);
+ RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset());
// FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
// shouldn't matter though, since moved paragraphs will usually be quite small.
@@ -825,13 +834,13 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap
// expects this behavior).
else if (isBlock(node))
removeNodeAndPruneAncestors(node);
- else if (lineBreakExistsAtPosition(caretAfterDelete)) {
+ else if (lineBreakExistsAtVisiblePosition(caretAfterDelete)) {
// There is a preserved '\n' at caretAfterDelete.
Text* textNode = static_cast<Text*>(node);
if (textNode->length() == 1)
removeNodeAndPruneAncestors(node);
else
- deleteTextFromNode(textNode, position.m_offset, 1);
+ deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
}
}
@@ -856,8 +865,10 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap
setEndingSelection(destination);
applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, true, false, !preserveStyle, false, true));
- // Restore styles from an empty paragraph to the new empty paragraph.
- if (styleInEmptyParagraph)
+
+ // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
+ bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
+ if (styleInEmptyParagraph && selectionIsEmptyParagraph)
applyStyle(styleInEmptyParagraph.get());
if (preserveSelection && startIndex != -1) {
@@ -941,7 +952,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
setEndingSelection(VisibleSelection(atBR));
// If this is an empty paragraph there must be a line break here.
- if (!lineBreakExistsAtPosition(caret))
+ if (!lineBreakExistsAtVisiblePosition(caret))
return false;
Position caretPos(caret.deepEquivalent());
@@ -953,7 +964,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
removeNode(caretPos.node());
prune(beforeBR.node());
} else {
- ASSERT(caretPos.m_offset == 0);
+ ASSERT(caretPos.deprecatedEditingOffset() == 0);
Text* textNode = static_cast<Text*>(caretPos.node());
Node* parentNode = textNode->parentNode();
// The preserved newline must be the first thing in the node, since otherwise the previous
@@ -995,7 +1006,7 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi
// Don't insert outside an anchor if doing so would skip over a line break. It would
// probably be safe to move the line break so that we could still avoid the anchor here.
Position downstream(visiblePos.deepEquivalent().downstream());
- if (lineBreakExistsAtPosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor))
+ if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor))
return original;
result = positionAfterNode(enclosingAnchor);
@@ -1030,8 +1041,10 @@ PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, b
if (positionInParent != positionInNode)
applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parent()), node));
}
- if (splitAncestor)
- return splitTreeToNode(end, end->parent());
+ if (splitAncestor) {
+ splitElement(static_cast<Element*>(end), node);
+ return node->parent();
+ }
return node.release();
}
diff --git a/WebCore/editing/CompositeEditCommand.h b/WebCore/editing/CompositeEditCommand.h
index 4a3defd..2c6403e 100644
--- a/WebCore/editing/CompositeEditCommand.h
+++ b/WebCore/editing/CompositeEditCommand.h
@@ -33,6 +33,7 @@
namespace WebCore {
class CSSStyleDeclaration;
+class HTMLElement;
class Text;
class CompositeEditCommand : public EditCommand {
@@ -71,6 +72,7 @@ protected:
void removeNodeAttribute(PassRefPtr<Element>, const QualifiedName& attribute);
void removeChildrenInRange(PassRefPtr<Node>, unsigned from, unsigned to);
virtual void removeNode(PassRefPtr<Node>);
+ HTMLElement* replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node>);
void removeNodePreservingChildren(PassRefPtr<Node>);
void removeNodeAndPruneAncestors(PassRefPtr<Node>);
void prune(PassRefPtr<Node>);
@@ -89,7 +91,7 @@ protected:
PassRefPtr<Node> appendBlockPlaceholder(PassRefPtr<Element>);
PassRefPtr<Node> insertBlockPlaceholder(const Position&);
PassRefPtr<Node> addBlockPlaceholderIfNeeded(Element*);
- void removePlaceholderAt(const VisiblePosition&);
+ void removePlaceholderAt(const Position&);
PassRefPtr<Node> insertNewDefaultParagraphElementAt(const Position&);
diff --git a/WebCore/editing/DeleteButtonController.cpp b/WebCore/editing/DeleteButtonController.cpp
index c0775e3..725c01d 100644
--- a/WebCore/editing/DeleteButtonController.cpp
+++ b/WebCore/editing/DeleteButtonController.cpp
@@ -66,36 +66,75 @@ static bool isDeletableElement(const Node* node)
if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable())
return false;
- const int minimumWidth = 25;
- const int minimumHeight = 25;
- const unsigned minimumVisibleBorders = 3;
+ // In general we want to only draw the UI arround object of a certain area, but we still keep the min width/height to
+ // make sure we don't end up with very thin or very short elements getting the UI.
+ const int minimumArea = 2500;
+ const int minimumWidth = 48;
+ const int minimumHeight = 16;
+ const unsigned minimumVisibleBorders = 1;
RenderObject* renderer = node->renderer();
if (!renderer || !renderer->isBox())
return false;
+ // Disallow the body element since it isn't practical to delete, and the deletion UI would be clipped.
+ if (node->hasTagName(bodyTag))
+ return false;
+
+ // Disallow elements with any overflow clip, since the deletion UI would be clipped as well. <rdar://problem/6840161>
+ if (renderer->hasOverflowClip())
+ return false;
+
+ // Disallow Mail blockquotes since the deletion UI would get in the way of editing for these.
+ if (isMailBlockquote(node))
+ return false;
+
RenderBox* box = toRenderBox(renderer);
IntRect borderBoundingBox = box->borderBoundingBox();
if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight)
return false;
+ if ((borderBoundingBox.width() * borderBoundingBox.height()) < minimumArea)
+ return false;
+
if (renderer->isTable())
return true;
- if (node->hasTagName(ulTag) || node->hasTagName(olTag))
+ if (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(iframeTag))
return true;
if (renderer->isPositioned())
return true;
- // allow block elements (excluding table cells) that have some non-transparent borders
if (renderer->isRenderBlock() && !renderer->isTableCell()) {
RenderStyle* style = renderer->style();
- if (style && style->hasBorder()) {
- unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
- if (visibleBorders >= minimumVisibleBorders)
- return true;
- }
+ if (!style)
+ return false;
+
+ // Allow blocks that have background images
+ if (style->hasBackgroundImage() && style->backgroundImage()->canRender(1.0f))
+ return true;
+
+ // Allow blocks with a minimum number of non-transparent borders
+ unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
+ if (visibleBorders >= minimumVisibleBorders)
+ return true;
+
+ // Allow blocks that have a different background from it's parent
+ Node* parentNode = node->parentNode();
+ if (!parentNode)
+ return false;
+
+ RenderObject* parentRenderer = parentNode->renderer();
+ if (!parentRenderer)
+ return false;
+
+ RenderStyle* parentStyle = parentRenderer->style();
+ if (!parentStyle)
+ return false;
+
+ if (style->hasBackground() && (!parentStyle->hasBackground() || style->backgroundColor() != parentStyle->backgroundColor()))
+ return true;
}
return false;
@@ -288,7 +327,7 @@ void DeleteButtonController::enable()
// Determining if the element is deletable currently depends on style
// because whether something is editable depends on style, so we need
// to recalculate style before calling enclosingDeletableElement.
- m_frame->document()->updateRendering();
+ m_frame->document()->updateStyleIfNeeded();
show(enclosingDeletableElement(m_frame->selection()->selection()));
}
}
diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp
index 09288ee..5a0d8fc 100644
--- a/WebCore/editing/DeleteSelectionCommand.cpp
+++ b/WebCore/editing/DeleteSelectionCommand.cpp
@@ -26,6 +26,7 @@
#include "config.h"
#include "DeleteSelectionCommand.h"
+#include "CSSMutableStyleDeclaration.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "Editor.h"
@@ -79,6 +80,7 @@ DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDel
m_replace(replace),
m_expandForSpecialElements(expandForSpecialElements),
m_pruneStartBlockIfNecessary(false),
+ m_startsAtEmptyLine(false),
m_startBlock(0),
m_endBlock(0),
m_typingStyle(0),
@@ -94,6 +96,7 @@ DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection
m_replace(replace),
m_expandForSpecialElements(expandForSpecialElements),
m_pruneStartBlockIfNecessary(false),
+ m_startsAtEmptyLine(false),
m_selectionToDelete(selection),
m_startBlock(0),
m_endBlock(0),
@@ -135,11 +138,12 @@ void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end)
break;
// If we're going to expand to include the startSpecialContainer, it must be fully selected.
- if (startSpecialContainer && !endSpecialContainer && Range::compareBoundaryPoints(positionAfterNode(startSpecialContainer), end) > -1)
+
+ if (startSpecialContainer && !endSpecialContainer && comparePositions(positionAfterNode(startSpecialContainer), end) > -1)
break;
- // If we're going to expand to include the endSpecialContainer, it must be fully selected.
- if (endSpecialContainer && !startSpecialContainer && Range::compareBoundaryPoints(start, positionBeforeNode(endSpecialContainer)) > -1)
+ // If we're going to expand to include the endSpecialContainer, it must be fully selected.
+ if (endSpecialContainer && !startSpecialContainer && comparePositions(start, positionBeforeNode(endSpecialContainer)) > -1)
break;
if (startSpecialContainer && startSpecialContainer->isDescendantOf(endSpecialContainer))
@@ -195,7 +199,11 @@ void DeleteSelectionCommand::initializePositionData()
// selections that contain a whole number paragraphs plus a line break, since it is unclear to most users
// that such a selection actually ends at the start of the next paragraph. This matches TextEdit behavior
// for indented paragraphs.
- if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end) && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start))) {
+ // Only apply this rule if the endingSelection is a range selection. If it is a caret, then other operations have created
+ // the selection we're deleting (like the process of creating a selection to delete during a backspace), and the user isn't in the situation described above.
+ if (numEnclosingMailBlockquotes(start) != numEnclosingMailBlockquotes(end)
+ && isStartOfParagraph(visibleEnd) && isStartOfParagraph(VisiblePosition(start))
+ && endingSelection().isRange()) {
m_mergeBlocksAfterDelete = false;
m_pruneStartBlockIfNecessary = true;
}
@@ -299,7 +307,7 @@ bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
// Not a special-case delete per se, but we can detect that the merging of content between blocks
// should not be done.
if (upstreamStartIsBR && downstreamStartIsBR) {
- m_mergeBlocksAfterDelete = false;
+ m_startsAtEmptyLine = true;
m_endingPosition = m_downstreamEnd;
}
@@ -310,8 +318,8 @@ static void updatePositionForNodeRemoval(Node* node, Position& position)
{
if (position.isNull())
return;
- if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.m_offset)
- position = Position(position.node(), position.m_offset - 1);
+ if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.deprecatedEditingOffset())
+ position = Position(position.node(), position.deprecatedEditingOffset() - 1);
if (position.node() == node || position.node()->isDescendantOf(node))
position = positionBeforeNode(node);
}
@@ -377,9 +385,9 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position)
{
if (position.node() == node) {
- if (position.m_offset > offset + count)
- position = Position(position.node(), position.m_offset - count);
- else if (position.m_offset > offset)
+ if (position.deprecatedEditingOffset() > offset + count)
+ position = Position(position.node(), position.deprecatedEditingOffset() - count);
+ else if (position.deprecatedEditingOffset() > offset)
position = Position(position.node(), offset);
}
}
@@ -390,13 +398,14 @@ void DeleteSelectionCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned
updatePositionForTextRemoval(node.get(), offset, count, m_endingPosition);
updatePositionForTextRemoval(node.get(), offset, count, m_leadingWhitespace);
updatePositionForTextRemoval(node.get(), offset, count, m_trailingWhitespace);
+ updatePositionForTextRemoval(node.get(), offset, count, m_downstreamEnd);
CompositeEditCommand::deleteTextFromNode(node, offset, count);
}
void DeleteSelectionCommand::handleGeneralDelete()
{
- int startOffset = m_upstreamStart.m_offset;
+ int startOffset = m_upstreamStart.deprecatedEditingOffset();
Node* startNode = m_upstreamStart.node();
// Never remove the start block unless it's a table, in which case we won't merge content in.
@@ -425,13 +434,13 @@ void DeleteSelectionCommand::handleGeneralDelete()
if (!startNode->renderer() || (startOffset == 0 && m_downstreamEnd.atLastEditingPositionForNode())) {
// just delete
removeNode(startNode);
- } else if (m_downstreamEnd.m_offset - startOffset > 0) {
+ } else if (m_downstreamEnd.deprecatedEditingOffset() - startOffset > 0) {
if (startNode->isTextNode()) {
// in a text node that needs to be trimmed
Text* text = static_cast<Text*>(startNode);
- deleteTextFromNode(text, startOffset, m_downstreamEnd.m_offset - startOffset);
+ deleteTextFromNode(text, startOffset, m_downstreamEnd.deprecatedEditingOffset() - startOffset);
} else {
- removeChildrenInRange(startNode, startOffset, m_downstreamEnd.m_offset);
+ removeChildrenInRange(startNode, startOffset, m_downstreamEnd.deprecatedEditingOffset());
m_endingPosition = m_upstreamStart;
}
}
@@ -454,7 +463,7 @@ void DeleteSelectionCommand::handleGeneralDelete()
// handle deleting all nodes that are completely selected
while (node && node != m_downstreamEnd.node()) {
- if (Range::compareBoundaryPoints(Position(node.get(), 0), m_downstreamEnd) >= 0) {
+ if (comparePositions(Position(node.get(), 0), m_downstreamEnd) >= 0) {
// traverseNextSibling just blew past the end position, so stop deleting
node = 0;
} else if (!m_downstreamEnd.node()->isDescendantOf(node.get())) {
@@ -462,14 +471,14 @@ void DeleteSelectionCommand::handleGeneralDelete()
// if we just removed a node from the end container, update end position so the
// check above will work
if (node->parentNode() == m_downstreamEnd.node()) {
- ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.m_offset);
- m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.m_offset - 1);
+ ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.deprecatedEditingOffset());
+ m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.deprecatedEditingOffset() - 1);
}
removeNode(node.get());
node = nextNode.get();
} else {
Node* n = node->lastDescendant();
- if (m_downstreamEnd.node() == n && m_downstreamEnd.m_offset >= caretMaxOffset(n)) {
+ if (m_downstreamEnd.node() == n && m_downstreamEnd.deprecatedEditingOffset() >= caretMaxOffset(n)) {
removeNode(node.get());
node = 0;
} else
@@ -477,7 +486,7 @@ void DeleteSelectionCommand::handleGeneralDelete()
}
}
- if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.m_offset >= caretMinOffset(m_downstreamEnd.node())) {
+ if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.deprecatedEditingOffset() >= caretMinOffset(m_downstreamEnd.node())) {
if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.node())) {
// The node itself is fully selected, not just its contents. Delete it.
removeNode(m_downstreamEnd.node());
@@ -485,9 +494,8 @@ void DeleteSelectionCommand::handleGeneralDelete()
if (m_downstreamEnd.node()->isTextNode()) {
// in a text node that needs to be trimmed
Text *text = static_cast<Text *>(m_downstreamEnd.node());
- if (m_downstreamEnd.m_offset > 0) {
- deleteTextFromNode(text, 0, m_downstreamEnd.m_offset);
- m_downstreamEnd = Position(text, 0);
+ if (m_downstreamEnd.deprecatedEditingOffset() > 0) {
+ deleteTextFromNode(text, 0, m_downstreamEnd.deprecatedEditingOffset());
}
// Remove children of m_downstreamEnd.node() that come after m_upstreamStart.
// Don't try to remove children if m_upstreamStart was inside m_downstreamEnd.node()
@@ -504,7 +512,7 @@ void DeleteSelectionCommand::handleGeneralDelete()
if (n)
offset = n->nodeIndex() + 1;
}
- removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.m_offset);
+ removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.deprecatedEditingOffset());
m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
}
}
@@ -519,12 +527,12 @@ void DeleteSelectionCommand::fixupWhitespace()
if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter()) {
Text* textNode = static_cast<Text*>(m_leadingWhitespace.node());
ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
- replaceTextInNode(textNode, m_leadingWhitespace.m_offset, 1, nonBreakingSpaceString());
+ replaceTextInNode(textNode, m_leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
}
if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter()) {
Text* textNode = static_cast<Text*>(m_trailingWhitespace.node());
ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace());
- replaceTextInNode(textNode, m_trailingWhitespace.m_offset, 1, nonBreakingSpaceString());
+ replaceTextInNode(textNode, m_trailingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
}
}
@@ -537,7 +545,7 @@ void DeleteSelectionCommand::mergeParagraphs()
// Make sure that the ending position isn't inside the block we're about to prune.
m_endingPosition = m_downstreamEnd;
// We aren't going to merge into the start block, so remove it if it's empty.
- prune(m_upstreamStart.node());
+ prune(m_startBlock);
// Removing the start block during a deletion is usually an indication that we need
// a placeholder, but not in this case.
m_needPlaceholder = false;
@@ -553,12 +561,11 @@ void DeleteSelectionCommand::mergeParagraphs()
return;
// FIXME: The deletion algorithm shouldn't let this happen.
- if (Range::compareBoundaryPoints(m_upstreamStart, m_downstreamEnd) > 0)
+ if (comparePositions(m_upstreamStart, m_downstreamEnd) > 0)
return;
- // FIXME: Merging will always be unnecessary in this case, but we really bail here because this is a case where
- // deletion commonly fails to adjust its endpoints, which would cause the visible position comparison below to false negative.
- if (m_endBlock == m_startBlock)
+ // There's nothing to merge.
+ if (m_upstreamStart == m_downstreamEnd)
return;
VisiblePosition startOfParagraphToMove(m_downstreamEnd);
@@ -573,7 +580,7 @@ void DeleteSelectionCommand::mergeParagraphs()
}
// We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion.
- if (!mergeDestination.deepEquivalent().node() || !mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement())) {
+ if (!mergeDestination.deepEquivalent().node() || !mergeDestination.deepEquivalent().node()->isDescendantOf(m_upstreamStart.node()->enclosingBlockFlowElement()) || m_startsAtEmptyLine) {
insertNodeAt(createBreakElement(document()).get(), m_upstreamStart);
mergeDestination = VisiblePosition(m_upstreamStart);
}
@@ -588,8 +595,7 @@ void DeleteSelectionCommand::mergeParagraphs()
// The rule for merging into an empty block is: only do so if its farther to the right.
// FIXME: Consider RTL.
- // FIXME: handleSpecialCaseBRDelete prevents us from getting here in a case like <ul><li>foo<br><br></li></ul>^foo
- if (isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
+ if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) {
ASSERT(mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag));
removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node());
m_endingPosition = startOfParagraphToMove.deepEquivalent();
@@ -671,7 +677,7 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
if (m_typingStyle &&
isStartOfParagraph(visibleEnd) &&
isEndOfParagraph(visibleEnd) &&
- lineBreakExistsAtPosition(visibleEnd)) {
+ lineBreakExistsAtVisiblePosition(visibleEnd)) {
// Apply style to the placeholder that is now holding open the empty paragraph.
// This makes sure that the paragraph has the right height, and that the paragraph
// takes on the right style and retains it even if you move the selection away and
@@ -729,7 +735,7 @@ void DeleteSelectionCommand::doApply()
Position downstreamEnd = m_selectionToDelete.end().downstream();
m_needPlaceholder = isStartOfParagraph(m_selectionToDelete.visibleStart()) &&
isEndOfParagraph(m_selectionToDelete.visibleEnd()) &&
- !lineBreakExistsAtPosition(m_selectionToDelete.visibleEnd());
+ !lineBreakExistsAtVisiblePosition(m_selectionToDelete.visibleEnd());
if (m_needPlaceholder) {
// Don't need a placeholder when deleting a selection that starts just before a table
// and ends inside it (we do need placeholders to hold open empty cells, but that's
diff --git a/WebCore/editing/DeleteSelectionCommand.h b/WebCore/editing/DeleteSelectionCommand.h
index 640c549..c8872ef 100644
--- a/WebCore/editing/DeleteSelectionCommand.h
+++ b/WebCore/editing/DeleteSelectionCommand.h
@@ -72,6 +72,7 @@ private:
bool m_replace;
bool m_expandForSpecialElements;
bool m_pruneStartBlockIfNecessary;
+ bool m_startsAtEmptyLine;
// This data is transient and should be cleared at the end of the doApply function.
VisibleSelection m_selectionToDelete;
@@ -90,6 +91,7 @@ private:
RefPtr<Node> m_endRoot;
RefPtr<Node> m_startTableRow;
RefPtr<Node> m_endTableRow;
+ RefPtr<Node> m_temporaryPlaceholder;
};
} // namespace WebCore
diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp
index f44449b..2c303f9 100644
--- a/WebCore/editing/Editor.cpp
+++ b/WebCore/editing/Editor.cpp
@@ -29,7 +29,10 @@
#include "AXObjectCache.h"
#include "ApplyStyleCommand.h"
+#include "CharacterNames.h"
+#include "CreateLinkCommand.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
@@ -388,7 +391,7 @@ void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
if (AXObjectCache::accessibilityEnabled()) {
Node* node = endingSelection.start().node();
if (node)
- m_frame->document()->axObjectCache()->postNotification(node->renderer(), "AXValueChanged");
+ m_frame->document()->axObjectCache()->postNotification(node->renderer(), "AXValueChanged", false);
}
if (client())
@@ -1122,6 +1125,110 @@ int Editor::spellCheckerDocumentTag()
return client() ? client()->spellCheckerDocumentTag() : 0;
}
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+
+void Editor::uppercaseWord()
+{
+ if (client())
+ client()->uppercaseWord();
+}
+
+void Editor::lowercaseWord()
+{
+ if (client())
+ client()->lowercaseWord();
+}
+
+void Editor::capitalizeWord()
+{
+ if (client())
+ client()->capitalizeWord();
+}
+
+void Editor::showSubstitutionsPanel()
+{
+ if (!client()) {
+ LOG_ERROR("No NSSpellChecker");
+ return;
+ }
+
+ if (client()->substitutionsPanelIsShowing()) {
+ client()->showSubstitutionsPanel(false);
+ return;
+ }
+ client()->showSubstitutionsPanel(true);
+}
+
+bool Editor::substitutionsPanelIsShowing()
+{
+ if (!client())
+ return false;
+ return client()->substitutionsPanelIsShowing();
+}
+
+void Editor::toggleSmartInsertDelete()
+{
+ if (client())
+ client()->toggleSmartInsertDelete();
+}
+
+bool Editor::isAutomaticQuoteSubstitutionEnabled()
+{
+ return client() && client()->isAutomaticQuoteSubstitutionEnabled();
+}
+
+void Editor::toggleAutomaticQuoteSubstitution()
+{
+ if (client())
+ client()->toggleAutomaticQuoteSubstitution();
+}
+
+bool Editor::isAutomaticLinkDetectionEnabled()
+{
+ return client() && client()->isAutomaticLinkDetectionEnabled();
+}
+
+void Editor::toggleAutomaticLinkDetection()
+{
+ if (client())
+ client()->toggleAutomaticLinkDetection();
+}
+
+bool Editor::isAutomaticDashSubstitutionEnabled()
+{
+ return client() && client()->isAutomaticDashSubstitutionEnabled();
+}
+
+void Editor::toggleAutomaticDashSubstitution()
+{
+ if (client())
+ client()->toggleAutomaticDashSubstitution();
+}
+
+bool Editor::isAutomaticTextReplacementEnabled()
+{
+ return client() && client()->isAutomaticTextReplacementEnabled();
+}
+
+void Editor::toggleAutomaticTextReplacement()
+{
+ if (client())
+ client()->toggleAutomaticTextReplacement();
+}
+
+bool Editor::isAutomaticSpellingCorrectionEnabled()
+{
+ return client() && client()->isAutomaticSpellingCorrectionEnabled();
+}
+
+void Editor::toggleAutomaticSpellingCorrection()
+{
+ if (client())
+ client()->toggleAutomaticSpellingCorrection();
+}
+
+#endif
+
bool Editor::shouldEndEditing(Range* range)
{
return client() && client()->shouldEndEditing(range);
@@ -1197,7 +1304,7 @@ void Editor::setBaseWritingDirection(WritingDirection direction)
if (direction == NaturalWritingDirection)
return;
static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl");
- frame()->document()->updateRendering();
+ frame()->document()->updateStyleIfNeeded();
return;
}
@@ -1290,9 +1397,9 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
TypingCommand::insertText(m_frame->document(), text, true, true);
Node* baseNode = m_frame->selection()->base().node();
- unsigned baseOffset = m_frame->selection()->base().m_offset;
+ unsigned baseOffset = m_frame->selection()->base().deprecatedEditingOffset();
Node* extentNode = m_frame->selection()->extent().node();
- unsigned extentOffset = m_frame->selection()->extent().m_offset;
+ unsigned extentOffset = m_frame->selection()->extent().deprecatedEditingOffset();
if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
m_compositionNode = static_cast<Text*>(baseNode);
@@ -1344,7 +1451,7 @@ void Editor::learnSpelling()
client()->learnWord(text);
}
-static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll)
+static String findFirstMisspellingInRange(EditorClient* client, Range* searchRange, int& firstMisspellingOffset, bool markAll, RefPtr<Range>& firstMisspellingRange)
{
ASSERT_ARG(client, client);
ASSERT_ARG(searchRange, searchRange);
@@ -1378,23 +1485,24 @@ static String findFirstMisspellingInRange(EditorClient* client, Range* searchRan
if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < len && misspellingLength <= len && misspellingLocation + misspellingLength <= len) {
- // Remember first-encountered misspelling and its offset
+ // Compute range of misspelled word
+ RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength);
+
+ // Remember first-encountered misspelling and its offset.
if (!firstMisspelling) {
firstMisspellingOffset = currentChunkOffset + misspellingLocation;
firstMisspelling = String(chars + misspellingLocation, misspellingLength);
+ firstMisspellingRange = misspellingRange;
}
-
- // Mark this instance if we're marking all instances. Otherwise bail out because we found the first one.
- if (!markAll)
- break;
-
- // Compute range of misspelled word
- RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingLocation, misspellingLength);
-
- // Store marker for misspelled word
+
+ // Store marker for misspelled word.
ExceptionCode ec = 0;
misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
ASSERT(ec == 0);
+
+ // Bail out if we're marking only the first misspelling, and not all instances.
+ if (!markAll)
+ break;
}
}
@@ -1592,17 +1700,18 @@ static String findFirstMisspellingOrBadGrammarInRange(EditorClient* client, Rang
unsigned grammarDetailIndex = 0;
Vector<TextCheckingResult> results;
- client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results);
+ uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
for (unsigned i = 0; i < results.size(); i++) {
const TextCheckingResult* result = &results[i];
- if (result->resultType == 1 && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
+ if (result->type == TextCheckingTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) {
ASSERT(result->length > 0 && result->location >= 0);
spellingLocation = result->location;
misspelledWord = paragraphString.substring(result->location, result->length);
ASSERT(misspelledWord.length() != 0);
break;
- } else if (checkGrammar && result->resultType == 2 && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
+ } else if (checkGrammar && result->type == TextCheckingTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) {
ASSERT(result->length > 0 && result->location >= 0);
// We can't stop after the first grammar result, since there might still be a spelling result after
// it begins but before the first detail in it, but we can stop if we find a second grammar result.
@@ -1696,7 +1805,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
return;
Position rangeCompliantPosition = rangeCompliantEquivalent(position);
- spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.m_offset, ec);
+ spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.deprecatedEditingOffset(), ec);
startedWithSelection = false; // won't need to wrap
}
@@ -1745,8 +1854,8 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
grammarPhraseOffset = foundOffset;
}
#else
- String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
-
+ RefPtr<Range> firstMisspellingRange;
+ String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange);
String badGrammarPhrase;
#ifndef BUILDING_ON_TIGER
@@ -1785,7 +1894,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
grammarPhraseOffset = foundOffset;
}
#else
- misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false);
+ misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false, firstMisspellingRange);
#ifndef BUILDING_ON_TIGER
grammarSearchRange = spellingSearchRange->cloneRange(ec);
@@ -1961,11 +2070,12 @@ static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* cli
return guesses;
Vector<TextCheckingResult> results;
- client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results);
+ uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ client->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
for (unsigned i = 0; i < results.size(); i++) {
const TextCheckingResult* result = &results[i];
- if (result->resultType == 1 && result->location == rangeStartOffset && result->length == rangeLength) {
+ if (result->type == TextCheckingTypeSpelling && result->location == rangeStartOffset && result->length == rangeLength) {
String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength);
ASSERT(misspelledWord.length() != 0);
client->getGuessesForWord(misspelledWord, guesses);
@@ -1980,7 +2090,7 @@ static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* cli
for (unsigned i = 0; i < results.size(); i++) {
const TextCheckingResult* result = &results[i];
- if (result->resultType == 2 && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) {
+ if (result->type == TextCheckingTypeGrammar && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) {
for (unsigned j = 0; j < result->details.size(); j++) {
const GrammarDetail* detail = &result->details[j];
ASSERT(detail->length > 0 && detail->location >= 0);
@@ -2049,21 +2159,58 @@ bool Editor::spellingPanelIsShowing()
void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
{
- if (!isContinuousSpellCheckingEnabled())
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ bool markSpelling = isContinuousSpellCheckingEnabled();
+ bool markGrammar = markSpelling && isGrammarCheckingEnabled();
+ bool performTextCheckingReplacements = isAutomaticQuoteSubstitutionEnabled()
+ || isAutomaticLinkDetectionEnabled()
+ || isAutomaticDashSubstitutionEnabled()
+ || isAutomaticTextReplacementEnabled()
+ || (markSpelling && isAutomaticSpellingCorrectionEnabled());
+ if (!markSpelling && !performTextCheckingReplacements)
return;
-#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary));
- if (isGrammarCheckingEnabled()) {
+ if (markGrammar) {
VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p));
- markMisspellingsAndBadGrammar(adjacentWords, true, selectedSentence);
+ markAllMisspellingsAndBadGrammarInRanges(true, adjacentWords.toNormalizedRange().get(), true, selectedSentence.toNormalizedRange().get(), performTextCheckingReplacements);
} else {
- markMisspellingsAndBadGrammar(adjacentWords, false, adjacentWords);
+ markAllMisspellingsAndBadGrammarInRanges(markSpelling, adjacentWords.toNormalizedRange().get(), false, adjacentWords.toNormalizedRange().get(), performTextCheckingReplacements);
}
#else
+ if (!isContinuousSpellCheckingEnabled())
+ return;
+
// Check spelling of one word
- markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)));
+ RefPtr<Range> misspellingRange;
+ markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)), misspellingRange);
+
+ // Autocorrect the misspelled word.
+ if (misspellingRange == 0)
+ return;
+ // Get the misspelled word.
+ const String misspelledWord = plainText(misspellingRange.get());
+ String autocorrectedString = client()->getAutoCorrectSuggestionForMisspelledWord(misspelledWord);
+
+ // If autocorrected word is non empty, replace the misspelled word by this word.
+ if (!autocorrectedString.isEmpty()) {
+ VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM);
+ if (newSelection != frame()->selection()->selection()) {
+ if (!frame()->shouldChangeSelection(newSelection))
+ return;
+ frame()->selection()->setSelection(newSelection);
+ }
+
+ if (!frame()->editor()->shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertActionTyped))
+ return;
+ frame()->editor()->replaceSelectionWithText(autocorrectedString, false, true);
+
+ // Reset the charet one character further.
+ frame()->selection()->moveTo(frame()->selection()->end());
+ frame()->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity);
+ }
+
if (!isGrammarCheckingEnabled())
return;
@@ -2072,12 +2219,12 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
#endif
}
-static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange)
+static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange, RefPtr<Range>& firstMisspellingRange)
{
// Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter";
// all we need to do is mark every instance.
int ignoredOffset;
- findFirstMisspellingInRange(client, searchRange, ignoredOffset, true);
+ findFirstMisspellingInRange(client, searchRange, ignoredOffset, true, firstMisspellingRange);
}
#ifndef BUILDING_ON_TIGER
@@ -2091,7 +2238,7 @@ static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange)
}
#endif
-static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling)
+static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling, RefPtr<Range>& firstMisspellingRange)
{
// This function is called with a selection already expanded to word boundaries.
// Might be nice to assert that here.
@@ -2110,12 +2257,26 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection&
if (!editableNode || !editableNode->isContentEditable())
return;
+ // Ascend the DOM tree to find a "spellcheck" attribute.
+ // When we find a "spellcheck" attribute, retrieve its value and exit if its value is "false".
+ const Node* node = editor->frame()->document()->focusedNode();
+ while (node) {
+ if (node->isElementNode()) {
+ const WebCore::AtomicString& value = static_cast<const Element*>(node)->getAttribute(spellcheckAttr);
+ if (equalIgnoringCase(value, "true"))
+ break;
+ if (equalIgnoringCase(value, "false"))
+ return;
+ }
+ node = node->parent();
+ }
+
// Get the spell checker if it is available
if (!editor->client())
return;
if (checkSpelling)
- markAllMisspellingsInRange(editor->client(), searchRange.get());
+ markAllMisspellingsInRange(editor->client(), searchRange.get(), firstMisspellingRange);
else {
#ifdef BUILDING_ON_TIGER
ASSERT_NOT_REACHED();
@@ -2126,15 +2287,16 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection&
}
}
-void Editor::markMisspellings(const VisibleSelection& selection)
+void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange)
{
- markMisspellingsOrBadGrammar(this, selection, true);
+ markMisspellingsOrBadGrammar(this, selection, true, firstMisspellingRange);
}
void Editor::markBadGrammar(const VisibleSelection& selection)
{
#ifndef BUILDING_ON_TIGER
- markMisspellingsOrBadGrammar(this, selection, false);
+ RefPtr<Range> firstMisspellingRange;
+ markMisspellingsOrBadGrammar(this, selection, false, firstMisspellingRange);
#else
UNUSED_PARAM(selection);
#endif
@@ -2142,11 +2304,19 @@ void Editor::markBadGrammar(const VisibleSelection& selection)
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
-static void markAllMisspellingsAndBadGrammarInRanges(EditorClient* client, Range *spellingRange, bool markGrammar, Range *grammarRange)
+static inline 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 6853027 is fixed and text checking can do this for us.
+ return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim;
+}
+
+void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range* spellingRange, bool markGrammar, Range* grammarRange, bool performTextCheckingReplacements)
{
// This function is called with selections already expanded to word boundaries.
- ExceptionCode ec;
- if (!client || !spellingRange || (markGrammar && !grammarRange))
+ ExceptionCode ec = 0;
+ if (!client() || !spellingRange || (markGrammar && !grammarRange))
return;
// If we're not in an editable node, bail.
@@ -2159,42 +2329,188 @@ static void markAllMisspellingsAndBadGrammarInRanges(EditorClient* client, Range
int spellingRangeEndOffset = 0;
int grammarRangeStartOffset = 0;
int grammarRangeEndOffset = 0;
+ int offsetDueToReplacement = 0;
+ int paragraphLength = 0;
+ int selectionOffset = 0;
+ int ambiguousBoundaryOffset = -1;
+ bool selectionChanged = false;
+ bool restoreSelectionAfterChange = false;
+ bool adjustSelectionForParagraphBoundaries = false;
String paragraphString;
+ RefPtr<Range> paragraphRange;
if (markGrammar) {
// The spelling range should be contained in the paragraph-aligned extension of the grammar range.
- RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString);
+ paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString);
RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition());
spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get());
grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange);
} else {
- RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString);
+ paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString);
}
spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange);
- if (paragraphString.length() == 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset)))
+ paragraphLength = paragraphString.length();
+ if (paragraphLength <= 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset)))
return;
+ if (performTextCheckingReplacements) {
+ if (m_frame->selection()->selectionType() == VisibleSelection::CaretSelection) {
+ // Attempt to save the caret position so we can restore it later if needed
+ RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), paragraphRange->startPosition());
+ Position caretPosition = m_frame->selection()->end();
+ offsetAsRange->setEnd(caretPosition.containerNode(), caretPosition.computeOffsetInContainerNode(), ec);
+ if (!ec) {
+ selectionOffset = TextIterator::rangeLength(offsetAsRange.get());
+ restoreSelectionAfterChange = true;
+ if (selectionOffset > 0 && (selectionOffset > paragraphLength || paragraphString[selectionOffset - 1] == newlineCharacter))
+ adjustSelectionForParagraphBoundaries = true;
+ if (selectionOffset > 0 && selectionOffset <= paragraphLength && isAmbiguousBoundaryCharacter(paragraphString[selectionOffset - 1]))
+ ambiguousBoundaryOffset = selectionOffset - 1;
+ }
+ }
+ }
+
Vector<TextCheckingResult> results;
- client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), markGrammar, results);
+ uint64_t checkingTypes = 0;
+ if (markSpelling)
+ checkingTypes |= TextCheckingTypeSpelling;
+ if (markGrammar)
+ checkingTypes |= TextCheckingTypeGrammar;
+ if (performTextCheckingReplacements) {
+ if (isAutomaticLinkDetectionEnabled())
+ checkingTypes |= TextCheckingTypeLink;
+ if (isAutomaticQuoteSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeQuote;
+ if (isAutomaticDashSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeDash;
+ if (isAutomaticTextReplacementEnabled())
+ checkingTypes |= TextCheckingTypeReplacement;
+ if (markSpelling && isAutomaticSpellingCorrectionEnabled())
+ checkingTypes |= TextCheckingTypeCorrection;
+ }
+ client()->checkTextOfParagraph(paragraphString.characters(), paragraphLength, checkingTypes, results);
for (unsigned i = 0; i < results.size(); i++) {
const TextCheckingResult* result = &results[i];
- if (result->resultType == 1 && result->location >= spellingRangeStartOffset && result->location + result->length <= spellingRangeEndOffset) {
- ASSERT(result->length > 0 && result->location >= 0);
- RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, result->location - spellingRangeStartOffset, result->length);
+ int resultLocation = result->location + offsetDueToReplacement;
+ int resultLength = result->length;
+ if (markSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingRangeStartOffset && resultLocation + resultLength <= spellingRangeEndOffset) {
+ ASSERT(resultLength > 0 && resultLocation >= 0);
+ RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, resultLocation - spellingRangeStartOffset, resultLength);
misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
- } else if (markGrammar && result->resultType == 2 && result->location < grammarRangeEndOffset && result->location + result->length > grammarRangeStartOffset) {
- ASSERT(result->length > 0 && result->location >= 0);
+ } else if (markGrammar && result->type == TextCheckingTypeGrammar && resultLocation < grammarRangeEndOffset && resultLocation + resultLength > grammarRangeStartOffset) {
+ ASSERT(resultLength > 0 && resultLocation >= 0);
for (unsigned j = 0; j < result->details.size(); j++) {
const GrammarDetail* detail = &result->details[j];
ASSERT(detail->length > 0 && detail->location >= 0);
- if (result->location + detail->location >= grammarRangeStartOffset && result->location + detail->location + detail->length <= grammarRangeEndOffset) {
- RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarRange, result->location + detail->location - grammarRangeStartOffset, detail->length);
+ if (resultLocation + detail->location >= grammarRangeStartOffset && resultLocation + detail->location + detail->length <= grammarRangeEndOffset) {
+ RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarRange, resultLocation + detail->location - grammarRangeStartOffset, detail->length);
grammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
}
}
+ } else if (performTextCheckingReplacements && resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingRangeStartOffset &&
+ (result->type == TextCheckingTypeLink
+ || result->type == TextCheckingTypeQuote
+ || result->type == TextCheckingTypeDash
+ || result->type == TextCheckingTypeReplacement
+ || result->type == TextCheckingTypeCorrection)) {
+ // In this case the result range just has to touch the spelling range, so we can handle replacing non-word text such as punctuation.
+ ASSERT(resultLength > 0 && resultLocation >= 0);
+ int replacementLength = result->replacement.length();
+ bool doReplacement = (replacementLength > 0);
+ RefPtr<Range> rangeToReplace = TextIterator::subrange(paragraphRange.get(), resultLocation, resultLength);
+ VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM);
+
+ // avoid correcting text after an ambiguous boundary character has been typed
+ // FIXME: this is required until 6853027 is fixed and text checking can do this for us
+ if (ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset)
+ doReplacement = false;
+
+ // adding links should be done only immediately after they are typed
+ if (result->type == TextCheckingTypeLink && selectionOffset > resultLocation + resultLength + 1)
+ doReplacement = false;
+
+ // Don't correct spelling in an already-corrected word.
+ if (doReplacement && result->type == TextCheckingTypeCorrection) {
+ Node* node = rangeToReplace->startContainer();
+ int startOffset = rangeToReplace->startOffset();
+ int endOffset = startOffset + replacementLength;
+ Vector<DocumentMarker> markers = node->document()->markersForNode(node);
+ size_t markerCount = markers.size();
+ for (size_t i = 0; i < markerCount; ++i) {
+ const DocumentMarker& marker = markers[i];
+ if (marker.type == DocumentMarker::Replacement && static_cast<int>(marker.startOffset) < endOffset && static_cast<int>(marker.endOffset) > startOffset) {
+ doReplacement = false;
+ break;
+ }
+ if (static_cast<int>(marker.startOffset) >= endOffset)
+ break;
+ }
+ }
+ if (doReplacement && selectionToReplace != m_frame->selection()->selection()) {
+ if (m_frame->shouldChangeSelection(selectionToReplace)) {
+ m_frame->selection()->setSelection(selectionToReplace);
+ selectionChanged = true;
+ } else {
+ doReplacement = false;
+ }
+ }
+ if (doReplacement) {
+ if (result->type == TextCheckingTypeLink) {
+ restoreSelectionAfterChange = false;
+ if (canEditRichly())
+ applyCommand(CreateLinkCommand::create(m_frame->document(), result->replacement));
+ } else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
+ String replacedString;
+ if (result->type == TextCheckingTypeCorrection)
+ replacedString = plainText(rangeToReplace.get());
+ replaceSelectionWithText(result->replacement, false, false);
+ spellingRangeEndOffset += replacementLength - resultLength;
+ offsetDueToReplacement += replacementLength - resultLength;
+ if (resultLocation < selectionOffset)
+ selectionOffset += replacementLength - resultLength;
+ if (result->type == TextCheckingTypeCorrection) {
+ // Add a marker so that corrections can easily be undone and won't be re-corrected.
+ RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength);
+ replacedRange->startContainer()->document()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString);
+ }
+ }
+ }
}
}
+
+ if (selectionChanged) {
+ // Restore the caret position if we have made any replacements
+ setEnd(paragraphRange.get(), endOfParagraph(startOfNextParagraph(paragraphRange->startPosition())));
+ int newLength = TextIterator::rangeLength(paragraphRange.get());
+ if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= newLength) {
+ RefPtr<Range> selectionRange = TextIterator::subrange(paragraphRange.get(), 0, selectionOffset);
+ m_frame->selection()->moveTo(selectionRange->endPosition(), DOWNSTREAM);
+ if (adjustSelectionForParagraphBoundaries)
+ m_frame->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity);
+ } else {
+ // If this fails for any reason, the fallback is to go one position beyond the last replacement
+ m_frame->selection()->moveTo(m_frame->selection()->end());
+ m_frame->selection()->modify(SelectionController::MOVE, SelectionController::FORWARD, CharacterGranularity);
+ }
+ }
+}
+
+void Editor::changeBackToReplacedString(const String& replacedString)
+{
+ if (replacedString.isEmpty())
+ return;
+
+ RefPtr<Range> selection = selectedRange();
+ if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted))
+ return;
+
+ String paragraphString;
+ int selectionOffset;
+ RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(selection.get(), selectionOffset, paragraphString);
+ replaceSelectionWithText(replacedString, false, false);
+ RefPtr<Range> changedRange = TextIterator::subrange(paragraphRange.get(), selectionOffset, replacedString.length());
+ changedRange->startContainer()->document()->addMarker(changedRange.get(), DocumentMarker::Replacement, String());
}
#endif
@@ -2204,9 +2520,10 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
if (!isContinuousSpellCheckingEnabled())
return;
- markAllMisspellingsAndBadGrammarInRanges(client(), spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get());
+ markAllMisspellingsAndBadGrammarInRanges(true, spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get(), false);
#else
- markMisspellings(spellingSelection);
+ RefPtr<Range> firstMisspellingRange;
+ markMisspellings(spellingSelection, firstMisspellingRange);
if (markGrammar)
markBadGrammar(grammarSelection);
#endif
@@ -2269,13 +2586,13 @@ bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selecti
if (end.node() != m_compositionNode)
return false;
- if (static_cast<unsigned>(start.m_offset) < m_compositionStart)
+ if (static_cast<unsigned>(start.deprecatedEditingOffset()) < m_compositionStart)
return false;
- if (static_cast<unsigned>(end.m_offset) > m_compositionEnd)
+ if (static_cast<unsigned>(end.deprecatedEditingOffset()) > m_compositionEnd)
return false;
- selectionStart = start.m_offset - m_compositionStart;
- selectionEnd = start.m_offset - m_compositionEnd;
+ selectionStart = start.deprecatedEditingOffset() - m_compositionStart;
+ selectionEnd = start.deprecatedEditingOffset() - m_compositionEnd;
return true;
}
diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h
index ce8fa0f..67a4b59 100644
--- a/WebCore/editing/Editor.h
+++ b/WebCore/editing/Editor.h
@@ -197,9 +197,29 @@ public:
Vector<String> guessesForUngrammaticalSelection();
Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical);
void markMisspellingsAfterTypingToPosition(const VisiblePosition&);
- void markMisspellings(const VisibleSelection&);
+ void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange);
void markBadGrammar(const VisibleSelection&);
void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection);
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ void uppercaseWord();
+ void lowercaseWord();
+ void capitalizeWord();
+ void showSubstitutionsPanel();
+ bool substitutionsPanelIsShowing();
+ void toggleSmartInsertDelete();
+ bool isAutomaticQuoteSubstitutionEnabled();
+ void toggleAutomaticQuoteSubstitution();
+ bool isAutomaticLinkDetectionEnabled();
+ void toggleAutomaticLinkDetection();
+ bool isAutomaticDashSubstitutionEnabled();
+ void toggleAutomaticDashSubstitution();
+ bool isAutomaticTextReplacementEnabled();
+ void toggleAutomaticTextReplacement();
+ bool isAutomaticSpellingCorrectionEnabled();
+ void toggleAutomaticSpellingCorrection();
+ void markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range* spellingRange, bool markGrammar, Range* grammarRange, bool performTextCheckingReplacements);
+ void changeBackToReplacedString(const String& replacedString);
+#endif
void advanceToNextMisspelling(bool startBeforeSelection = false);
void showSpellingGuessPanel();
bool spellingPanelIsShowing();
diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp
index 0090df7..5a189d4 100644
--- a/WebCore/editing/EditorCommand.cpp
+++ b/WebCore/editing/EditorCommand.cpp
@@ -27,6 +27,7 @@
#include "config.h"
#include "AtomicString.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "CreateLinkCommand.h"
diff --git a/WebCore/editing/FormatBlockCommand.cpp b/WebCore/editing/FormatBlockCommand.cpp
index 88169be..d92f365 100644
--- a/WebCore/editing/FormatBlockCommand.cpp
+++ b/WebCore/editing/FormatBlockCommand.cpp
@@ -124,7 +124,7 @@ void FormatBlockCommand::doApply()
appendNode(placeholder, blockNode);
VisiblePosition destination(Position(placeholder.get(), 0));
- if (paragraphStart == paragraphEnd && !lineBreakExistsAtPosition(paragraphStart)) {
+ if (paragraphStart == paragraphEnd && !lineBreakExistsAtVisiblePosition(paragraphStart)) {
setEndingSelection(destination);
return;
}
diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp
index 9444b11..0f9b106 100644
--- a/WebCore/editing/IndentOutdentCommand.cpp
+++ b/WebCore/editing/IndentOutdentCommand.cpp
@@ -200,7 +200,7 @@ void IndentOutdentCommand::outdentParagraph()
VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);
Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote);
- if (!enclosingNode)
+ if (!enclosingNode || !isContentEditable(enclosingNode->parentNode())) // We can't outdent if there is no place to go!
return;
// Use InsertListCommand to remove the selection from the list
@@ -216,11 +216,24 @@ void IndentOutdentCommand::outdentParagraph()
// The selection is inside a blockquote
VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0));
VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock);
- VisiblePosition endOfEnclosingBlock = endOfBlock(positionInEnclosingBlock);
+ VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(Position(enclosingNode, enclosingNode->childNodeCount()));
+ VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock);
if (visibleStartOfParagraph == startOfEnclosingBlock &&
visibleEndOfParagraph == endOfEnclosingBlock) {
// The blockquote doesn't contain anything outside the paragraph, so it can be totally removed.
+ Node* splitPoint = enclosingNode->nextSibling();
removeNodePreservingChildren(enclosingNode);
+ // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've
+ // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true
+ if (splitPoint) {
+ if (Node* splitPointParent = splitPoint->parentNode()) {
+ if (isIndentBlockquote(splitPointParent)
+ && !isIndentBlockquote(splitPoint)
+ && isContentEditable(splitPointParent->parentNode())) // We can't outdent if there is no place to go!
+ splitElement(static_cast<Element*>(splitPointParent), splitPoint);
+ }
+ }
+
updateLayout();
visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent());
visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent());
@@ -228,6 +241,7 @@ void IndentOutdentCommand::outdentParagraph()
insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent());
if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph))
insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent());
+
return;
}
Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph);
diff --git a/WebCore/editing/InsertLineBreakCommand.cpp b/WebCore/editing/InsertLineBreakCommand.cpp
index a5c588d..f020459 100644
--- a/WebCore/editing/InsertLineBreakCommand.cpp
+++ b/WebCore/editing/InsertLineBreakCommand.cpp
@@ -28,8 +28,8 @@
#include "CSSMutableStyleDeclaration.h"
#include "Document.h"
-#include "Element.h"
#include "Frame.h"
+#include "HTMLElement.h"
#include "HTMLNames.h"
#include "Range.h"
#include "RenderObject.h"
@@ -108,7 +108,7 @@ void InsertLineBreakCommand::doApply()
// FIXME: Need to merge text nodes when inserting just after or before text.
- if (isEndOfParagraph(caret) && !lineBreakExistsAtPosition(caret)) {
+ if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) {
bool needExtraLineBreak = !pos.node()->hasTagName(hrTag) && !pos.node()->hasTagName(tableTag);
insertNodeAt(nodeToInsert.get(), pos);
@@ -118,7 +118,7 @@ void InsertLineBreakCommand::doApply()
VisiblePosition endingPosition(Position(nodeToInsert.get(), 0));
setEndingSelection(VisibleSelection(endingPosition));
- } else if (pos.m_offset <= caretMinOffset(pos.node())) {
+ } else if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.node())) {
insertNodeAt(nodeToInsert.get(), pos);
// Insert an extra br or '\n' if the just inserted one collapsed.
@@ -128,7 +128,7 @@ void InsertLineBreakCommand::doApply()
setEndingSelection(VisibleSelection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM));
// If we're inserting after all of the rendered text in a text node, or into a non-text node,
// a simple insertion is sufficient.
- } else if (pos.m_offset >= caretMaxOffset(pos.node()) || !pos.node()->isTextNode()) {
+ } else if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node()) || !pos.node()->isTextNode()) {
insertNodeAt(nodeToInsert.get(), pos);
setEndingSelection(VisibleSelection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM));
} else {
@@ -137,7 +137,7 @@ void InsertLineBreakCommand::doApply()
// Do the split
Text* textNode = static_cast<Text*>(pos.node());
- splitTextNode(textNode, pos.m_offset);
+ splitTextNode(textNode, pos.deprecatedEditingOffset());
insertNodeBefore(nodeToInsert, textNode);
Position endingPosition = Position(textNode, 0);
diff --git a/WebCore/editing/InsertListCommand.cpp b/WebCore/editing/InsertListCommand.cpp
index aa48761..ec707d2 100644
--- a/WebCore/editing/InsertListCommand.cpp
+++ b/WebCore/editing/InsertListCommand.cpp
@@ -202,7 +202,8 @@ void InsertListCommand::doApply()
}
if (!listChildNode || switchListType || m_forceCreateList) {
// Create list.
- VisiblePosition start = startOfParagraph(endingSelection().visibleStart());
+ VisiblePosition originalStart = endingSelection().visibleStart();
+ VisiblePosition start = startOfParagraph(originalStart);
VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
// Check for adjoining lists.
@@ -251,12 +252,17 @@ void InsertListCommand::doApply()
Node* listChild = enclosingListChild(insertionPos.node());
if (listChild && listChild->hasTagName(liTag))
insertionPos = positionBeforeNode(listChild);
-
+
insertNodeAt(listElement, insertionPos);
+
+ // We inserted the list at the start of the content we're about to move
+ // Update the start of content, so we don't try to move the list into itself. bug 19066
+ if (insertionPos == start.deepEquivalent())
+ start = startOfParagraph(originalStart);
}
moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true);
if (nextList && previousList)
- mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList));
+ mergeIdenticalElements(previousList, nextList);
}
}
diff --git a/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/WebCore/editing/InsertParagraphSeparatorCommand.cpp
index 0e9c291..734d8fc 100644
--- a/WebCore/editing/InsertParagraphSeparatorCommand.cpp
+++ b/WebCore/editing/InsertParagraphSeparatorCommand.cpp
@@ -26,16 +26,17 @@
#include "config.h"
#include "InsertParagraphSeparatorCommand.h"
-#include "Document.h"
-#include "Logging.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
-#include "Text.h"
-#include "htmlediting.h"
+#include "Document.h"
#include "HTMLElement.h"
#include "HTMLNames.h"
#include "InsertLineBreakCommand.h"
+#include "Logging.h"
#include "RenderObject.h"
+#include "Text.h"
+#include "htmlediting.h"
#include "visible_units.h"
namespace WebCore {
@@ -164,13 +165,13 @@ void InsertParagraphSeparatorCommand::doApply()
blockToInsert = createDefaultParagraphElement(document());
else
blockToInsert = startBlock->cloneElementWithoutChildren();
-
+
//---------------------------------------------------------------------
// Handle case when position is in the last visible position in its block,
// including when the block is empty.
if (isLastInBlock) {
if (nestNewBlock) {
- if (isFirstInBlock && !lineBreakExistsAtPosition(visiblePos)) {
+ if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
// The block is empty. Create an empty block to
// represent the paragraph that we're leaving.
RefPtr<Element> extraBlock = createDefaultParagraphElement(document());
@@ -178,8 +179,12 @@ void InsertParagraphSeparatorCommand::doApply()
appendBlockPlaceholder(extraBlock);
}
appendNode(blockToInsert, startBlock);
- } else
- insertNodeAfter(blockToInsert, startBlock);
+ } else {
+ // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it
+ // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
+ Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote);
+ insertNodeAfter(blockToInsert, highestBlockquote ? highestBlockquote : startBlock);
+ }
appendBlockPlaceholder(blockToInsert);
setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
@@ -195,7 +200,7 @@ void InsertParagraphSeparatorCommand::doApply()
if (isFirstInBlock && !nestNewBlock)
refNode = startBlock;
else if (insertionPosition.node() == startBlock && nestNewBlock) {
- refNode = startBlock->childNode(insertionPosition.m_offset);
+ refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset());
ASSERT(refNode); // must be true or we'd be in the end of block case
} else
refNode = insertionPosition.node();
@@ -248,16 +253,16 @@ void InsertParagraphSeparatorCommand::doApply()
if (leadingWhitespace.isNotNull()) {
Text* textNode = static_cast<Text*>(leadingWhitespace.node());
ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
- replaceTextInNode(textNode, leadingWhitespace.m_offset, 1, nonBreakingSpaceString());
+ replaceTextInNode(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
}
// Split at pos if in the middle of a text node.
if (insertionPosition.node()->isTextNode()) {
Text* textNode = static_cast<Text*>(insertionPosition.node());
- bool atEnd = (unsigned)insertionPosition.m_offset >= textNode->length();
- if (insertionPosition.m_offset > 0 && !atEnd) {
- splitTextNode(textNode, insertionPosition.m_offset);
- insertionPosition.m_offset = 0;
+ bool atEnd = (unsigned)insertionPosition.deprecatedEditingOffset() >= textNode->length();
+ if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
+ splitTextNode(textNode, insertionPosition.deprecatedEditingOffset());
+ insertionPosition.moveToOffset(0);
visiblePos = VisiblePosition(insertionPosition);
splitText = true;
}
@@ -282,13 +287,13 @@ void InsertParagraphSeparatorCommand::doApply()
// If the paragraph separator was inserted at the end of a paragraph, an empty line must be
// created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph
// element. If the first node to be inserted won't be one that will hold an empty line open, add a br.
- if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtPosition(visiblePos))
+ if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
appendNode(createBreakElement(document()).get(), blockToInsert.get());
// Move the start node and the siblings of the start node.
if (insertionPosition.node() != startBlock) {
Node* n = insertionPosition.node();
- if (insertionPosition.m_offset >= caretMaxOffset(n))
+ if (insertionPosition.deprecatedEditingOffset() >= caretMaxOffset(n))
n = n->nextSibling();
while (n && n != blockToInsert) {
diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp
index 52da5d0..bf6fd19 100644
--- a/WebCore/editing/InsertTextCommand.cpp
+++ b/WebCore/editing/InsertTextCommand.cpp
@@ -89,9 +89,9 @@ bool InsertTextCommand::performTrivialReplace(const String& text, bool selectIns
if (start.node() != end.node() || !start.node()->isTextNode() || isTabSpanTextNode(start.node()))
return false;
- replaceTextInNode(static_cast<Text*>(start.node()), start.m_offset, end.m_offset - start.m_offset, text);
+ replaceTextInNode(static_cast<Text*>(start.node()), start.deprecatedEditingOffset(), end.deprecatedEditingOffset() - start.deprecatedEditingOffset(), text);
- Position endPosition(start.node(), start.m_offset + text.length());
+ Position endPosition(start.node(), start.deprecatedEditingOffset() + text.length());
// We could have inserted a part of composed character sequence,
// so we are basically treating ending selection as a range to avoid validation.
@@ -130,8 +130,27 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex
deleteSelection(false, true, true, false);
}
+ Position startPosition(endingSelection().start());
+
+ Position placeholder;
+ // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content
+ // is inserted just before them.
+ // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661.
+ // If the caret is just before a placeholder, downstream will normalize the caret to it.
+ Position downstream(startPosition.downstream());
+ if (lineBreakExistsAtPosition(downstream)) {
+ // FIXME: This doesn't handle placeholders at the end of anonymous blocks.
+ VisiblePosition caret(startPosition);
+ if (isEndOfBlock(caret) && isStartOfParagraph(caret))
+ placeholder = downstream;
+ // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before
+ // we get a chance to insert into it. We check for a placeholder now, though, because doing so requires
+ // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout.
+ }
+
// Insert the character at the leftmost candidate.
- Position startPosition = endingSelection().start().upstream();
+ startPosition = startPosition.upstream();
+
// It is possible for the node that contains startPosition to contain only unrendered whitespace,
// and so deleteInsignificantText could remove it. Save the position before the node in case that happens.
Position positionBeforeStartNode(positionBeforeNode(startPosition.node()));
@@ -148,14 +167,16 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex
if (text == "\t") {
endPosition = insertTab(startPosition);
startPosition = endPosition.previous();
- removePlaceholderAt(VisiblePosition(startPosition));
+ if (placeholder.isNotNull())
+ removePlaceholderAt(placeholder);
m_charactersAdded += 1;
} else {
// Make sure the document is set up to receive text
startPosition = prepareForTextInsertion(startPosition);
- removePlaceholderAt(VisiblePosition(startPosition));
+ if (placeholder.isNotNull())
+ removePlaceholderAt(placeholder);
Text *textNode = static_cast<Text *>(startPosition.node());
- int offset = startPosition.m_offset;
+ int offset = startPosition.deprecatedEditingOffset();
insertTextIntoNode(textNode, offset, text);
endPosition = Position(textNode, offset + text.length());
@@ -207,7 +228,7 @@ Position InsertTextCommand::insertTab(const Position& pos)
Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
Node *node = insertPos.node();
- unsigned int offset = insertPos.m_offset;
+ unsigned int offset = insertPos.deprecatedEditingOffset();
// keep tabs coalesced in tab span
if (isTabSpanTextNode(node)) {
diff --git a/WebCore/editing/ModifySelectionListLevel.cpp b/WebCore/editing/ModifySelectionListLevel.cpp
index 5ea658c..9a7e105 100644
--- a/WebCore/editing/ModifySelectionListLevel.cpp
+++ b/WebCore/editing/ModifySelectionListLevel.cpp
@@ -27,8 +27,8 @@
#include "ModifySelectionListLevel.h"
#include "Document.h"
-#include "Element.h"
#include "Frame.h"
+#include "HTMLElement.h"
#include "RenderObject.h"
#include "SelectionController.h"
#include "htmlediting.h"
diff --git a/WebCore/editing/MoveSelectionCommand.cpp b/WebCore/editing/MoveSelectionCommand.cpp
index 2c656e7..0a2d3f4 100644
--- a/WebCore/editing/MoveSelectionCommand.cpp
+++ b/WebCore/editing/MoveSelectionCommand.cpp
@@ -48,14 +48,14 @@ void MoveSelectionCommand::doApply()
// Update the position otherwise it may become invalid after the selection is deleted.
Node *positionNode = m_position.node();
- int positionOffset = m_position.m_offset;
+ int positionOffset = m_position.deprecatedEditingOffset();
Position selectionEnd = selection.end();
- int selectionEndOffset = selectionEnd.m_offset;
+ int selectionEndOffset = selectionEnd.deprecatedEditingOffset();
if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
positionOffset -= selectionEndOffset;
Position selectionStart = selection.start();
if (selectionStart.node() == positionNode) {
- positionOffset += selectionStart.m_offset;
+ positionOffset += selectionStart.deprecatedEditingOffset();
}
pos = Position(positionNode, positionOffset);
}
diff --git a/WebCore/editing/RemoveCSSPropertyCommand.h b/WebCore/editing/RemoveCSSPropertyCommand.h
index fd81307..836f9d7 100644
--- a/WebCore/editing/RemoveCSSPropertyCommand.h
+++ b/WebCore/editing/RemoveCSSPropertyCommand.h
@@ -27,6 +27,7 @@
#define RemoveCSSPropertyCommand_h
#include "EditCommand.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
namespace WebCore {
diff --git a/WebCore/editing/RemoveFormatCommand.cpp b/WebCore/editing/RemoveFormatCommand.cpp
index 609ab0e..6d681ee 100644
--- a/WebCore/editing/RemoveFormatCommand.cpp
+++ b/WebCore/editing/RemoveFormatCommand.cpp
@@ -27,6 +27,7 @@
#include "RemoveFormatCommand.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "Editor.h"
#include "Frame.h"
#include "HTMLNames.h"
diff --git a/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp b/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp
index 98f4282..1452f88 100644
--- a/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp
+++ b/WebCore/editing/RemoveNodePreservingChildrenCommand.cpp
@@ -32,7 +32,8 @@
namespace WebCore {
RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(PassRefPtr<Node> node)
- : CompositeEditCommand(node->document()), m_node(node)
+ : CompositeEditCommand(node->document())
+ , m_node(node)
{
ASSERT(m_node);
}
diff --git a/WebCore/editing/ReplaceNodeWithSpanCommand.cpp b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp
new file mode 100644
index 0000000..21ca924
--- /dev/null
+++ b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ReplaceNodeWithSpanCommand.h"
+
+#include "htmlediting.h"
+#include "HTMLElement.h"
+#include "HTMLNames.h"
+#include "NamedAttrMap.h"
+
+#include <wtf/Assertions.h>
+
+namespace WebCore {
+
+using namespace HTMLNames;
+
+ReplaceNodeWithSpanCommand::ReplaceNodeWithSpanCommand(PassRefPtr<Node> node)
+ : CompositeEditCommand(node->document())
+ , m_node(node)
+{
+ ASSERT(m_node);
+}
+
+static void swapInNodePreservingAttributesAndChildren(Node* newNode, Node* nodeToReplace)
+{
+ ASSERT(nodeToReplace->inDocument());
+ ExceptionCode ec = 0;
+ Node* parentNode = nodeToReplace->parentNode();
+ parentNode->insertBefore(newNode, nodeToReplace, ec);
+ ASSERT(!ec);
+
+ for (Node* child = nodeToReplace->firstChild(); child; child = child->nextSibling()) {
+ newNode->appendChild(child, ec);
+ ASSERT(!ec);
+ }
+
+ newNode->attributes()->setAttributes(*nodeToReplace->attributes());
+
+ parentNode->removeChild(nodeToReplace, ec);
+ ASSERT(!ec);
+}
+
+void ReplaceNodeWithSpanCommand::doApply()
+{
+ if (!m_node->inDocument())
+ return;
+ if (!m_spanElement)
+ m_spanElement = createHTMLElement(m_node->document(), spanTag);
+ swapInNodePreservingAttributesAndChildren(m_spanElement.get(), m_node.get());
+}
+
+void ReplaceNodeWithSpanCommand::doUnapply()
+{
+ if (!m_spanElement->inDocument())
+ return;
+ swapInNodePreservingAttributesAndChildren(m_node.get(), m_spanElement.get());
+}
+
+} // namespace WebCore
diff --git a/WebCore/editing/ReplaceNodeWithSpanCommand.h b/WebCore/editing/ReplaceNodeWithSpanCommand.h
new file mode 100644
index 0000000..7b375b6
--- /dev/null
+++ b/WebCore/editing/ReplaceNodeWithSpanCommand.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ReplaceNodeWithSpanCommand_h
+#define ReplaceNodeWithSpanCommand_h
+
+#include "CompositeEditCommand.h"
+
+namespace WebCore {
+
+class HTMLElement;
+
+// More accurately, this is ReplaceNodeWithSpanPreservingChildrenAndAttributesCommand
+class ReplaceNodeWithSpanCommand : public CompositeEditCommand {
+public:
+ static PassRefPtr<ReplaceNodeWithSpanCommand> create(PassRefPtr<Node> node)
+ {
+ return adoptRef(new ReplaceNodeWithSpanCommand(node));
+ }
+
+ HTMLElement* spanElement() { return m_spanElement.get(); }
+
+private:
+ ReplaceNodeWithSpanCommand(PassRefPtr<Node>);
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ RefPtr<Node> m_node;
+ RefPtr<HTMLElement> m_spanElement;
+};
+
+} // namespace WebCore
+
+#endif // ReplaceNodeWithSpanCommand
diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp
index 09ad985..c6da864 100644
--- a/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -28,20 +28,21 @@
#include "ApplyStyleCommand.h"
#include "BeforeTextInsertedEvent.h"
-#include "BreakBlockquoteCommand.h"
+#include "BreakBlockquoteCommand.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "EditingText.h"
-#include "EventNames.h"
#include "Element.h"
+#include "EventNames.h"
#include "Frame.h"
#include "HTMLElement.h"
-#include "HTMLInterchange.h"
#include "HTMLInputElement.h"
+#include "HTMLInterchange.h"
#include "HTMLNames.h"
#include "SelectionController.h"
#include "SmartReplace.h"
@@ -124,7 +125,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f
Node* shadowAncestorNode = editableRoot->shadowAncestorNode();
- if (!editableRoot->inlineEventListenerForType(eventNames().webkitBeforeTextInsertedEvent) &&
+ if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) &&
// FIXME: Remove these checks once textareas and textfields actually register an event handler.
!(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) &&
editableRoot->isContentRichlyEditable()) {
@@ -343,14 +344,22 @@ static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisibleP
return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted));
}
-bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart)
+bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMailBlockquote)
{
+ if (m_movingParagraph)
+ return false;
+
VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
VisiblePosition prev = startOfInsertedContent.previous(true);
if (prev.isNull())
return false;
- if (!m_movingParagraph && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent()))
+ // When we have matching quote levels, its ok to merge more frequently.
+ // For a successful merge, we still need to make sure that the inserted content starts with the beginning of a paragraph.
+ // And we should only merge here if the selection start was inside a mail blockquote. This prevents against removing a
+ // blockquote from newly pasted quoted content that was pasted into an unquoted position. If that unquoted position happens
+ // to be right after another blockquote, we don't want to merge and risk stripping a valid block (and newline) from the pasted content.
+ if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMailBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent()))
return true;
return !selectionStartWasStartOfParagraph &&
@@ -690,7 +699,8 @@ void ReplaceSelectionCommand::mergeEndIfNeeded()
moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
// Merging forward will remove m_lastLeafInserted from the document.
// FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
- // only ever used to create positions where inserted content starts/ends.
+ // only ever used to create positions where inserted content starts/ends. Also, we sometimes insert content
+ // directly into text nodes already in the document, in which case tracking inserted nodes is inadequate.
if (mergeForward) {
m_lastLeafInserted = destination.previous().deepEquivalent().node();
if (!m_firstNodeInserted->inDocument())
@@ -711,6 +721,9 @@ void ReplaceSelectionCommand::doApply()
Element* currentRoot = selection.rootEditableElement();
ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection);
+ if (performTrivialReplace(fragment))
+ return;
+
if (m_matchStyle)
m_insertionStyle = styleAtPosition(selection.start());
@@ -772,9 +785,10 @@ void ReplaceSelectionCommand::doApply()
insertionPos = endingSelection().start();
}
- if (startIsInsideMailBlockquote && m_preventNesting) {
- // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break
- // out of any surrounding Mail blockquotes.
+ // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break
+ // out of any surrounding Mail blockquotes. Unless we're inserting in a table, in which case
+ // breaking the blockquote will prevent the content from actually being inserted in the table.
+ if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) {
applyCommandToComposite(BreakBlockquoteCommand::create(document()));
// This will leave a br between the split.
Node* br = endingSelection().start().node();
@@ -821,6 +835,9 @@ void ReplaceSelectionCommand::doApply()
bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
+ // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try
+ // again here if they've been removed.
+
// We're finished if there is nothing to add.
if (fragment.isEmpty() || !fragment.firstChild())
return;
@@ -876,7 +893,7 @@ void ReplaceSelectionCommand::doApply()
// We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
// didn't have a br after it, so the inserted content ended up in the same paragraph.
- if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.m_offset < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
+ if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
Position lastPositionToSelect;
@@ -890,13 +907,7 @@ void ReplaceSelectionCommand::doApply()
// the start merge so that the start merge doesn't effect our decision.
m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph);
- if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart())) {
- // Bail to avoid infinite recursion.
- if (m_movingParagraph) {
- // setting display:inline does not work for td elements in quirks mode
- ASSERT(m_firstNodeInserted->hasTagName(tdTag));
- return;
- }
+ if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote)) {
VisiblePosition destination = startOfInsertedContent.previous();
VisiblePosition startOfParagraphToMove = startOfInsertedContent;
@@ -1092,4 +1103,38 @@ void ReplaceSelectionCommand::updateNodesInserted(Node *node)
m_lastLeafInserted = node->lastDescendant();
}
+// During simple pastes, where we're just pasting a text node into a run of text, we insert the text node
+// directly into the text node that holds the selection. This is much faster than the generalized code in
+// ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.cgi?id=6148> since we don't
+// split text nodes.
+bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& fragment)
+{
+ if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode())
+ return false;
+
+ // FIXME: Would be nice to handle smart replace in the fast path.
+ if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd())
+ return false;
+
+ Text* textNode = static_cast<Text*>(fragment.firstChild());
+ // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here.
+ String text(textNode->data());
+
+ Position start = endingSelection().start();
+ Position end = endingSelection().end();
+
+ if (start.anchorNode() != end.anchorNode() || !start.anchorNode()->isTextNode())
+ return false;
+
+ replaceTextInNode(static_cast<Text*>(start.anchorNode()), start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text);
+
+ end = Position(start.anchorNode(), start.offsetInContainerNode() + text.length());
+
+ VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end);
+
+ setEndingSelection(selectionAfterReplace);
+
+ return true;
+}
+
} // namespace WebCore
diff --git a/WebCore/editing/ReplaceSelectionCommand.h b/WebCore/editing/ReplaceSelectionCommand.h
index 18dffa5..1cb93c3 100644
--- a/WebCore/editing/ReplaceSelectionCommand.h
+++ b/WebCore/editing/ReplaceSelectionCommand.h
@@ -31,6 +31,7 @@
namespace WebCore {
class DocumentFragment;
+class ReplacementFragment;
class ReplaceSelectionCommand : public CompositeEditCommand {
public:
@@ -57,7 +58,7 @@ private:
void updateNodesInserted(Node*);
bool shouldRemoveEndBR(Node*, const VisiblePosition&);
- bool shouldMergeStart(bool, bool);
+ bool shouldMergeStart(bool, bool, bool);
bool shouldMergeEnd(bool selectEndWasEndOfParagraph);
bool shouldMerge(const VisiblePosition&, const VisiblePosition&);
@@ -74,6 +75,8 @@ private:
VisiblePosition positionAtStartOfInsertedContent();
VisiblePosition positionAtEndOfInsertedContent();
+
+ bool performTrivialReplace(const ReplacementFragment&);
RefPtr<Node> m_firstNodeInserted;
RefPtr<Node> m_lastLeafInserted;
diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp
index d606891..d0427c0 100644
--- a/WebCore/editing/SelectionController.cpp
+++ b/WebCore/editing/SelectionController.cpp
@@ -32,7 +32,6 @@
#include "Editor.h"
#include "Element.h"
#include "EventHandler.h"
-#include "EventNames.h"
#include "ExceptionCode.h"
#include "FocusController.h"
#include "FloatQuad.h"
@@ -70,7 +69,7 @@ SelectionController::SelectionController(Frame* frame, bool isDragCaretControlle
, m_lastChangeWasHorizontalExtension(false)
, m_isDragCaretController(isDragCaretController)
, m_isCaretBlinkingSuspended(false)
- , m_focused(false)
+ , m_focused(frame && frame->page() && frame->page()->focusController()->focusedFrame() == frame)
{
}
@@ -203,8 +202,7 @@ void SelectionController::nodeWillBeRemoved(Node *node)
else
m_sel.setWithoutValidation(m_sel.end(), m_sel.start());
// FIXME: This could be more efficient if we had an isNodeInRange function on Ranges.
- } else if (Range::compareBoundaryPoints(m_sel.start(), Position(node, 0)) == -1 &&
- Range::compareBoundaryPoints(m_sel.end(), Position(node, 0)) == 1) {
+ } else if (comparePositions(m_sel.start(), Position(node, 0)) == -1 && comparePositions(m_sel.end(), Position(node, 0)) == 1) {
// If we did nothing here, when this node's renderer was destroyed, the rect that it
// occupied would be invalidated, but, selection gaps that change as a result of
// the removal wouldn't be invalidated.
@@ -214,7 +212,7 @@ void SelectionController::nodeWillBeRemoved(Node *node)
if (clearRenderTreeSelection) {
RefPtr<Document> document = m_sel.start().node()->document();
- document->updateRendering();
+ document->updateStyleIfNeeded();
if (RenderView* view = toRenderView(document->renderer()))
view->clearSelection();
}
@@ -252,7 +250,53 @@ void SelectionController::willBeModified(EAlteration alter, EDirection direction
}
}
-VisiblePosition SelectionController::modifyExtendingRightForward(TextGranularity granularity)
+TextDirection SelectionController::directionOfEnclosingBlock() {
+ Node* n = m_sel.extent().node();
+ Node* enclosingBlockNode = enclosingBlock(n);
+ if (!enclosingBlockNode)
+ return LTR;
+ RenderObject* renderer = enclosingBlockNode->renderer();
+ if (renderer)
+ return renderer->style()->direction();
+ return LTR;
+}
+
+VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granularity)
+{
+ VisiblePosition pos(m_sel.extent(), m_sel.affinity());
+
+ // The difference between modifyExtendingRight and modifyExtendingForward is:
+ // modifyExtendingForward always extends forward logically.
+ // modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word,
+ // it extends forward logically if the enclosing block is LTR direction,
+ // but it extends backward logically if the enclosing block is RTL direction.
+ switch (granularity) {
+ case CharacterGranularity:
+ if (directionOfEnclosingBlock() == LTR)
+ pos = pos.next(true);
+ else
+ pos = pos.previous(true);
+ break;
+ case WordGranularity:
+ if (directionOfEnclosingBlock() == LTR)
+ pos = nextWordPosition(pos);
+ else
+ pos = previousWordPosition(pos);
+ break;
+ case SentenceGranularity:
+ case LineGranularity:
+ case ParagraphGranularity:
+ case SentenceBoundary:
+ case LineBoundary:
+ case ParagraphBoundary:
+ case DocumentBoundary:
+ // FIXME: implement all of the above?
+ pos = modifyExtendingForward(granularity);
+ }
+ return pos;
+}
+
+VisiblePosition SelectionController::modifyExtendingForward(TextGranularity granularity)
{
VisiblePosition pos(m_sel.extent(), m_sel.affinity());
switch (granularity) {
@@ -275,7 +319,7 @@ VisiblePosition SelectionController::modifyExtendingRightForward(TextGranularity
pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
break;
case LineBoundary:
- pos = endOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
break;
case ParagraphBoundary:
pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
@@ -349,7 +393,7 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula
pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
break;
case LineBoundary:
- pos = endOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
+ pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
break;
case ParagraphBoundary:
pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
@@ -366,10 +410,44 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula
return pos;
}
-VisiblePosition SelectionController::modifyExtendingLeftBackward(TextGranularity granularity)
+VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granularity)
{
VisiblePosition pos(m_sel.extent(), m_sel.affinity());
-
+
+ // The difference between modifyExtendingLeft and modifyExtendingBackward is:
+ // modifyExtendingBackward always extends backward logically.
+ // modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word,
+ // it extends backward logically if the enclosing block is LTR direction,
+ // but it extends forward logically if the enclosing block is RTL direction.
+ switch (granularity) {
+ case CharacterGranularity:
+ if (directionOfEnclosingBlock() == LTR)
+ pos = pos.previous(true);
+ else
+ pos = pos.next(true);
+ break;
+ case WordGranularity:
+ if (directionOfEnclosingBlock() == LTR)
+ pos = previousWordPosition(pos);
+ else
+ pos = nextWordPosition(pos);
+ break;
+ case SentenceGranularity:
+ case LineGranularity:
+ case ParagraphGranularity:
+ case SentenceBoundary:
+ case LineBoundary:
+ case ParagraphBoundary:
+ case DocumentBoundary:
+ pos = modifyExtendingBackward(granularity);
+ }
+ return pos;
+}
+
+VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity granularity)
+{
+ VisiblePosition pos(m_sel.extent(), m_sel.affinity());
+
// Extending a selection backward by word or character from just after a table selects
// the table. This "makes sense" from the user perspective, esp. when deleting.
// It was done here instead of in VisiblePosition because we want VPs to iterate
@@ -394,7 +472,7 @@ VisiblePosition SelectionController::modifyExtendingLeftBackward(TextGranularity
pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
break;
case LineBoundary:
- pos = startOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
break;
case ParagraphBoundary:
pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
@@ -461,7 +539,7 @@ VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granul
pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
break;
case LineBoundary:
- pos = startOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
+ pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
break;
case ParagraphBoundary:
pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
@@ -501,11 +579,11 @@ bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranular
if (alter == MOVE)
pos = modifyMovingRight(granularity);
else
- pos = modifyExtendingRightForward(granularity);
+ pos = modifyExtendingRight(granularity);
break;
case FORWARD:
if (alter == EXTEND)
- pos = modifyExtendingRightForward(granularity);
+ pos = modifyExtendingForward(granularity);
else
pos = modifyMovingForward(granularity);
break;
@@ -513,11 +591,11 @@ bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranular
if (alter == MOVE)
pos = modifyMovingLeft(granularity);
else
- pos = modifyExtendingLeftBackward(granularity);
+ pos = modifyExtendingLeft(granularity);
break;
case BACKWARD:
if (alter == EXTEND)
- pos = modifyExtendingLeftBackward(granularity);
+ pos = modifyExtendingBackward(granularity);
else
pos = modifyMovingBackward(granularity);
break;
@@ -735,7 +813,7 @@ void SelectionController::layout()
return;
}
- m_sel.start().node()->document()->updateRendering();
+ m_sel.start().node()->document()->updateStyleIfNeeded();
m_caretRect = IntRect();
@@ -796,11 +874,20 @@ RenderObject* SelectionController::caretRenderer() const
IntRect SelectionController::localCaretRect() const
{
if (m_needsLayout)
- const_cast<SelectionController *>(this)->layout();
+ const_cast<SelectionController*>(this)->layout();
return m_caretRect;
}
+IntRect SelectionController::absoluteBoundsForLocalRect(const IntRect& rect) const
+{
+ RenderObject* caretPainter = caretRenderer();
+ if (!caretPainter)
+ return IntRect();
+
+ return caretPainter->localToAbsoluteQuad(FloatRect(rect)).enclosingBoundingBox();
+}
+
IntRect SelectionController::absoluteCaretBounds()
{
recomputeCaretRect();
@@ -819,13 +906,7 @@ static IntRect repaintRectForCaret(IntRect caret)
IntRect SelectionController::caretRepaintRect() const
{
- IntRect localRect = repaintRectForCaret(localCaretRect());
-
- RenderObject* caretPainter = caretRenderer();
- if (caretPainter)
- return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox();
-
- return IntRect();
+ return absoluteBoundsForLocalRect(repaintRectForCaret(localCaretRect()));
}
bool SelectionController::recomputeCaretRect()
@@ -841,22 +922,26 @@ bool SelectionController::recomputeCaretRect()
return false;
IntRect oldRect = m_caretRect;
- m_needsLayout = true;
IntRect newRect = localCaretRect();
if (oldRect == newRect && !m_absCaretBoundsDirty)
return false;
- IntRect oldAbsRepaintRect = m_absCaretBounds;
- m_absCaretBounds = caretRepaintRect();
+ IntRect oldAbsCaretBounds = m_absCaretBounds;
+ // FIXME: Rename m_caretRect to m_localCaretRect.
+ m_absCaretBounds = absoluteBoundsForLocalRect(m_caretRect);
m_absCaretBoundsDirty = false;
- if (oldAbsRepaintRect == m_absCaretBounds)
+ if (oldAbsCaretBounds == m_absCaretBounds)
return false;
+
+ IntRect oldAbsoluteCaretRepaintBounds = m_absoluteCaretRepaintBounds;
+ // We believe that we need to inflate the local rect before transforming it to obtain the repaint bounds.
+ m_absoluteCaretRepaintBounds = caretRepaintRect();
if (RenderView* view = toRenderView(m_frame->document()->renderer())) {
// FIXME: make caret repainting container-aware.
- view->repaintRectangleInViewAndCompositedLayers(oldAbsRepaintRect, false);
- view->repaintRectangleInViewAndCompositedLayers(m_absCaretBounds, false);
+ view->repaintRectangleInViewAndCompositedLayers(oldAbsoluteCaretRepaintBounds, false);
+ view->repaintRectangleInViewAndCompositedLayers(m_absoluteCaretRepaintBounds, false);
}
return true;
@@ -932,9 +1017,9 @@ void SelectionController::debugRenderer(RenderObject *r, bool selected) const
if (selected) {
int offset = 0;
if (r->node() == m_sel.start().node())
- offset = m_sel.start().m_offset;
+ offset = m_sel.start().deprecatedEditingOffset();
else if (r->node() == m_sel.end().node())
- offset = m_sel.end().m_offset;
+ offset = m_sel.end().deprecatedEditingOffset();
int pos;
InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos);
@@ -1179,7 +1264,7 @@ void SelectionController::focusedOrActiveStateChanged()
// RenderTheme::isFocused() check if the frame is active, we have to
// update style and theme state that depended on those.
if (Node* node = m_frame->document()->focusedNode()) {
- node->setChanged();
+ node->setNeedsStyleRecalc();
if (RenderObject* renderer = node->renderer())
if (renderer && renderer->style()->hasAppearance())
theme()->stateChanged(renderer, FocusState);
@@ -1202,8 +1287,6 @@ void SelectionController::setFocused(bool flag)
m_focused = flag;
focusedOrActiveStateChanged();
-
- m_frame->document()->dispatchWindowEvent(flag ? eventNames().focusEvent : eventNames().blurEvent, false, false);
}
bool SelectionController::isFocusedAndActive() const
diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h
index 21e849d..bbd343c 100644
--- a/WebCore/editing/SelectionController.h
+++ b/WebCore/editing/SelectionController.h
@@ -102,6 +102,7 @@ public:
bool isRange() const { return m_sel.isRange(); }
bool isCaretOrRange() const { return m_sel.isCaretOrRange(); }
bool isInPasswordField() const;
+ bool isAll(StayInEditableContent stayInEditableContent = MustStayInEditableContent) const { return m_sel.isAll(stayInEditableContent); }
PassRefPtr<Range> toNormalizedRange() const { return m_sel.toNormalizedRange(); }
@@ -119,6 +120,7 @@ public:
// Focus
void setFocused(bool);
+ bool isFocused() const { return m_focused; }
bool isFocusedAndActive() const;
void pageActivationChanged();
@@ -130,10 +132,14 @@ public:
private:
enum EPositionType { START, END, BASE, EXTENT };
- VisiblePosition modifyExtendingRightForward(TextGranularity);
+ TextDirection directionOfEnclosingBlock();
+
+ VisiblePosition modifyExtendingRight(TextGranularity);
+ VisiblePosition modifyExtendingForward(TextGranularity);
VisiblePosition modifyMovingRight(TextGranularity);
VisiblePosition modifyMovingForward(TextGranularity);
- VisiblePosition modifyExtendingLeftBackward(TextGranularity);
+ VisiblePosition modifyExtendingLeft(TextGranularity);
+ VisiblePosition modifyExtendingBackward(TextGranularity);
VisiblePosition modifyMovingLeft(TextGranularity);
VisiblePosition modifyMovingBackward(TextGranularity);
@@ -142,7 +148,7 @@ private:
int xPosForVerticalArrowNavigation(EPositionType);
-#if PLATFORM(MAC)
+#if PLATFORM(MAC) || PLATFORM(GTK)
void notifyAccessibilityForSelectionChange();
#else
void notifyAccessibilityForSelectionChange() {};
@@ -150,6 +156,8 @@ private:
void focusedOrActiveStateChanged();
bool caretRendersInsideNode(Node*) const;
+
+ IntRect absoluteBoundsForLocalRect(const IntRect&) const;
Frame* m_frame;
int m_xPosForVerticalArrowNavigation;
@@ -158,6 +166,7 @@ private:
IntRect m_caretRect; // caret rect in coords local to the renderer responsible for painting the caret
IntRect m_absCaretBounds; // absolute bounding rect for the caret
+ IntRect m_absoluteCaretRepaintBounds;
bool m_needsLayout : 1; // true if the caret and expectedVisible rectangles need to be calculated
bool m_absCaretBoundsDirty: 1;
@@ -187,3 +196,4 @@ void showTree(const WebCore::SelectionController*);
#endif
#endif // SelectionController_h
+
diff --git a/WebCore/editing/TextAffinity.h b/WebCore/editing/TextAffinity.h
index 5562cc4..a5565c7 100644
--- a/WebCore/editing/TextAffinity.h
+++ b/WebCore/editing/TextAffinity.h
@@ -38,20 +38,22 @@ namespace WebCore {
// From NSTextView.h:
// NSSelectionAffinityUpstream = 0
// NSSelectionAffinityDownstream = 1
-typedef enum { UPSTREAM = 0, DOWNSTREAM = 1 } EAffinity;
+enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 };
+
+} // namespace WebCore
#ifdef __OBJC__
-inline NSSelectionAffinity kit(EAffinity affinity)
+
+inline NSSelectionAffinity kit(WebCore::EAffinity affinity)
{
return static_cast<NSSelectionAffinity>(affinity);
}
-inline EAffinity core(NSSelectionAffinity affinity)
+inline WebCore::EAffinity core(NSSelectionAffinity affinity)
{
- return static_cast<EAffinity>(affinity);
+ return static_cast<WebCore::EAffinity>(affinity);
}
-#endif
-} // namespace WebCore
+#endif
#endif // TextAffinity_h
diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp
index 2d7e641..b311853 100644
--- a/WebCore/editing/TextIterator.cpp
+++ b/WebCore/editing/TextIterator.cpp
@@ -29,7 +29,7 @@
#include "CharacterNames.h"
#include "Document.h"
-#include "Element.h"
+#include "HTMLElement.h"
#include "HTMLNames.h"
#include "htmlediting.h"
#include "InlineTextBox.h"
@@ -103,6 +103,8 @@ TextIterator::TextIterator()
, m_endContainer(0)
, m_endOffset(0)
, m_positionNode(0)
+ , m_textCharacters(0)
+ , m_textLength(0)
, m_lastCharacter(0)
, m_emitCharactersBetweenAllVisiblePositions(false)
, m_enterTextControls(false)
@@ -116,6 +118,8 @@ TextIterator::TextIterator(const Range* r, bool emitCharactersBetweenAllVisibleP
, m_endContainer(0)
, m_endOffset(0)
, m_positionNode(0)
+ , m_textCharacters(0)
+ , m_textLength(0)
, m_emitCharactersBetweenAllVisiblePositions(emitCharactersBetweenAllVisiblePositions)
, m_enterTextControls(enterTextControls)
{
@@ -609,11 +613,13 @@ bool TextIterator::shouldRepresentNodeOffsetZero()
if (!m_node->renderer() || m_node->renderer()->style()->visibility() != VISIBLE)
return false;
- // The currPos.isNotNull() check is needed because positions in non-html content
- // (like svg) do not have visible positions, and we don't want to emit for them either.
+ // The startPos.isNotNull() check is needed because the start could be before the body,
+ // and in that case we'll get null. We don't want to put in newlines at the start in that case.
+ // The currPos.isNotNull() check is needed because positions in non-HTML content
+ // (like SVG) do not have visible positions, and we don't want to emit for them either.
VisiblePosition startPos = VisiblePosition(m_startContainer, m_startOffset, DOWNSTREAM);
VisiblePosition currPos = VisiblePosition(m_node, 0, DOWNSTREAM);
- return currPos.isNotNull() && !inSameLine(startPos, currPos);
+ return startPos.isNotNull() && currPos.isNotNull() && !inSameLine(startPos, currPos);
}
bool TextIterator::shouldEmitSpaceBeforeAndAfterNode(Node* node)
@@ -1558,7 +1564,7 @@ PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element *scope, int r
Position runEnd = VisiblePosition(runStart).next().deepEquivalent();
if (runEnd.isNotNull()) {
ExceptionCode ec = 0;
- textRunRange->setEnd(runEnd.node(), runEnd.m_offset, ec);
+ textRunRange->setEnd(runEnd.node(), runEnd.deprecatedEditingOffset(), ec);
ASSERT(!ec);
}
}
diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp
index 6235f7a..5ce4e56 100644
--- a/WebCore/editing/TypingCommand.cpp
+++ b/WebCore/editing/TypingCommand.cpp
@@ -33,6 +33,7 @@
#include "Editor.h"
#include "Element.h"
#include "Frame.h"
+#include "HTMLNames.h"
#include "InsertLineBreakCommand.h"
#include "InsertParagraphSeparatorCommand.h"
#include "InsertTextCommand.h"
@@ -44,6 +45,8 @@
namespace WebCore {
+using namespace HTMLNames;
+
TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, bool selectInsertedText, TextGranularity granularity, bool killRing)
: CompositeEditCommand(document),
m_commandType(commandType),
@@ -281,8 +284,17 @@ EditAction TypingCommand::editingAction() const
void TypingCommand::markMisspellingsAfterTyping()
{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled()
+ && !document()->frame()->editor()->isAutomaticQuoteSubstitutionEnabled()
+ && !document()->frame()->editor()->isAutomaticLinkDetectionEnabled()
+ && !document()->frame()->editor()->isAutomaticDashSubstitutionEnabled()
+ && !document()->frame()->editor()->isAutomaticTextReplacementEnabled())
+ return;
+#else
if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled())
return;
+#endif
// Take a look at the selection that results after typing and determine whether we need to spellcheck.
// Since the word containing the current selection is never marked, this does a check to
// see if typing made a new word that is not in the current selection. Basically, you
@@ -299,8 +311,15 @@ void TypingCommand::markMisspellingsAfterTyping()
void TypingCommand::typingAddedToOpenCommand()
{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ document()->frame()->editor()->appliedEditing(this);
+ // Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes.
+ markMisspellingsAfterTyping();
+#else
+ // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
markMisspellingsAfterTyping();
document()->frame()->editor()->appliedEditing(this);
+#endif
}
void TypingCommand::insertText(const String &text, bool selectInsertedText)
@@ -358,10 +377,38 @@ void TypingCommand::insertParagraphSeparator()
void TypingCommand::insertParagraphSeparatorInQuotedContent()
{
+ // If the selection starts inside a table, just insert the paragraph separator normally
+ // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
+ if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
+ insertParagraphSeparator();
+ return;
+ }
+
applyCommandToComposite(BreakBlockquoteCommand::create(document()));
typingAddedToOpenCommand();
}
+bool TypingCommand::makeEditableRootEmpty()
+{
+ Element* root = endingSelection().rootEditableElement();
+ if (!root->firstChild())
+ return false;
+
+ if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) {
+ // If there is a single child and it could be a placeholder, leave it alone.
+ if (root->renderer() && root->renderer()->isBlockFlow())
+ return false;
+ }
+
+ while (Node* child = root->firstChild())
+ removeNode(child);
+
+ addBlockPlaceholderIfNeeded(root);
+ setEndingSelection(VisibleSelection(Position(root, 0), DOWNSTREAM));
+
+ return true;
+}
+
void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
{
VisibleSelection selectionToDelete;
@@ -386,12 +433,17 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
if (killRing && selection.isCaret() && granularity != CharacterGranularity)
selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, CharacterGranularity);
- // When the caret is at the start of the editable area in an empty list item, break out of the list item.
if (endingSelection().visibleStart().previous(true).isNull()) {
+ // When the caret is at the start of the editable area in an empty list item, break out of the list item.
if (breakOutOfEmptyListItem()) {
typingAddedToOpenCommand();
return;
}
+ // When there are no visible positions in the editing root, delete its entire contents.
+ if (endingSelection().visibleStart().next(true).isNull() && makeEditableRootEmpty()) {
+ typingAddedToOpenCommand();
+ return;
+ }
}
VisiblePosition visibleStart(endingSelection().visibleStart());
@@ -411,7 +463,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
selectionToDelete = selection.selection();
- if (granularity == CharacterGranularity && selectionToDelete.end().node() == selectionToDelete.start().node() && selectionToDelete.end().m_offset - selectionToDelete.start().m_offset > 1) {
+ if (granularity == CharacterGranularity && selectionToDelete.end().node() == selectionToDelete.start().node() && selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset() > 1) {
// If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
}
@@ -476,7 +528,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
if (visibleEnd == endOfParagraph(visibleEnd))
downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream();
// When deleting tables: Select the table first, then perform the deletion
- if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.m_offset == 0) {
+ if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.deprecatedEditingOffset() == 0) {
setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.node()), DOWNSTREAM));
typingAddedToOpenCommand();
return;
@@ -499,10 +551,10 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
else {
int extraCharacters;
if (selectionToDelete.start().node() == selectionToDelete.end().node())
- extraCharacters = selectionToDelete.end().m_offset - selectionToDelete.start().m_offset;
+ extraCharacters = selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset();
else
- extraCharacters = selectionToDelete.end().m_offset;
- extent = Position(extent.node(), extent.m_offset + extraCharacters);
+ extraCharacters = selectionToDelete.end().deprecatedEditingOffset();
+ extent = Position(extent.node(), extent.deprecatedEditingOffset() + extraCharacters);
}
selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
}
diff --git a/WebCore/editing/TypingCommand.h b/WebCore/editing/TypingCommand.h
index bf588be..2c52447 100644
--- a/WebCore/editing/TypingCommand.h
+++ b/WebCore/editing/TypingCommand.h
@@ -83,6 +83,7 @@ private:
void markMisspellingsAfterTyping();
void typingAddedToOpenCommand();
+ bool makeEditableRootEmpty();
ETypingCommand m_commandType;
String m_textToInsert;
diff --git a/WebCore/editing/VisiblePosition.cpp b/WebCore/editing/VisiblePosition.cpp
index 27ee146..2db6d31 100644
--- a/WebCore/editing/VisiblePosition.cpp
+++ b/WebCore/editing/VisiblePosition.cpp
@@ -28,8 +28,8 @@
#include "CString.h"
#include "Document.h"
-#include "Element.h"
#include "FloatQuad.h"
+#include "HTMLElement.h"
#include "HTMLNames.h"
#include "InlineTextBox.h"
#include "Logging.h"
@@ -511,7 +511,7 @@ Position VisiblePosition::canonicalPosition(const Position& position)
return next;
}
-UChar VisiblePosition::characterAfter() const
+UChar32 VisiblePosition::characterAfter() const
{
// We canonicalize to the first of two equivalent candidates, but the second of the two candidates
// is the one that will be inside the text node containing the character after this visible position.
@@ -520,10 +520,15 @@ UChar VisiblePosition::characterAfter() const
if (!node || !node->isTextNode())
return 0;
Text* textNode = static_cast<Text*>(pos.node());
- int offset = pos.m_offset;
- if ((unsigned)offset >= textNode->length())
+ unsigned offset = pos.deprecatedEditingOffset();
+ unsigned length = textNode->length();
+ if (offset >= length)
return 0;
- return textNode->data()[offset];
+
+ UChar32 ch;
+ const UChar* characters = textNode->data().characters();
+ U16_NEXT(characters, offset, length, ch);
+ return ch;
}
IntRect VisiblePosition::localCaretRect(RenderObject*& renderer) const
@@ -576,7 +581,7 @@ void VisiblePosition::debugPosition(const char* msg) const
if (isNull())
fprintf(stderr, "Position [%s]: null\n", msg);
else
- fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, m_deepPosition.node()->nodeName().utf8().data(), m_deepPosition.node(), m_deepPosition.m_offset);
+ fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, m_deepPosition.node()->nodeName().utf8().data(), m_deepPosition.node(), m_deepPosition.deprecatedEditingOffset());
}
#ifndef NDEBUG
@@ -600,7 +605,7 @@ PassRefPtr<Range> makeRange(const VisiblePosition &start, const VisiblePosition
Position s = rangeCompliantEquivalent(start);
Position e = rangeCompliantEquivalent(end);
- return Range::create(s.node()->document(), s.node(), s.m_offset, e.node(), e.m_offset);
+ return Range::create(s.node()->document(), s.node(), s.deprecatedEditingOffset(), e.node(), e.deprecatedEditingOffset());
}
VisiblePosition startVisiblePosition(const Range *r, EAffinity affinity)
@@ -621,7 +626,7 @@ bool setStart(Range *r, const VisiblePosition &visiblePosition)
return false;
Position p = rangeCompliantEquivalent(visiblePosition);
int code = 0;
- r->setStart(p.node(), p.m_offset, code);
+ r->setStart(p.node(), p.deprecatedEditingOffset(), code);
return code == 0;
}
@@ -631,7 +636,7 @@ bool setEnd(Range *r, const VisiblePosition &visiblePosition)
return false;
Position p = rangeCompliantEquivalent(visiblePosition);
int code = 0;
- r->setEnd(p.node(), p.m_offset, code);
+ r->setEnd(p.node(), p.deprecatedEditingOffset(), code);
return code == 0;
}
diff --git a/WebCore/editing/VisiblePosition.h b/WebCore/editing/VisiblePosition.h
index 403c816..d888806 100644
--- a/WebCore/editing/VisiblePosition.h
+++ b/WebCore/editing/VisiblePosition.h
@@ -47,6 +47,8 @@ namespace WebCore {
class InlineBox;
+enum StayInEditableContent { MayLeaveEditableContent, MustStayInEditableContent };
+
class VisiblePosition {
public:
// NOTE: UPSTREAM affinity will be used only if pos is at end of a wrapped line,
@@ -64,6 +66,8 @@ public:
EAffinity affinity() const { ASSERT(m_affinity == UPSTREAM || m_affinity == DOWNSTREAM); return m_affinity; }
void setAffinity(EAffinity affinity) { m_affinity = affinity; }
+ // FIXME: Change the following functions' parameter from a boolean to StayInEditableContent.
+
// next() and previous() will increment/decrement by a character cluster.
VisiblePosition next(bool stayInEditableContent = false) const;
VisiblePosition previous(bool stayInEditableContent = false) const;
@@ -73,8 +77,8 @@ public:
VisiblePosition left(bool stayInEditableContent = false) const;
VisiblePosition right(bool stayInEditableContent = false) const;
- UChar characterAfter() const;
- UChar characterBefore() const { return previous().characterAfter(); }
+ UChar32 characterAfter() const;
+ UChar32 characterBefore() const { return previous().characterAfter(); }
void debugPosition(const char* msg = "") const;
diff --git a/WebCore/editing/VisibleSelection.cpp b/WebCore/editing/VisibleSelection.cpp
index 279adf2..56ad6b3 100644
--- a/WebCore/editing/VisibleSelection.cpp
+++ b/WebCore/editing/VisibleSelection.cpp
@@ -169,7 +169,7 @@ PassRefPtr<Range> VisibleSelection::toNormalizedRange() const
ASSERT(isRange());
s = m_start.downstream();
e = m_end.upstream();
- if (Range::compareBoundaryPoints(s.node(), s.m_offset, e.node(), e.m_offset) > 0) {
+ if (comparePositions(s, e) > 0) {
// Make sure the start is before the end.
// The end can wind up before the start if collapsed whitespace is the only thing selected.
Position tmp = s;
@@ -213,7 +213,7 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos)
Position start(rangeCompliantEquivalent(pos));
searchRange->selectNodeContents(boundary, ec);
- searchRange->setStart(start.node(), start.m_offset, ec);
+ searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec);
ASSERT(!ec);
if (ec)
@@ -222,6 +222,11 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos)
return searchRange.release();
}
+bool VisibleSelection::isAll(StayInEditableContent stayInEditableContent) const
+{
+ return !shadowTreeRootNode() && visibleStart().previous(stayInEditableContent).isNull() && visibleEnd().next(stayInEditableContent).isNull();
+}
+
void VisibleSelection::appendTrailingWhitespace()
{
RefPtr<Range> searchRange = makeSearchRange(m_end);
@@ -588,13 +593,13 @@ void VisibleSelection::debugPosition() const
if (m_start == m_end) {
Position pos = m_start;
- fprintf(stderr, "pos: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset);
+ fprintf(stderr, "pos: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.deprecatedEditingOffset());
} else {
Position pos = m_start;
- fprintf(stderr, "start: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset);
+ fprintf(stderr, "start: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.deprecatedEditingOffset());
fprintf(stderr, "-----------------------------------\n");
pos = m_end;
- fprintf(stderr, "end: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset);
+ fprintf(stderr, "end: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.deprecatedEditingOffset());
fprintf(stderr, "-----------------------------------\n");
}
@@ -628,7 +633,7 @@ void VisibleSelection::showTreeForThis() const
{
if (start().node()) {
start().node()->showTreeAndMark(start().node(), "S", end().node(), "E");
- fprintf(stderr, "start offset: %d, end offset: %d\n", start().m_offset, end().m_offset);
+ fprintf(stderr, "start offset: %d, end offset: %d\n", start().deprecatedEditingOffset(), end().deprecatedEditingOffset());
}
}
diff --git a/WebCore/editing/VisibleSelection.h b/WebCore/editing/VisibleSelection.h
index ae2142d..e346b27 100644
--- a/WebCore/editing/VisibleSelection.h
+++ b/WebCore/editing/VisibleSelection.h
@@ -42,7 +42,7 @@ public:
VisibleSelection();
VisibleSelection(const Position&, EAffinity);
- VisibleSelection(const Position&, const Position&, EAffinity);
+ VisibleSelection(const Position&, const Position&, EAffinity = SEL_DEFAULT_AFFINITY);
VisibleSelection(const Range*, EAffinity = SEL_DEFAULT_AFFINITY);
@@ -76,6 +76,8 @@ public:
bool isBaseFirst() const { return m_baseIsFirst; }
+ bool isAll(StayInEditableContent) const;
+
void appendTrailingWhitespace();
bool expandUsingGranularity(TextGranularity granularity);
diff --git a/WebCore/editing/gtk/SelectionControllerGtk.cpp b/WebCore/editing/gtk/SelectionControllerGtk.cpp
new file mode 100644
index 0000000..52fbbab
--- /dev/null
+++ b/WebCore/editing/gtk/SelectionControllerGtk.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009 Igalia S.L.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+#include "SelectionController.h"
+
+#include "AXObjectCache.h"
+#include "Frame.h"
+
+#include <gtk/gtk.h>
+
+namespace WebCore {
+
+void SelectionController::notifyAccessibilityForSelectionChange()
+{
+ if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull()) {
+ RenderObject* focusedNode = m_sel.start().node()->renderer();
+ AccessibilityObject* accessibilityObject = m_frame->document()->axObjectCache()->getOrCreate(focusedNode);
+ AtkObject* wrapper = accessibilityObject->wrapper();
+ if (ATK_IS_TEXT(wrapper)) {
+ g_signal_emit_by_name(wrapper, "text-caret-moved", m_sel.start().offsetInContainerNode());
+
+ if (m_sel.isRange())
+ g_signal_emit_by_name(wrapper, "text-selection-changed");
+ }
+ }
+}
+
+} // namespace WebCore
diff --git a/WebCore/editing/htmlediting.cpp b/WebCore/editing/htmlediting.cpp
index 055c3a7..7b51295 100644
--- a/WebCore/editing/htmlediting.cpp
+++ b/WebCore/editing/htmlediting.cpp
@@ -98,8 +98,8 @@ int comparePositions(const Position& a, const Position& b)
ASSERT(nodeA);
Node* nodeB = b.node();
ASSERT(nodeB);
- int offsetA = a.m_offset;
- int offsetB = b.m_offset;
+ int offsetA = a.deprecatedEditingOffset();
+ int offsetB = b.deprecatedEditingOffset();
Node* shadowAncestorA = nodeA->shadowAncestorNode();
if (shadowAncestorA == nodeA)
@@ -126,6 +126,11 @@ int comparePositions(const Position& a, const Position& b)
return result ? result : bias;
}
+int comparePositions(const VisiblePosition& a, const VisiblePosition& b)
+{
+ return comparePositions(a.deepEquivalent(), b.deepEquivalent());
+}
+
Node* highestEditableRoot(const Position& position)
{
Node* node = position.node();
@@ -322,17 +327,17 @@ Position rangeCompliantEquivalent(const Position& pos)
Node* node = pos.node();
- if (pos.m_offset <= 0) {
+ if (pos.deprecatedEditingOffset() <= 0) {
if (node->parentNode() && (editingIgnoresContent(node) || isTableElement(node)))
return positionBeforeNode(node);
return Position(node, 0);
}
if (node->offsetInCharacters())
- return Position(node, min(node->maxCharacterOffset(), pos.m_offset));
+ return Position(node, min(node->maxCharacterOffset(), pos.deprecatedEditingOffset()));
int maxCompliantOffset = node->childNodeCount();
- if (pos.m_offset > maxCompliantOffset) {
+ if (pos.deprecatedEditingOffset() > maxCompliantOffset) {
if (node->parentNode())
return positionAfterNode(node);
@@ -342,12 +347,12 @@ Position rangeCompliantEquivalent(const Position& pos)
}
// Editing should never generate positions like this.
- if ((pos.m_offset < maxCompliantOffset) && editingIgnoresContent(node)) {
+ if ((pos.deprecatedEditingOffset() < maxCompliantOffset) && editingIgnoresContent(node)) {
ASSERT_NOT_REACHED();
return node->parentNode() ? positionBeforeNode(node) : Position(node, 0);
}
- if (pos.m_offset == maxCompliantOffset && (editingIgnoresContent(node) || isTableElement(node)))
+ if (pos.deprecatedEditingOffset() == maxCompliantOffset && (editingIgnoresContent(node) || isTableElement(node)))
return positionAfterNode(node);
return Position(pos);
@@ -906,14 +911,25 @@ int caretMaxOffset(const Node* n)
return lastOffsetForEditing(n);
}
-bool lineBreakExistsAtPosition(const VisiblePosition& visiblePosition)
+bool lineBreakExistsAtVisiblePosition(const VisiblePosition& visiblePosition)
{
- if (visiblePosition.isNull())
+ return lineBreakExistsAtPosition(visiblePosition.deepEquivalent().downstream());
+}
+
+bool lineBreakExistsAtPosition(const Position& position)
+{
+ if (position.isNull())
return false;
-
- Position downstream(visiblePosition.deepEquivalent().downstream());
- return downstream.node()->hasTagName(brTag) ||
- (downstream.node()->isTextNode() && downstream.node()->renderer()->style()->preserveNewline() && visiblePosition.characterAfter() == '\n');
+
+ if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode())
+ return true;
+
+ if (!position.anchorNode()->isTextNode() || !position.anchorNode()->renderer()->style()->preserveNewline())
+ return false;
+
+ Text* textNode = static_cast<Text*>(position.anchorNode());
+ unsigned offset = position.offsetInContainerNode();
+ return offset < textNode->length() && textNode->data()[offset] == '\n';
}
// Modifies selections that have an end point at the edge of a table
diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h
index ece5e29..374b512 100644
--- a/WebCore/editing/htmlediting.h
+++ b/WebCore/editing/htmlediting.h
@@ -37,9 +37,9 @@ class HTMLElement;
class Node;
class Position;
class Range;
-class VisibleSelection;
class String;
class VisiblePosition;
+class VisibleSelection;
Position rangeCompliantEquivalent(const Position&);
Position rangeCompliantEquivalent(const VisiblePosition&);
@@ -51,6 +51,7 @@ Node* highestEditableRoot(const Position&);
VisiblePosition firstEditablePositionAfterPositionInRoot(const Position&, Node*);
VisiblePosition lastEditablePositionBeforePositionInRoot(const Position&, Node*);
int comparePositions(const Position&, const Position&);
+int comparePositions(const VisiblePosition&, const VisiblePosition&);
Node* lowestEditableAncestor(Node*);
bool isContentEditable(const Node*);
Position nextCandidate(const Position&);
@@ -127,7 +128,8 @@ Node* highestAncestor(Node*);
bool isTableElement(Node*);
bool isTableCell(const Node*);
-bool lineBreakExistsAtPosition(const VisiblePosition&);
+bool lineBreakExistsAtPosition(const Position&);
+bool lineBreakExistsAtVisiblePosition(const VisiblePosition&);
VisibleSelection selectionForParagraphIteration(const VisibleSelection&);
diff --git a/WebCore/editing/mac/SelectionControllerMac.mm b/WebCore/editing/mac/SelectionControllerMac.mm
index 03e1051..5970f99 100644
--- a/WebCore/editing/mac/SelectionControllerMac.mm
+++ b/WebCore/editing/mac/SelectionControllerMac.mm
@@ -38,7 +38,7 @@ void SelectionController::notifyAccessibilityForSelectionChange()
Document* document = m_frame->document();
if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull())
- document->axObjectCache()->postNotification(m_sel.start().node()->renderer(), "AXSelectedTextChanged");
+ document->axObjectCache()->postNotification(m_sel.start().node()->renderer(), "AXSelectedTextChanged", false);
// if zoom feature is enabled, insertion point changes should update the zoom
if (!UAZoomEnabled() || !m_sel.isCaret())
diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp
index 7de5287..d6fe1ce 100644
--- a/WebCore/editing/markup.cpp
+++ b/WebCore/editing/markup.cpp
@@ -30,6 +30,7 @@
#include "CharacterNames.h"
#include "Comment.h"
#include "CSSComputedStyleDeclaration.h"
+#include "CSSMutableStyleDeclaration.h"
#include "CSSPrimitiveValue.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
@@ -190,7 +191,7 @@ static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& u
{
UChar quoteChar = '\"';
String strippedURLString = urlString.stripWhiteSpace();
- if (protocolIs(strippedURLString, "javascript")) {
+ if (protocolIsJavaScript(strippedURLString)) {
// minimal escaping for javascript urls
if (strippedURLString.contains('"')) {
if (strippedURLString.contains('\''))
@@ -387,7 +388,14 @@ static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
append(result, ">");
}
-static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
+static void removeExteriorStyles(CSSMutableStyleDeclaration* style)
+{
+ style->removeProperty(CSSPropertyFloat);
+}
+
+enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
+
+static void appendStartMarkup(Vector<UChar>& result, const Node* node, const Range* range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0, RangeFullySelectsNode rangeFullySelectsNode = DoesFullySelectNode)
{
bool documentIsHTML = node->document()->isHTMLDocument();
switch (node->nodeType()) {
@@ -439,7 +447,7 @@ static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Ran
const Element* el = static_cast<const Element*>(node);
bool convert = convertBlocksToInlines && isBlock(const_cast<Node*>(node));
append(result, el->nodeNamePreservingCase());
- NamedAttrMap *attrs = el->attributes();
+ NamedNodeMap *attrs = el->attributes();
unsigned length = attrs->length();
if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
@@ -502,6 +510,10 @@ static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Ran
}
if (convert)
style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
+ // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
+ // only the ones that affect it and the nodes within it.
+ if (rangeFullySelectsNode == DoesNotFullySelectNode)
+ removeExteriorStyles(style.get());
if (style->length() > 0) {
DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
append(result, stylePrefix);
@@ -536,10 +548,10 @@ static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Ran
}
}
-static String getStartMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
+static String getStartMarkup(const Node* node, const Range* range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0, RangeFullySelectsNode rangeFullySelectsNode = DoesFullySelectNode)
{
Vector<UChar> result;
- appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces);
+ appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces, rangeFullySelectsNode);
return String::adopt(result);
}
@@ -642,7 +654,7 @@ static void completeURLs(Node* node, const String& baseURL)
for (Node* n = node; n != end; n = n->traverseNextNode()) {
if (n->isElementNode()) {
Element* e = static_cast<Element*>(n);
- NamedAttrMap* attrs = e->attributes();
+ NamedNodeMap* attrs = e->attributes();
unsigned length = attrs->length();
for (unsigned i = 0; i < length; i++) {
Attribute* attr = attrs->attributeItem(i);
@@ -742,6 +754,15 @@ static bool isSpecialAncestorBlock(Node* node)
node->hasTagName(h5Tag);
}
+static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CSSMutableStyleDeclaration* style)
+{
+ if (fullySelectedRoot->isElementNode() && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
+ return true;
+
+ return style->getPropertyCSSValue(CSSPropertyBackgroundImage) ||
+ style->getPropertyCSSValue(CSSPropertyBackgroundColor);
+}
+
// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
// FIXME: At least, annotation and style info should probably not be included in range.markupString()
String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines)
@@ -798,14 +819,20 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
markups.append(interchangeNewlineString);
startNode = visibleStart.next().deepEquivalent().node();
+
+ if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0) >= 0) {
+ if (deleteButton)
+ deleteButton->enable();
+ return interchangeNewlineString;
+ }
}
Node* next;
for (Node* n = startNode; n != pastEnd; n = next) {
-
- // According to <rdar://problem/5730668>, it is possible for n to blow past pastEnd and become null here. This
- // shouldn't be possible. This null check will prevent crashes (but create too much markup) and the ASSERT will
- // hopefully lead us to understanding the problem.
+ // According to <rdar://problem/5730668>, it is possible for n to blow
+ // past pastEnd and become null here. This shouldn't be possible.
+ // This null check will prevent crashes (but create too much markup)
+ // and the ASSERT will hopefully lead us to understanding the problem.
ASSERT(n);
if (!n)
break;
@@ -878,7 +905,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
// We added markup for this node, and we're descending into it. Set it to close eventually.
ancestorsToClose.append(n);
}
-
+
// Include ancestors that aren't completely inside the range but are required to retain
// the structure and appearance of the copied markup.
Node* specialCommonAncestor = 0;
@@ -921,29 +948,29 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
specialCommonAncestor = enclosingAnchor;
Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
- // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if
- // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup.
// FIXME: Do this for all fully selected blocks, not just the body.
Node* fullySelectedRoot = body && *VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0;
- if (annotate && fullySelectedRoot)
- specialCommonAncestor = fullySelectedRoot;
+ RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = fullySelectedRoot ? styleFromMatchedRulesAndInlineDecl(fullySelectedRoot) : 0;
+ if (annotate && fullySelectedRoot) {
+ if (shouldIncludeWrapperForFullySelectedRoot(fullySelectedRoot, fullySelectedRootStyle.get()))
+ specialCommonAncestor = fullySelectedRoot;
+ }
if (specialCommonAncestor && lastClosed) {
// Also include all of the ancestors of lastClosed up to this special ancestor.
for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
- RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot);
// Bring the background attribute over, but not as an attribute because a background attribute on a div
// appears to have no effect.
- if (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
- style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
+ if (!fullySelectedRootStyle->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
+ fullySelectedRootStyle->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
- if (style->length()) {
+ if (fullySelectedRootStyle->length()) {
Vector<UChar> openTag;
DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
append(openTag, divStyle);
- appendAttributeValue(openTag, style->cssText(), documentIsHTML);
+ appendAttributeValue(openTag, fullySelectedRootStyle->cssText(), documentIsHTML);
openTag.append('\"');
openTag.append('>');
preMarkups.append(String::adopt(openTag));
@@ -952,7 +979,9 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
markups.append(divCloseTag);
}
} else {
- preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines));
+ // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode
+ // so that styles that affect the exterior of the node are not included.
+ preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines, 0, DoesNotFullySelectNode));
markups.append(getEndMarkup(ancestor));
}
if (nodes)
diff --git a/WebCore/editing/visible_units.cpp b/WebCore/editing/visible_units.cpp
index 1e8b05b..02e9fb8 100644
--- a/WebCore/editing/visible_units.cpp
+++ b/WebCore/editing/visible_units.cpp
@@ -43,25 +43,35 @@ namespace WebCore {
using namespace HTMLNames;
using namespace WTF::Unicode;
-static int firstNonComplexContextLineBreak(const UChar* characters, int length)
+static int endOfFirstWordBoundaryContext(const UChar* characters, int length)
{
- for (int i = 0; i < length; ++i) {
- if (!hasLineBreakingPropertyComplexContext(characters[i]))
- return i;
+ for (int i = 0; i < length; ) {
+ int first = i;
+ UChar32 ch;
+ U16_NEXT(characters, i, length, ch);
+ if (!requiresContextForWordBoundary(ch))
+ return first;
}
return length;
}
-static int lastNonComplexContextLineBreak(const UChar* characters, int length)
+static int startOfLastWordBoundaryContext(const UChar* characters, int length)
{
- for (int i = length - 1; i >= 0; --i) {
- if (!hasLineBreakingPropertyComplexContext(characters[i]))
- return i;
+ for (int i = length; i > 0; ) {
+ int last = i;
+ UChar32 ch;
+ U16_PREV(characters, 0, i, ch);
+ if (!requiresContextForWordBoundary(ch))
+ return last;
}
- return -1;
+ return 0;
}
-static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned, unsigned))
+enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext };
+
+typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext);
+
+static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction)
{
Position pos = c.deepEquivalent();
Node *n = pos.node();
@@ -86,15 +96,15 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea
unsigned suffixLength = 0;
ExceptionCode ec = 0;
- if (hasLineBreakingPropertyComplexContext(c.characterBefore())) {
+ if (requiresContextForWordBoundary(c.characterBefore())) {
RefPtr<Range> forwardsScanRange(d->createRange());
forwardsScanRange->setEndAfter(boundary, ec);
- forwardsScanRange->setStart(end.node(), end.m_offset, ec);
+ forwardsScanRange->setStart(end.node(), end.deprecatedEditingOffset(), ec);
TextIterator forwardsIterator(forwardsScanRange.get());
while (!forwardsIterator.atEnd()) {
const UChar* characters = forwardsIterator.characters();
int length = forwardsIterator.length();
- int i = firstNonComplexContextLineBreak(characters, length);
+ int i = endOfFirstWordBoundaryContext(characters, length);
string.append(characters, i);
suffixLength += i;
if (i < length)
@@ -103,8 +113,8 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea
}
}
- searchRange->setStart(start.node(), start.m_offset, ec);
- searchRange->setEnd(end.node(), end.m_offset, ec);
+ searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec);
+ searchRange->setEnd(end.node(), end.deprecatedEditingOffset(), ec);
ASSERT(!ec);
if (ec)
@@ -113,6 +123,7 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea
SimplifiedBackwardsTextIterator it(searchRange.get());
unsigned next = 0;
bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE;
+ bool needMoreContext = false;
while (!it.atEnd()) {
// iterate to get chunks until the searchFunction returns a non-zero value.
if (!inTextSecurityMode)
@@ -123,13 +134,18 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea
iteratorString = iteratorString.impl()->secure('x');
string.prepend(iteratorString.characters(), iteratorString.length());
}
-
- next = searchFunction(string.data(), string.size(), string.size() - suffixLength);
+ next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext);
if (next != 0)
break;
it.advance();
}
-
+ if (needMoreContext) {
+ // The last search returned the beginning of the buffer and asked for more context,
+ // but there is no earlier text. Force a search with what's available.
+ next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext);
+ ASSERT(!needMoreContext);
+ }
+
if (it.atEnd() && next == 0) {
pos = it.range()->startPosition();
} else if (next != 0) {
@@ -148,7 +164,7 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea
return VisiblePosition(pos, DOWNSTREAM);
}
-static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned, unsigned))
+static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction)
{
Position pos = c.deepEquivalent();
Node *n = pos.node();
@@ -172,27 +188,28 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF
unsigned prefixLength = 0;
ExceptionCode ec = 0;
- if (hasLineBreakingPropertyComplexContext(c.characterAfter())) {
+ if (requiresContextForWordBoundary(c.characterAfter())) {
RefPtr<Range> backwardsScanRange(d->createRange());
- backwardsScanRange->setEnd(start.node(), start.m_offset, ec);
+ backwardsScanRange->setEnd(start.node(), start.deprecatedEditingOffset(), ec);
SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get());
while (!backwardsIterator.atEnd()) {
const UChar* characters = backwardsIterator.characters();
int length = backwardsIterator.length();
- int i = lastNonComplexContextLineBreak(characters, length);
- string.prepend(characters + i + 1, length - i - 1);
- prefixLength += length - i - 1;
- if (i > -1)
+ int i = startOfLastWordBoundaryContext(characters, length);
+ string.prepend(characters + i, length - i);
+ prefixLength += length - i;
+ if (i > 0)
break;
backwardsIterator.advance();
}
}
searchRange->selectNodeContents(boundary, ec);
- searchRange->setStart(start.node(), start.m_offset, ec);
+ searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec);
TextIterator it(searchRange.get(), true);
unsigned next = 0;
bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE;
+ bool needMoreContext = false;
while (!it.atEnd()) {
// Keep asking the iterator for chunks until the search function
// returns an end value not equal to the length of the string passed to it.
@@ -204,12 +221,17 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF
iteratorString = iteratorString.impl()->secure('x');
string.append(iteratorString.characters(), iteratorString.length());
}
-
- next = searchFunction(string.data(), string.size(), prefixLength);
+ next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext);
if (next != string.size())
break;
it.advance();
}
+ if (needMoreContext) {
+ // The last search returned the end of the buffer and asked for more context,
+ // but there is no further text. Force a search with what's available.
+ next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext);
+ ASSERT(!needMoreContext);
+ }
if (it.atEnd() && next == string.size()) {
pos = it.range()->startPosition();
@@ -233,11 +255,14 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF
// ---------
-static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset)
+static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
{
ASSERT(offset);
- if (lastNonComplexContextLineBreak(characters, offset) == -1)
+ if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) {
+ needMoreContext = true;
return 0;
+ }
+ needMoreContext = false;
int start, end;
findWordBoundary(characters, length, offset - 1, &start, &end);
return start;
@@ -260,11 +285,14 @@ VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side)
return previousBoundary(p, startWordBoundary);
}
-static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset)
+static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
{
ASSERT(offset <= length);
- if (firstNonComplexContextLineBreak(characters + offset, length - offset) == static_cast<int>(length - offset))
+ if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) {
+ needMoreContext = true;
return length;
+ }
+ needMoreContext = false;
int start, end;
findWordBoundary(characters, length, offset, &start, &end);
return end;
@@ -286,10 +314,13 @@ VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side)
return nextBoundary(p, endWordBoundary);
}
-static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset)
+static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
{
- if (lastNonComplexContextLineBreak(characters, offset) == -1)
+ if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) {
+ needMoreContext = true;
return 0;
+ }
+ needMoreContext = false;
return findNextWordFromIndex(characters, length, offset, false);
}
@@ -299,10 +330,13 @@ VisiblePosition previousWordPosition(const VisiblePosition &c)
return c.honorEditableBoundaryAtOrAfter(prev);
}
-static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset)
+static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
{
- if (firstNonComplexContextLineBreak(characters + offset, length - offset) == static_cast<int>(length - offset))
+ if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) {
+ needMoreContext = true;
return length;
+ }
+ needMoreContext = false;
return findNextWordFromIndex(characters, length, offset, true);
}
@@ -352,7 +386,7 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c)
// There are VisiblePositions at offset 0 in blocks without
// RootInlineBoxes, like empty editable blocks and bordered blocks.
Position p = c.deepEquivalent();
- if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.m_offset == 0)
+ if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0)
return positionAvoidingFirstPositionInTable(c);
return VisiblePosition();
@@ -399,7 +433,7 @@ VisiblePosition startOfLine(const VisiblePosition& c)
// greater than the input position. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space
// style versus lines without that style, which would break before a space by default.
Position p = visPos.deepEquivalent();
- if (p.m_offset > c.deepEquivalent().m_offset && p.node()->isSameNode(c.deepEquivalent().node())) {
+ if (p.deprecatedEditingOffset() > c.deepEquivalent().deprecatedEditingOffset() && p.node()->isSameNode(c.deepEquivalent().node())) {
visPos = c.previous();
if (visPos.isNull())
return VisiblePosition();
@@ -420,7 +454,7 @@ static VisiblePosition endPositionForLine(const VisiblePosition& c)
// There are VisiblePositions at offset 0 in blocks without
// RootInlineBoxes, like empty editable blocks and bordered blocks.
Position p = c.deepEquivalent();
- if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.m_offset == 0)
+ if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0)
return c;
return VisiblePosition();
}
@@ -505,6 +539,15 @@ static Node* previousLeafWithSameEditability(Node* node)
return 0;
}
+static Node* enclosingNodeWithNonInlineRenderer(Node* n)
+{
+ for (Node* p = n; p; p = p->parentNode()) {
+ if (p->renderer() && !p->renderer()->isInline())
+ return p;
+ }
+ return 0;
+}
+
VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int x)
{
Position p = visiblePosition.deepEquivalent();
@@ -534,9 +577,9 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int
// This containing editable block does not have a previous line.
// Need to move back to previous containing editable block in this root editable
// block and find the last root line box in that block.
- Node* startBlock = enclosingBlock(node);
+ Node* startBlock = enclosingNodeWithNonInlineRenderer(node);
Node* n = previousLeafWithSameEditability(node);
- while (n && startBlock == enclosingBlock(n))
+ while (n && startBlock == enclosingNodeWithNonInlineRenderer(n))
n = previousLeafWithSameEditability(n);
while (n) {
if (highestEditableRoot(Position(n, 0)) != highestRoot)
@@ -566,15 +609,15 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int
absPos -= containingBlock->layer()->scrolledContentOffset();
RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer();
Node* node = renderer->node();
- if (editingIgnoresContent(node))
+ if (node && editingIgnoresContent(node))
return Position(node->parent(), node->nodeIndex());
- return renderer->positionForCoordinates(x - absPos.x(), root->topOverflow());
+ return renderer->positionForPoint(IntPoint(x - absPos.x(), root->topOverflow()));
}
// Could not find a previous line. This means we must already be on the first line.
// Move to the start of the content in this block, which effectively moves us
// to the start of the line we're on.
- Node* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement();
+ Element* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement();
return VisiblePosition(rootElement, 0, DOWNSTREAM);
}
@@ -636,9 +679,9 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x)
// This containing editable block does not have a next line.
// Need to move forward to next containing editable block in this root editable
// block and find the first root line box in that block.
- Node* startBlock = enclosingBlock(node);
- Node* n = nextLeafWithSameEditability(node, p.m_offset);
- while (n && startBlock == enclosingBlock(n))
+ Node* startBlock = enclosingNodeWithNonInlineRenderer(node);
+ Node* n = nextLeafWithSameEditability(node, p.deprecatedEditingOffset());
+ while (n && startBlock == enclosingNodeWithNonInlineRenderer(n))
n = nextLeafWithSameEditability(n);
while (n) {
if (highestEditableRoot(Position(n, 0)) != highestRoot)
@@ -667,9 +710,9 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x)
absPos -= containingBlock->layer()->scrolledContentOffset();
RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer();
Node* node = renderer->node();
- if (editingIgnoresContent(node))
+ if (node && editingIgnoresContent(node))
return Position(node->parent(), node->nodeIndex());
- return renderer->positionForCoordinates(x - absPos.x(), root->topOverflow());
+ return renderer->positionForPoint(IntPoint(x - absPos.x(), root->topOverflow()));
}
// Could not find a next line. This means we must already be on the last line.
@@ -681,7 +724,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x)
// ---------
-static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned)
+static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&)
{
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
// FIXME: The following function can return -1; we don't handle that.
@@ -693,7 +736,7 @@ VisiblePosition startOfSentence(const VisiblePosition &c)
return previousBoundary(c, startSentenceBoundary);
}
-static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned)
+static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&)
{
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
return textBreakNext(iterator);
@@ -705,7 +748,7 @@ VisiblePosition endOfSentence(const VisiblePosition &c)
return nextBoundary(c, endSentenceBoundary);
}
-static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned)
+static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&)
{
// FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right.
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
@@ -719,7 +762,7 @@ VisiblePosition previousSentencePosition(const VisiblePosition &c)
return c.honorEditableBoundaryAtOrAfter(prev);
}
-static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned)
+static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&)
{
// FIXME: This is identical to endSentenceBoundary. This isn't right, it needs to
// move to the equivlant position in the following sentence.
@@ -753,7 +796,7 @@ VisiblePosition startOfParagraph(const VisiblePosition& c)
Node* startBlock = enclosingBlock(startNode);
Node *node = startNode;
- int offset = p.m_offset;
+ int offset = p.deprecatedEditingOffset();
Node *n = startNode;
while (n) {
@@ -814,7 +857,7 @@ VisiblePosition endOfParagraph(const VisiblePosition &c)
Node *stayInsideBlock = startBlock;
Node *node = startNode;
- int offset = p.m_offset;
+ int offset = p.deprecatedEditingOffset();
Node *n = startNode;
while (n) {
@@ -1021,4 +1064,178 @@ VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition)
return lastDeepEditingPositionForNode(highestRoot);
}
+static void getLeafBoxesInLogicalOrder(RootInlineBox* rootBox, Vector<InlineBox*>& leafBoxesInLogicalOrder)
+{
+ unsigned char minLevel = 128;
+ unsigned char maxLevel = 0;
+ unsigned count = 0;
+ InlineBox* r = rootBox->firstLeafChild();
+ // First find highest and lowest levels,
+ // and initialize leafBoxesInLogicalOrder with the leaf boxes in visual order.
+ while (r) {
+ if (r->bidiLevel() > maxLevel)
+ maxLevel = r->bidiLevel();
+ if (r->bidiLevel() < minLevel)
+ minLevel = r->bidiLevel();
+ leafBoxesInLogicalOrder.append(r);
+ r = r->nextLeafChild();
+ ++count;
+ }
+
+ if (rootBox->renderer()->style()->visuallyOrdered())
+ return;
+ // Reverse of reordering of the line (L2 according to Bidi spec):
+ // L2. From the highest level found in the text to the lowest odd level on each line,
+ // reverse any contiguous sequence of characters that are at that level or higher.
+
+ // Reversing the reordering of the line is only done up to the lowest odd level.
+ if (!(minLevel % 2))
+ minLevel++;
+
+ InlineBox** end = leafBoxesInLogicalOrder.end();
+ while (minLevel <= maxLevel) {
+ InlineBox** iter = leafBoxesInLogicalOrder.begin();
+ while (iter != end) {
+ while (iter != end) {
+ if ((*iter)->bidiLevel() >= minLevel)
+ break;
+ ++iter;
+ }
+ InlineBox** first = iter;
+ while (iter != end) {
+ if ((*iter)->bidiLevel() < minLevel)
+ break;
+ ++iter;
+ }
+ InlineBox** last = iter;
+ std::reverse(first, last);
+ }
+ ++minLevel;
+ }
+}
+
+static void getLogicalStartBoxAndNode(RootInlineBox* rootBox, InlineBox*& startBox, Node*& startNode)
+{
+ Vector<InlineBox*> leafBoxesInLogicalOrder;
+ getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+ startBox = 0;
+ startNode = 0;
+ for (size_t i = 0; i < leafBoxesInLogicalOrder.size(); ++i) {
+ startBox = leafBoxesInLogicalOrder[i];
+ startNode = startBox->renderer()->node();
+ if (startNode)
+ return;
+ }
+}
+
+static void getLogicalEndBoxAndNode(RootInlineBox* rootBox, InlineBox*& endBox, Node*& endNode)
+{
+ Vector<InlineBox*> leafBoxesInLogicalOrder;
+ getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+ endBox = 0;
+ endNode = 0;
+ // Generated content (e.g. list markers and CSS :before and :after
+ // pseudoelements) have no corresponding DOM element, and so cannot be
+ // represented by a VisiblePosition. Use whatever precedes instead.
+ for (size_t i = leafBoxesInLogicalOrder.size(); i > 0; --i) {
+ endBox = leafBoxesInLogicalOrder[i - 1];
+ endNode = endBox->renderer()->node();
+ if (endNode)
+ return;
+ }
+}
+
+static VisiblePosition logicalStartPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox* rootBox = rootBoxForLine(c);
+ if (!rootBox) {
+ // There are VisiblePositions at offset 0 in blocks without
+ // RootInlineBoxes, like empty editable blocks and bordered blocks.
+ Position p = c.deepEquivalent();
+ if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset())
+ return positionAvoidingFirstPositionInTable(c);
+
+ return VisiblePosition();
+ }
+
+ InlineBox* logicalStartBox;
+ Node* logicalStartNode;
+ getLogicalStartBoxAndNode(rootBox, logicalStartBox, logicalStartNode);
+
+ if (!logicalStartNode)
+ return VisiblePosition();
+
+ int startOffset = logicalStartBox->caretMinOffset();
+
+ VisiblePosition visPos = VisiblePosition(logicalStartNode, startOffset, DOWNSTREAM);
+ return positionAvoidingFirstPositionInTable(visPos);
+}
+
+VisiblePosition logicalStartOfLine(const VisiblePosition& c)
+{
+ VisiblePosition visPos = logicalStartPositionForLine(c);
+
+ if (visPos.isNull())
+ return c.honorEditableBoundaryAtOrAfter(visPos);
+
+ return c.honorEditableBoundaryAtOrAfter(visPos);
+}
+
+static VisiblePosition logicalEndPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox* rootBox = rootBoxForLine(c);
+ if (!rootBox) {
+ // There are VisiblePositions at offset 0 in blocks without
+ // RootInlineBoxes, like empty editable blocks and bordered blocks.
+ Position p = c.deepEquivalent();
+ if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset())
+ return c;
+ return VisiblePosition();
+ }
+
+ InlineBox* logicalEndBox;
+ Node* logicalEndNode;
+ getLogicalEndBoxAndNode(rootBox, logicalEndBox, logicalEndNode);
+ if (!logicalEndNode)
+ return VisiblePosition();
+
+ int endOffset = 1;
+ if (logicalEndNode->hasTagName(brTag))
+ endOffset = 0;
+ else if (logicalEndBox->isInlineTextBox()) {
+ InlineTextBox* endTextBox = static_cast<InlineTextBox*>(logicalEndBox);
+ endOffset = endTextBox->start();
+ if (!endTextBox->isLineBreak())
+ endOffset += endTextBox->len();
+ }
+
+ return VisiblePosition(logicalEndNode, endOffset, VP_UPSTREAM_IF_POSSIBLE);
+}
+
+bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b)
+{
+ return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b);
+}
+
+VisiblePosition logicalEndOfLine(const VisiblePosition& c)
+{
+ VisiblePosition visPos = logicalEndPositionForLine(c);
+
+ // Make sure the end of line is at the same line as the given input position. For a wrapping line, the logical end
+ // position for the not-last-2-lines might incorrectly hand back the logical beginning of the next line.
+ // For example, <div contenteditable dir="rtl" style="line-break:before-white-space">abcdefg abcdefg abcdefg
+ // a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg </div>
+ // In this case, use the previous position of the computed logical end position.
+ if (!inSameLogicalLine(c, visPos))
+ visPos = visPos.previous();
+
+ return c.honorEditableBoundaryAtOrBefore(visPos);
+}
+
}
diff --git a/WebCore/editing/visible_units.h b/WebCore/editing/visible_units.h
index 2663888..a20b588 100644
--- a/WebCore/editing/visible_units.h
+++ b/WebCore/editing/visible_units.h
@@ -53,8 +53,11 @@ VisiblePosition endOfLine(const VisiblePosition &);
VisiblePosition previousLinePosition(const VisiblePosition &, int x);
VisiblePosition nextLinePosition(const VisiblePosition &, int x);
bool inSameLine(const VisiblePosition &, const VisiblePosition &);
+bool inSameLogicalLine(const VisiblePosition &, const VisiblePosition &);
bool isStartOfLine(const VisiblePosition &);
bool isEndOfLine(const VisiblePosition &);
+VisiblePosition logicalStartOfLine(const VisiblePosition &);
+VisiblePosition logicalEndOfLine(const VisiblePosition &);
// paragraphs (perhaps a misnomer, can be divided by line break elements)
VisiblePosition startOfParagraph(const VisiblePosition&);