summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2009-08-11 17:01:47 +0100
committerBen Murdoch <benm@google.com>2009-08-11 18:21:02 +0100
commit0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5 (patch)
tree2943df35f62d885c89d01063cc528dd73b480fea /WebCore/editing
parent7e7a70bfa49a1122b2597a1e6367d89eb4035eca (diff)
downloadexternal_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.zip
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.gz
external_webkit-0bf48ef3be53ddaa52bbead65dfd75bf90e7a2b5.tar.bz2
Merge in WebKit r47029.
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/AppendNodeCommand.cpp2
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp224
-rw-r--r--WebCore/editing/ApplyStyleCommand.h12
-rw-r--r--WebCore/editing/BreakBlockquoteCommand.cpp12
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp13
-rw-r--r--WebCore/editing/DeleteSelectionCommand.cpp33
-rw-r--r--WebCore/editing/EditCommand.cpp12
-rw-r--r--WebCore/editing/EditCommand.h1
-rw-r--r--WebCore/editing/Editor.cpp147
-rw-r--r--WebCore/editing/Editor.h1
-rw-r--r--WebCore/editing/EditorCommand.cpp93
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp244
-rw-r--r--WebCore/editing/IndentOutdentCommand.h8
-rw-r--r--WebCore/editing/InsertNodeBeforeCommand.cpp2
-rw-r--r--WebCore/editing/InsertParagraphSeparatorCommand.cpp18
-rw-r--r--WebCore/editing/InsertTextCommand.cpp16
-rw-r--r--WebCore/editing/RemoveFormatCommand.cpp5
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp26
-rw-r--r--WebCore/editing/SelectionController.cpp51
-rw-r--r--WebCore/editing/SelectionController.h2
-rw-r--r--WebCore/editing/SmartReplaceICU.cpp3
-rw-r--r--WebCore/editing/TextIterator.cpp344
-rw-r--r--WebCore/editing/TextIterator.h52
-rw-r--r--WebCore/editing/TypingCommand.cpp41
-rw-r--r--WebCore/editing/TypingCommand.h6
-rw-r--r--WebCore/editing/gtk/SelectionControllerGtk.cpp4
-rw-r--r--WebCore/editing/haiku/EditorHaiku.cpp45
-rw-r--r--WebCore/editing/htmlediting.cpp126
-rw-r--r--WebCore/editing/htmlediting.h15
-rw-r--r--WebCore/editing/markup.cpp97
-rw-r--r--WebCore/editing/visible_units.cpp3
31 files changed, 1118 insertions, 540 deletions
diff --git a/WebCore/editing/AppendNodeCommand.cpp b/WebCore/editing/AppendNodeCommand.cpp
index 06d0158..ef79e9c 100644
--- a/WebCore/editing/AppendNodeCommand.cpp
+++ b/WebCore/editing/AppendNodeCommand.cpp
@@ -39,7 +39,7 @@ AppendNodeCommand::AppendNodeCommand(PassRefPtr<Element> parent, PassRefPtr<Node
ASSERT(m_node);
ASSERT(!m_node->parent());
- ASSERT(enclosingNodeOfType(Position(m_parent.get(), 0), isContentEditable) || !m_parent->attached());
+ ASSERT(m_parent->isContentEditable() || !m_parent->attached());
}
void AppendNodeCommand::doApply()
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp
index 8d0312b..86ef214 100644
--- a/WebCore/editing/ApplyStyleCommand.cpp
+++ b/WebCore/editing/ApplyStyleCommand.cpp
@@ -98,13 +98,12 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& po
Document* document = position.node() ? position.node()->document() : 0;
if (!document || !document->frame())
return;
-
+
bool useHTMLFormattingTags = !document->frame()->editor()->shouldStyleWithCSS();
-
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
-
+ // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
+ ASSERT(!mutableStyle->getPropertyCSSValue(CSSPropertyTextDecoration) || !mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect));
String styleText("");
-
bool addedDirection = false;
CSSMutableStyleDeclaration::const_iterator end = mutableStyle->end();
for (CSSMutableStyleDeclaration::const_iterator it = mutableStyle->begin(); it != end; ++it) {
@@ -130,12 +129,12 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& po
}
// Add this property
-
- if (property->id() == CSSPropertyWebkitTextDecorationsInEffect) {
- // we have to special-case text decorations
- // FIXME: Why?
+ if (property->id() == CSSPropertyTextDecoration || property->id() == CSSPropertyWebkitTextDecorationsInEffect) {
+ // Always use text-decoration because -webkit-text-decoration-in-effect is internal.
CSSProperty alteredProperty(CSSPropertyTextDecoration, property->value(), property->isImportant());
- styleText += alteredProperty.cssText();
+ // We don't add "text-decoration: none" because it doesn't override the existing text decorations; i.e. redundant
+ if (!equalIgnoringCase(alteredProperty.value()->cssText(), "none"))
+ styleText += alteredProperty.cssText();
} else
styleText += property->cssText();
@@ -232,8 +231,11 @@ bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *prop
RefPtr<CSSValue> value;
if (property->id() == CSSPropertyFontSize)
value = style->getFontSizeCSSValuePreferringKeyword();
+ // We need to use -webkit-text-decorations-in-effect to take care of text decorations set by u, s, and strike
+ else if (property->id() == CSSPropertyTextDecoration)
+ value = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
else
- value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
+ value = style->getPropertyCSSValue(property->id());
if (!value)
return false;
return equalIgnoringCase(value->cssText(), property->value()->cssText());
@@ -300,7 +302,129 @@ PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
styleElement->setAttribute(classAttr, styleSpanClassString());
return styleElement.release();
}
+
+RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDeclaration* style, CSSComputedStyleDeclaration* computedStyle)
+{
+ ASSERT(style);
+ ASSERT(computedStyle);
+ RefPtr<CSSMutableStyleDeclaration> result = style->copy();
+ computedStyle->diff(result.get());
+
+ // If text decorations in effect is not present in the computed style, then there is nothing to remove from result
+ RefPtr<CSSValue> computedValue = computedStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
+ if (!computedValue || !computedValue->isValueList())
+ return result;
+
+ // Take care of both text-decoration and -webkit-text-decorations-in-effect
+ static const int textDecorationProperties[]={CSSPropertyTextDecoration, CSSPropertyWebkitTextDecorationsInEffect};
+ for (size_t n = 0; n < sizeof(textDecorationProperties)/sizeof(textDecorationProperties[1]); n++) {
+ RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(textDecorationProperties[n]);
+ if (!styleValue || !styleValue->isValueList())
+ continue;
+
+ CSSValueList* desiredValueList = static_cast<CSSValueList*>(styleValue.get());
+ CSSValueList* computedValueList = static_cast<CSSValueList*>(computedValue.get());
+ for (size_t i = 0; i < desiredValueList->length(); i++) {
+ if (!computedValueList->hasValue(desiredValueList->item(i))) {
+ result->removeProperty(textDecorationProperties[n]);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+// Editing style properties must be preserved during editing operation.
+// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
+// FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties
+static const int editingStyleProperties[] = {
+ CSSPropertyBackgroundColor,
+ // CSS inheritable properties
+ CSSPropertyBorderCollapse,
+ CSSPropertyColor,
+ CSSPropertyFontFamily,
+ CSSPropertyFontSize,
+ CSSPropertyFontStyle,
+ CSSPropertyFontVariant,
+ CSSPropertyFontWeight,
+ CSSPropertyLetterSpacing,
+ CSSPropertyLineHeight,
+ CSSPropertyOrphans,
+ CSSPropertyTextAlign,
+ CSSPropertyTextIndent,
+ CSSPropertyTextTransform,
+ CSSPropertyWhiteSpace,
+ CSSPropertyWidows,
+ CSSPropertyWordSpacing,
+ CSSPropertyWebkitBorderHorizontalSpacing,
+ CSSPropertyWebkitBorderVerticalSpacing,
+ CSSPropertyWebkitTextDecorationsInEffect,
+ CSSPropertyWebkitTextFillColor,
+ CSSPropertyWebkitTextSizeAdjust,
+ CSSPropertyWebkitTextStrokeColor,
+ CSSPropertyWebkitTextStrokeWidth,
+};
+size_t numEditingStyleProperties = sizeof(editingStyleProperties)/sizeof(editingStyleProperties[0]);
+
+PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle)
+{
+ RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = pos.computedStyle();
+ RefPtr<CSSMutableStyleDeclaration> style = computedStyleAtPosition->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties);
+
+ if (style && pos.node() && pos.node()->computedStyle()) {
+ RenderStyle* renderStyle = pos.node()->computedStyle();
+ // If a node's text fill color is invalid, then its children use
+ // their font-color as their text fill color (they don't
+ // inherit it). Likewise for stroke color.
+ ExceptionCode ec = 0;
+ if (!renderStyle->textFillColor().isValid())
+ style->removeProperty(CSSPropertyWebkitTextFillColor, ec);
+ if (!renderStyle->textStrokeColor().isValid())
+ style->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
+ ASSERT(ec == 0);
+ if (renderStyle->fontDescription().keywordSize())
+ style->setProperty(CSSPropertyFontSize, computedStyleAtPosition->getFontSizeCSSValuePreferringKeyword()->cssText());
+ }
+
+ if (shouldIncludeTypingStyle == IncludeTypingStyle) {
+ CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->typingStyle();
+ if (typingStyle)
+ style->merge(typingStyle);
+ }
+
+ return style.release();
+}
+
+void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration* editingStyle, Position pos)
+{
+ // ReplaceSelectionCommand::handleStyleSpans() requiers that this function only removes the editing style.
+ // If this function was modified in the futureto delete all redundant properties, then add a boolean value to indicate
+ // which one of editingStyleAtPosition or computedStyle is called.
+ RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(pos);
+ style->diff(editingStyle);
+
+ // if alpha value is zero, we don't add the background color.
+ RefPtr<CSSValue> backgroundColor = editingStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
+ if (backgroundColor && backgroundColor->isPrimitiveValue()) {
+ CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(backgroundColor.get());
+ Color color = Color(primitiveValue->getRGBA32Value());
+ ExceptionCode ec;
+ if (color.alpha() == 0)
+ editingStyle->removeProperty(CSSPropertyBackgroundColor, ec);
+ }
+}
+void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* node)
+{
+ ASSERT(node);
+ ASSERT(node->parentNode());
+ RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleAtPosition(Position(node->parentNode(), 0));
+ RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(Position(node, 0));
+ parentStyle->diff(style.get());
+ style->diff(editingStyle);
+}
+
ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel)
: CompositeEditCommand(document)
, m_style(style->makeMutable())
@@ -787,7 +911,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
removeInlineStyle(styleWithoutEmbedding, removeStart, end);
- } else
+ } else
removeInlineStyle(style, removeStart, end);
start = startPosition();
@@ -857,7 +981,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end);
- } else
+ } else
applyInlineStyleToRange(style, start, end);
// Remove dummy style spans created by splitting text elements.
@@ -962,6 +1086,25 @@ bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(
if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag))
return true;
break;
+ case CSSPropertyTextDecoration:
+ case CSSPropertyWebkitTextDecorationsInEffect:
+ ASSERT(property.value());
+ if (property.value()->isValueList()) {
+ CSSValueList* valueList = static_cast<CSSValueList*>(property.value());
+ DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
+ DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
+ // Because style is new style to be applied, we delete element only if the element is not used in style.
+ if (!valueList->hasValue(underline.get()) && elem->hasLocalName(uTag))
+ return true;
+ if (!valueList->hasValue(lineThrough.get()) && (elem->hasLocalName(strikeTag) || elem->hasLocalName(sTag)))
+ return true;
+ } else {
+ // If the value is NOT a list, then it must be "none", in which case we should remove all text decorations.
+ ASSERT(property.value()->cssText() == "none");
+ if (elem->hasLocalName(uTag) || elem->hasLocalName(strikeTag) || elem->hasLocalName(sTag))
+ return true;
+ }
+ break;
}
}
return false;
@@ -1075,11 +1218,17 @@ static bool hasTextDecorationProperty(Node *node)
static Node* highestAncestorWithTextDecoration(Node *node)
{
- Node *result = NULL;
+ ASSERT(node);
+ Node* result = 0;
+ Node* unsplittableElement = unsplittableElementForPosition(Position(node, 0));
for (Node *n = node; n; n = n->parentNode()) {
if (hasTextDecorationProperty(n))
result = n;
+ // Should stop at the editable root (cannot cross editing boundary) and
+ // also stop at the unsplittable element to be consistent with other UAs
+ if (n == unsplittableElement)
+ break;
}
return result;
@@ -1162,32 +1311,35 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl
}
}
-void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* node, bool force)
+void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, bool forceNegate)
{
- Node *highestAncestor = highestAncestorWithTextDecoration(node);
-
- if (highestAncestor) {
- Node *nextCurrent;
- Node *nextChild;
- for (Node *current = highestAncestor; current != node; current = nextCurrent) {
- ASSERT(current);
-
- nextCurrent = NULL;
-
- RefPtr<CSSMutableStyleDeclaration> decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
+ ASSERT(targetNode);
+ Node* highestAncestor = highestAncestorWithTextDecoration(targetNode);
+ if (!highestAncestor)
+ return;
- for (Node *child = current->firstChild(); child; child = nextChild) {
- nextChild = child->nextSibling();
+ // The outer loop is traversing the tree vertically from highestAncestor to targetNode
+ Node* current = highestAncestor;
+ while (current != targetNode) {
+ ASSERT(current);
+ ASSERT(current->contains(targetNode));
+ RefPtr<CSSMutableStyleDeclaration> decoration = forceNegate ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
+
+ // The inner loop will go through children on each level
+ Node* child = current->firstChild();
+ while (child) {
+ Node* nextChild = child->nextSibling();
+
+ // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode
+ if (child != targetNode)
+ applyTextDecorationStyle(child, decoration.get());
+
+ // We found the next node for the outer loop (contains targetNode)
+ // When reached targetNode, stop the outer loop upon the completion of the current inner loop
+ if (child == targetNode || child->contains(targetNode))
+ current = child;
- if (node == child) {
- nextCurrent = child;
- } else if (node->isDescendantOf(child)) {
- applyTextDecorationStyle(child, decoration.get());
- nextCurrent = child;
- } else {
- applyTextDecorationStyle(child, decoration.get());
- }
- }
+ child = nextChild;
}
}
}
diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h
index 74fe605..29aa483 100644
--- a/WebCore/editing/ApplyStyleCommand.h
+++ b/WebCore/editing/ApplyStyleCommand.h
@@ -73,7 +73,7 @@ private:
PassRefPtr<CSSMutableStyleDeclaration> extractTextDecorationStyle(Node*);
PassRefPtr<CSSMutableStyleDeclaration> extractAndNegateTextDecorationStyle(Node*);
void applyTextDecorationStyle(Node*, CSSMutableStyleDeclaration *style);
- void pushDownTextDecorationStyleAroundNode(Node*, bool force);
+ void pushDownTextDecorationStyleAroundNode(Node*, bool forceNegate);
void pushDownTextDecorationStyleAtBoundaries(const Position& start, const Position& end);
// style-application helpers
@@ -114,6 +114,16 @@ private:
bool isStyleSpan(const Node*);
PassRefPtr<HTMLElement> createStyleSpanElement(Document*);
+RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDeclaration* style, CSSComputedStyleDeclaration* computedStyle);
+
+enum ShouldIncludeTypingStyle {
+ IncludeTypingStyle,
+ IgnoreTypingStyle
+};
+
+PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position, ShouldIncludeTypingStyle = IgnoreTypingStyle);
+void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration*, Position);
+void removeStylesAddedByNode(CSSMutableStyleDeclaration*, Node*);
} // namespace WebCore
diff --git a/WebCore/editing/BreakBlockquoteCommand.cpp b/WebCore/editing/BreakBlockquoteCommand.cpp
index e3d66ba..1ca2d87 100644
--- a/WebCore/editing/BreakBlockquoteCommand.cpp
+++ b/WebCore/editing/BreakBlockquoteCommand.cpp
@@ -67,9 +67,11 @@ void BreakBlockquoteCommand::doApply()
RefPtr<Element> breakNode = createBreakElement(document());
+ bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote);
+
// 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)) {
+ // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content.
+ if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) {
insertNodeBefore(breakNode.get(), topBlockquote);
setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM));
rebalanceWhitespace();
@@ -78,9 +80,9 @@ void BreakBlockquoteCommand::doApply()
// 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)) {
+ if (isLastVisPosInNode) {
setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM));
rebalanceWhitespace();
return;
@@ -142,7 +144,7 @@ void BreakBlockquoteCommand::doApply()
while (listChildNode && !listChildNode->hasTagName(liTag))
listChildNode = listChildNode->nextSibling();
if (listChildNode && listChildNode->renderer())
- setNodeAttribute(static_cast<Element*>(clonedChild.get()), startAttr, String::number(static_cast<RenderListItem*>(listChildNode->renderer())->value()));
+ setNodeAttribute(static_cast<Element*>(clonedChild.get()), startAttr, String::number(toRenderListItem(listChildNode->renderer())->value()));
}
appendNode(clonedChild.get(), clonedAncestor.get());
diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp
index 89a0f8a..25f167c 100644
--- a/WebCore/editing/CompositeEditCommand.cpp
+++ b/WebCore/editing/CompositeEditCommand.cpp
@@ -802,7 +802,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap
// too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
RefPtr<CSSMutableStyleDeclaration> styleInEmptyParagraph;
if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
- styleInEmptyParagraph = styleAtPosition(startOfParagraphToMove.deepEquivalent());
+ styleInEmptyParagraph = editingStyleAtPosition(startOfParagraphToMove.deepEquivalent(), IncludeTypingStyle);
// The moved paragraph should assume the block style of the destination.
styleInEmptyParagraph->removeBlockProperties();
}
@@ -891,7 +891,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem()
if (!emptyListItem)
return false;
- RefPtr<CSSMutableStyleDeclaration> style = styleAtPosition(endingSelection().start());
+ RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(endingSelection().start(), IncludeTypingStyle);
Node* listNode = emptyListItem->parentNode();
@@ -988,6 +988,10 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi
VisiblePosition visiblePos(original);
Node* enclosingAnchor = enclosingAnchorElement(original);
Position result = original;
+
+ if (!enclosingAnchor)
+ return result;
+
// Don't avoid block level anchors, because that would insert content into the wrong paragraph.
if (enclosingAnchor && !isBlock(enclosingAnchor)) {
VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor));
@@ -1020,6 +1024,9 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi
pushAnchorElementDown(enclosingAnchor);
enclosingAnchor = enclosingAnchorElement(original);
}
+ if (!enclosingAnchor)
+ return original;
+
result = positionBeforeNode(enclosingAnchor);
}
}
@@ -1034,6 +1041,8 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi
// to determine if the split is necessary. Returns the last split node.
PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor)
{
+ ASSERT(start != end);
+
RefPtr<Node> node;
for (node = start; node && node->parent() != end; node = node->parent()) {
VisiblePosition positionInParent(Position(node->parent(), 0), DOWNSTREAM);
diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp
index 5a0d8fc..d57c241 100644
--- a/WebCore/editing/DeleteSelectionCommand.cpp
+++ b/WebCore/editing/DeleteSelectionCommand.cpp
@@ -44,6 +44,7 @@
#include "Text.h"
#include "TextIterator.h"
#include "visible_units.h"
+#include "ApplyStyleCommand.h"
namespace WebCore {
@@ -259,11 +260,8 @@ static void removeEnclosingAnchorStyle(CSSMutableStyleDeclaration* style, const
Node* enclosingAnchor = enclosingAnchorElement(position);
if (!enclosingAnchor || !enclosingAnchor->parentNode())
return;
-
- RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(enclosingAnchor->parentNode(), 0).computedStyle()->copyInheritableProperties();
- RefPtr<CSSMutableStyleDeclaration> anchorStyle = Position(enclosingAnchor, 0).computedStyle()->copyInheritableProperties();
- parentStyle->diff(anchorStyle.get());
- anchorStyle->diff(style);
+
+ removeStylesAddedByNode(style, enclosingAnchor);
}
void DeleteSelectionCommand::saveTypingStyleState()
@@ -277,19 +275,17 @@ void DeleteSelectionCommand::saveTypingStyleState()
// early return in calculateTypingStyleAfterDelete).
if (m_upstreamStart.node() == m_downstreamEnd.node() && m_upstreamStart.node()->isTextNode())
return;
-
+
// Figure out the typing style in effect before the delete is done.
- RefPtr<CSSComputedStyleDeclaration> computedStyle = positionBeforeTabSpan(m_selectionToDelete.start()).computedStyle();
- m_typingStyle = computedStyle->copyInheritableProperties();
-
+ m_typingStyle = editingStyleAtPosition(positionBeforeTabSpan(m_selectionToDelete.start()));
+
removeEnclosingAnchorStyle(m_typingStyle.get(), m_selectionToDelete.start());
-
+
// If we're deleting into a Mail blockquote, save the style at end() instead of start()
// We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote
- if (nearestMailBlockquote(m_selectionToDelete.start().node())) {
- computedStyle = m_selectionToDelete.end().computedStyle();
- m_deleteIntoBlockquoteStyle = computedStyle->copyInheritableProperties();
- } else
+ if (nearestMailBlockquote(m_selectionToDelete.start().node()))
+ m_deleteIntoBlockquoteStyle = editingStyleAtPosition(m_selectionToDelete.end());
+ else
m_deleteIntoBlockquoteStyle = 0;
}
@@ -364,8 +360,8 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
// make sure empty cell has some height
updateLayout();
RenderObject *r = node->renderer();
- if (r && r->isTableCell() && static_cast<RenderTableCell*>(r)->contentHeight() <= 0)
- insertBlockPlaceholder(Position(node,0));
+ if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0)
+ insertBlockPlaceholder(Position(node, 0));
return;
}
@@ -668,9 +664,8 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.node()))
m_typingStyle = m_deleteIntoBlockquoteStyle;
m_deleteIntoBlockquoteStyle = 0;
-
- RefPtr<CSSComputedStyleDeclaration> endingStyle = computedStyle(m_endingPosition.node());
- endingStyle->diff(m_typingStyle.get());
+
+ prepareEditingStyleToApplyAt(m_typingStyle.get(), m_endingPosition);
if (!m_typingStyle->length())
m_typingStyle = 0;
VisiblePosition visibleEnd(m_endingPosition);
diff --git a/WebCore/editing/EditCommand.cpp b/WebCore/editing/EditCommand.cpp
index fefe658..2301c54 100644
--- a/WebCore/editing/EditCommand.cpp
+++ b/WebCore/editing/EditCommand.cpp
@@ -195,18 +195,6 @@ bool EditCommand::isTypingCommand() const
return false;
}
-PassRefPtr<CSSMutableStyleDeclaration> EditCommand::styleAtPosition(const Position &pos)
-{
- RefPtr<CSSMutableStyleDeclaration> style = positionBeforeTabSpan(pos).computedStyle()->copyInheritableProperties();
-
- // FIXME: It seems misleading to also include the typing style when returning the style at some arbitrary
- // position in the document.
- CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle();
- if (typingStyle)
- style->merge(typingStyle);
-
- return style.release();
-}
void EditCommand::updateLayout() const
{
diff --git a/WebCore/editing/EditCommand.h b/WebCore/editing/EditCommand.h
index c4969f6..7dc4d92 100644
--- a/WebCore/editing/EditCommand.h
+++ b/WebCore/editing/EditCommand.h
@@ -66,7 +66,6 @@ protected:
void setStartingSelection(const VisibleSelection&);
void setEndingSelection(const VisibleSelection&);
- PassRefPtr<CSSMutableStyleDeclaration> styleAtPosition(const Position&);
void updateLayout() const;
private:
diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp
index 2c303f9..b9dfc5d 100644
--- a/WebCore/editing/Editor.cpp
+++ b/WebCore/editing/Editor.cpp
@@ -340,9 +340,10 @@ bool Editor::tryDHTMLCopy()
if (m_frame->selection()->isInPasswordField())
return false;
- // Must be done before oncopy adds types and data to the pboard,
- // also done for security, as it erases data from the last copy/paste.
- Pasteboard::generalPasteboard()->clear();
+ if (canCopy())
+ // Must be done before oncopy adds types and data to the pboard,
+ // also done for security, as it erases data from the last copy/paste.
+ Pasteboard::generalPasteboard()->clear();
return !dispatchCPPEvent(eventNames().copyEvent, ClipboardWritable);
}
@@ -351,10 +352,11 @@ bool Editor::tryDHTMLCut()
{
if (m_frame->selection()->isInPasswordField())
return false;
-
- // Must be done before oncut adds types and data to the pboard,
- // also done for security, as it erases data from the last copy/paste.
- Pasteboard::generalPasteboard()->clear();
+
+ if (canCut())
+ // Must be done before oncut adds types and data to the pboard,
+ // also done for security, as it erases data from the last copy/paste.
+ Pasteboard::generalPasteboard()->clear();
return !dispatchCPPEvent(eventNames().cutEvent, ClipboardWritable);
}
@@ -653,9 +655,9 @@ PassRefPtr<Node> Editor::increaseSelectionListLevelOrdered()
if (!canEditRichly() || m_frame->selection()->isNone())
return 0;
- PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(m_frame->document());
+ RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(m_frame->document());
revealSelectionAfterEditingOperation();
- return newList;
+ return newList.release();
}
PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered()
@@ -663,9 +665,9 @@ PassRefPtr<Node> Editor::increaseSelectionListLevelUnordered()
if (!canEditRichly() || m_frame->selection()->isNone())
return 0;
- PassRefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(m_frame->document());
+ RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(m_frame->document());
revealSelectionAfterEditingOperation();
- return newList;
+ return newList.release();
}
void Editor::decreaseSelectionListLevel()
@@ -769,66 +771,54 @@ bool Editor::clientIsEditable() const
return client() && client()->isEditable();
}
+// CSS properties that only has a visual difference when applied to text.
+static const int textOnlyProperties[] = {
+ CSSPropertyTextDecoration,
+ CSSPropertyWebkitTextDecorationsInEffect,
+ CSSPropertyFontStyle,
+ CSSPropertyFontWeight,
+ CSSPropertyColor,
+};
+
+static TriState triStateOfStyleInComputedStyle(CSSStyleDeclaration* desiredStyle, CSSComputedStyleDeclaration* computedStyle, bool ignoreTextOnlyProperties = false)
+{
+ RefPtr<CSSMutableStyleDeclaration> diff = getPropertiesNotInComputedStyle(desiredStyle, computedStyle);
+
+ if (ignoreTextOnlyProperties)
+ diff->removePropertiesInSet(textOnlyProperties, sizeof(textOnlyProperties)/sizeof(textOnlyProperties[0]));
+
+ if (!diff->length())
+ return TrueTriState;
+ else if (diff->length() == desiredStyle->length())
+ return FalseTriState;
+ return MixedTriState;
+}
+
bool Editor::selectionStartHasStyle(CSSStyleDeclaration* style) const
{
Node* nodeToRemove;
RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
if (!selectionStyle)
return false;
-
- RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
-
- bool match = true;
- CSSMutableStyleDeclaration::const_iterator end = mutableStyle->end();
- for (CSSMutableStyleDeclaration::const_iterator it = mutableStyle->begin(); it != end; ++it) {
- int propertyID = (*it).id();
- if (!equalIgnoringCase(mutableStyle->getPropertyValue(propertyID), selectionStyle->getPropertyValue(propertyID))) {
- match = false;
- break;
- }
- }
-
+ TriState state = triStateOfStyleInComputedStyle(style, selectionStyle.get());
if (nodeToRemove) {
ExceptionCode ec = 0;
nodeToRemove->remove(ec);
ASSERT(ec == 0);
}
-
- return match;
-}
-
-static void updateState(CSSMutableStyleDeclaration* desiredStyle, CSSComputedStyleDeclaration* computedStyle, bool& atStart, TriState& state)
-{
- CSSMutableStyleDeclaration::const_iterator end = desiredStyle->end();
- for (CSSMutableStyleDeclaration::const_iterator it = desiredStyle->begin(); it != end; ++it) {
- int propertyID = (*it).id();
- String desiredProperty = desiredStyle->getPropertyValue(propertyID);
- String computedProperty = computedStyle->getPropertyValue(propertyID);
- TriState propertyState = equalIgnoringCase(desiredProperty, computedProperty)
- ? TrueTriState : FalseTriState;
- if (atStart) {
- state = propertyState;
- atStart = false;
- } else if (state != propertyState) {
- state = MixedTriState;
- break;
- }
- }
+ return state == TrueTriState;
}
TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const
{
- bool atStart = true;
TriState state = FalseTriState;
- RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
-
if (!m_frame->selection()->isRange()) {
Node* nodeToRemove;
RefPtr<CSSComputedStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
if (!selectionStyle)
return FalseTriState;
- updateState(mutableStyle.get(), selectionStyle.get(), atStart, state);
+ state = triStateOfStyleInComputedStyle(style, selectionStyle.get());
if (nodeToRemove) {
ExceptionCode ec = 0;
nodeToRemove->remove(ec);
@@ -837,10 +827,15 @@ TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const
} else {
for (Node* node = m_frame->selection()->start().node(); node; node = node->traverseNextNode()) {
RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
- if (nodeStyle)
- updateState(mutableStyle.get(), nodeStyle.get(), atStart, state);
- if (state == MixedTriState)
- break;
+ if (nodeStyle) {
+ TriState nodeState = triStateOfStyleInComputedStyle(style, nodeStyle.get(), !node->isTextNode());
+ if (node == m_frame->selection()->start().node())
+ state = nodeState;
+ else if (state != nodeState) {
+ state = MixedTriState;
+ break;
+ }
+ }
if (node == m_frame->selection()->end().node())
break;
}
@@ -1064,9 +1059,9 @@ void Editor::paste()
void Editor::pasteAsPlainText()
{
- if (!canPaste())
+ if (!canPaste())
return;
- pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
+ pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
}
void Editor::performDelete()
@@ -2204,7 +2199,7 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
if (!frame()->editor()->shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertActionTyped))
return;
- frame()->editor()->replaceSelectionWithText(autocorrectedString, false, true);
+ frame()->editor()->replaceSelectionWithText(autocorrectedString, false, false);
// Reset the charet one character further.
frame()->selection()->moveTo(frame()->selection()->end());
@@ -2256,20 +2251,9 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection&
Node* editableNode = searchRange->startContainer();
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();
- }
+
+ if (!editor->spellCheckingEnabledInFocusedNode())
+ return;
// Get the spell checker if it is available
if (!editor->client())
@@ -2287,6 +2271,24 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection&
}
}
+bool Editor::spellCheckingEnabledInFocusedNode() const
+{
+ // Ascend the DOM tree to find a "spellcheck" attribute.
+ // When we find a "spellcheck" attribute, retrieve its value and return false if its value is "false".
+ const Node* node = frame()->document()->focusedNode();
+ while (node) {
+ if (node->isElementNode()) {
+ const WebCore::AtomicString& value = static_cast<const Element*>(node)->getAttribute(spellcheckAttr);
+ if (equalIgnoringCase(value, "true"))
+ return true;
+ if (equalIgnoringCase(value, "false"))
+ return false;
+ }
+ node = node->parent();
+ }
+ return true;
+}
+
void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange)
{
markMisspellingsOrBadGrammar(this, selection, true, firstMisspellingRange);
@@ -2323,7 +2325,10 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
Node* editableNode = spellingRange->startContainer();
if (!editableNode || !editableNode->isContentEditable())
return;
-
+
+ if (!spellCheckingEnabledInFocusedNode())
+ return;
+
// Expand the range to encompass entire paragraphs, since text checking needs that much context.
int spellingRangeStartOffset = 0;
int spellingRangeEndOffset = 0;
diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h
index 67a4b59..5b0cc9c 100644
--- a/WebCore/editing/Editor.h
+++ b/WebCore/editing/Editor.h
@@ -196,6 +196,7 @@ public:
Vector<String> guessesForMisspelledSelection();
Vector<String> guessesForUngrammaticalSelection();
Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical);
+ bool spellCheckingEnabledInFocusedNode() const;
void markMisspellingsAfterTypingToPosition(const VisiblePosition&);
void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange);
void markBadGrammar(const VisibleSelection&);
diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp
index 5a189d4..55750ff 100644
--- a/WebCore/editing/EditorCommand.cpp
+++ b/WebCore/editing/EditorCommand.cpp
@@ -27,6 +27,7 @@
#include "config.h"
#include "AtomicString.h"
+#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
@@ -89,64 +90,85 @@ static Frame* targetFrame(Frame* frame, Event* event)
return node->document()->frame();
}
-static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const String& propertyValue)
+static bool applyCommandToFrame(Frame* frame, EditorCommandSource source, EditAction action, CSSMutableStyleDeclaration* style)
{
- RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
- style->setProperty(propertyID, propertyValue);
// FIXME: We don't call shouldApplyStyle when the source is DOM; is there a good reason for that?
switch (source) {
case CommandFromMenuOrKeyBinding:
- frame->editor()->applyStyleToSelection(style.get(), action);
+ frame->editor()->applyStyleToSelection(style, action);
return true;
case CommandFromDOM:
case CommandFromDOMWithUserInterface:
- frame->editor()->applyStyle(style.get());
+ frame->editor()->applyStyle(style);
return true;
}
ASSERT_NOT_REACHED();
return false;
}
-static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const char* propertyValue)
+static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const String& propertyValue)
{
- return executeApplyStyle(frame, source, action, propertyID, String(propertyValue));
+ RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
+ style->setProperty(propertyID, propertyValue);
+ return applyCommandToFrame(frame, source, action, style.get());
}
static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, int propertyValue)
{
RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
style->setProperty(propertyID, propertyValue);
- // FIXME: We don't call shouldApplyStyle when the source is DOM; is there a good reason for that?
- switch (source) {
- case CommandFromMenuOrKeyBinding:
- frame->editor()->applyStyleToSelection(style.get(), action);
- return true;
- case CommandFromDOM:
- case CommandFromDOMWithUserInterface:
- frame->editor()->applyStyle(style.get());
- return true;
+ return applyCommandToFrame(frame, source, action, style.get());
+}
+
+// FIXME: executeToggleStyleInList does not handle complicated cases such as <b><u>hello</u>world</b> properly.
+// This function must use Editor::selectionHasStyle to determine the current style but we cannot fix this
+// until https://bugs.webkit.org/show_bug.cgi?id=27818 is resolved.
+static bool executeToggleStyleInList(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, CSSValue* value)
+{
+ ExceptionCode ec = 0;
+ Node* nodeToRemove = 0;
+ RefPtr<CSSComputedStyleDeclaration> selectionStyle = frame->selectionComputedStyle(nodeToRemove);
+ RefPtr<CSSValue> selectedCSSValue = selectionStyle->getPropertyCSSValue(propertyID);
+ String newStyle = "none";
+ if (selectedCSSValue->isValueList()) {
+ RefPtr<CSSValueList> selectedCSSValueList = static_cast<CSSValueList*>(selectedCSSValue.get());
+ if (!selectedCSSValueList->removeAll(value))
+ selectedCSSValueList->append(value);
+ if (selectedCSSValueList->length())
+ newStyle = selectedCSSValueList->cssText();
+
+ } else if (selectedCSSValue->cssText() == "none")
+ newStyle = value->cssText();
+
+ ASSERT(ec == 0);
+ if (nodeToRemove) {
+ nodeToRemove->remove(ec);
+ ASSERT(ec == 0);
}
- ASSERT_NOT_REACHED();
- return false;
+
+ // FIXME: We shouldn't be having to convert new style into text. We should have setPropertyCSSValue.
+ RefPtr<CSSMutableStyleDeclaration> newMutableStyle = CSSMutableStyleDeclaration::create();
+ newMutableStyle->setProperty(propertyID, newStyle,ec);
+ return applyCommandToFrame(frame, source, action, newMutableStyle.get());
}
static bool executeToggleStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const char* offValue, const char* onValue)
{
RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
- style->setProperty(propertyID, onValue);
- style->setProperty(propertyID, frame->editor()->selectionStartHasStyle(style.get()) ? offValue : onValue);
- // FIXME: We don't call shouldApplyStyle when the source is DOM; is there a good reason for that?
- switch (source) {
- case CommandFromMenuOrKeyBinding:
- frame->editor()->applyStyleToSelection(style.get(), action);
- return true;
- case CommandFromDOM:
- case CommandFromDOMWithUserInterface:
- frame->editor()->applyStyle(style.get());
- return true;
- }
- ASSERT_NOT_REACHED();
- return false;
+ style->setProperty(propertyID, onValue); // We need to add this style to pass it to selectionStartHasStyle / selectionHasStyle
+
+ // Style is considered present when
+ // mac: present at the beginning of selection
+ // other: present throughout the selection
+ Settings* settings = frame->document()->settings();
+ bool styleIsPresent;
+ if (settings && settings->editingBehavior() == EditingMacBehavior)
+ styleIsPresent = frame->editor()->selectionStartHasStyle(style.get());
+ else
+ styleIsPresent = frame->editor()->selectionHasStyle(style.get()) == TrueTriState;
+
+ style->setProperty(propertyID, styleIsPresent ? offValue : onValue);
+ return applyCommandToFrame(frame, source, action, style.get());
}
static bool executeApplyParagraphStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const String& propertyValue)
@@ -937,7 +959,8 @@ static bool executeSetMark(Frame* frame, Event*, EditorCommandSource, const Stri
static bool executeStrikethrough(Frame* frame, Event*, EditorCommandSource source, const String&)
{
- return executeToggleStyle(frame, source, EditActionChangeAttributes, CSSPropertyWebkitTextDecorationsInEffect, "none", "line-through");
+ RefPtr<CSSPrimitiveValue> lineThrough = CSSPrimitiveValue::createIdentifier(CSSValueLineThrough);
+ return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, lineThrough.get());
}
static bool executeStyleWithCSS(Frame* frame, Event*, EditorCommandSource, const String& value)
@@ -990,8 +1013,8 @@ static bool executeTranspose(Frame* frame, Event*, EditorCommandSource, const St
static bool executeUnderline(Frame* frame, Event*, EditorCommandSource source, const String&)
{
- // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect.
- return executeToggleStyle(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, "none", "underline");
+ RefPtr<CSSPrimitiveValue> underline = CSSPrimitiveValue::createIdentifier(CSSValueUnderline);
+ return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, underline.get());
}
static bool executeUndo(Frame* frame, Event*, EditorCommandSource, const String&)
diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp
index 0f9b106..8c4ba01 100644
--- a/WebCore/editing/IndentOutdentCommand.cpp
+++ b/WebCore/editing/IndentOutdentCommand.cpp
@@ -33,6 +33,7 @@
#include "InsertLineBreakCommand.h"
#include "InsertListCommand.h"
#include "Range.h"
+#include "DocumentFragment.h"
#include "SplitElementCommand.h"
#include "TextIterator.h"
#include "htmlediting.h"
@@ -57,18 +58,9 @@ static PassRefPtr<HTMLBlockquoteElement> createIndentBlockquoteElement(Document*
return element.release();
}
-static bool isIndentBlockquote(const Node* node)
-{
- if (!node || !node->hasTagName(blockquoteTag) || !node->isElementNode())
- return false;
-
- const Element* elem = static_cast<const Element*>(node);
- return elem->getAttribute(classAttr) == indentBlockquoteString();
-}
-
static bool isListOrIndentBlockquote(const Node* node)
{
- return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || isIndentBlockquote(node));
+ return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag));
}
IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels)
@@ -76,35 +68,115 @@ IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeO
{
}
-// This function is a workaround for moveParagraph's tendency to strip blockquotes. It updates lastBlockquote to point to the
-// correct level for the current paragraph, and returns a pointer to a placeholder br where the insertion should be performed.
-PassRefPtr<Element> IndentOutdentCommand::prepareBlockquoteLevelForInsertion(VisiblePosition& currentParagraph, RefPtr<Element>& lastBlockquote)
+bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCurrentParagraph)
+{
+ // If our selection is not inside a list, bail out.
+ Node* lastNodeInSelectedParagraph = endOfCurrentParagraph.deepEquivalent().node();
+ RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph);
+ if (!listNode)
+ return false;
+
+ // Find the list item enclosing the current paragraph
+ Element* selectedListItem = static_cast<Element*>(enclosingBlock(endOfCurrentParagraph.deepEquivalent().node()));
+ // FIXME: we need to deal with the case where there is no li (malformed HTML)
+ if (!selectedListItem->hasTagName(liTag))
+ return false;
+
+ // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>. Should we?
+ Element* previousList = selectedListItem->previousElementSibling();
+ Element* nextList = selectedListItem->nextElementSibling();
+
+ RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false);
+ insertNodeBefore(newList, selectedListItem);
+ appendParagraphIntoNode(visiblePositionBeforeNode(selectedListItem), visiblePositionAfterNode(selectedListItem), newList.get());
+
+ if (canMergeLists(previousList, newList.get()))
+ mergeIdenticalElements(previousList, newList);
+ if (canMergeLists(newList.get(), nextList))
+ mergeIdenticalElements(newList, nextList);
+
+ return true;
+}
+
+void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& startOfCurrentParagraph, const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& targetBlockquote, Node* nodeToSplitTo)
{
- int currentBlockquoteLevel = 0;
- int lastBlockquoteLevel = 0;
- Node* node = currentParagraph.deepEquivalent().node();
- while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
- currentBlockquoteLevel++;
- node = lastBlockquote.get();
- while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
- lastBlockquoteLevel++;
- while (currentBlockquoteLevel > lastBlockquoteLevel) {
- RefPtr<Element> newBlockquote = createIndentBlockquoteElement(document());
- appendNode(newBlockquote, lastBlockquote);
- lastBlockquote = newBlockquote;
- lastBlockquoteLevel++;
+ Node* enclosingCell = 0;
+
+ if (!targetBlockquote) {
+ // Create a new blockquote and insert it as a child of the enclosing block element. We accomplish
+ // this by splitting all parents of the current paragraph up to that point.
+ targetBlockquote = createIndentBlockquoteElement(document());
+ if (isTableCell(nodeToSplitTo))
+ enclosingCell = nodeToSplitTo;
+ RefPtr<Node> startOfNewBlock = splitTreeToNode(startOfCurrentParagraph.deepEquivalent().node(), nodeToSplitTo);
+ insertNodeBefore(targetBlockquote, startOfNewBlock);
}
- while (currentBlockquoteLevel < lastBlockquoteLevel) {
- lastBlockquote = static_cast<Element*>(enclosingNodeOfType(Position(lastBlockquote->parentNode(), 0), isIndentBlockquote));
- lastBlockquoteLevel--;
+
+ VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+ appendParagraphIntoNode(startOfCurrentParagraph, endOfCurrentParagraph, targetBlockquote.get());
+
+ // Don't put the next paragraph in the blockquote we just created for this paragraph unless
+ // the next paragraph is in the same cell.
+ if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
+ targetBlockquote = 0;
+}
+
+bool IndentOutdentCommand::isAtUnsplittableElement(const Position& pos) const
+{
+ Node* node = pos.node();
+ return node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell);
+}
+
+// Enclose all nodes between start and end by newParent, which is a sibling node of nodes between start and end
+// FIXME: moveParagraph is overly complicated. We need to clean up moveParagraph so that it uses appendParagraphIntoNode
+// or prepare more specialized functions and delete moveParagraph
+void IndentOutdentCommand::appendParagraphIntoNode(const VisiblePosition& start, const VisiblePosition& end, Node* newParent)
+{
+ ASSERT(newParent);
+ ASSERT(newParent->isContentEditable());
+ ASSERT(isStartOfParagraph(start) && isEndOfParagraph(end));
+
+ Position endOfParagraph = end.deepEquivalent().downstream();
+ Node* insertionPoint = newParent->lastChild();// Remember the place to put br later
+ // Look for the beginning of the last paragraph in newParent
+ Node* startOfLastParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node();
+ if (startOfLastParagraph && !startOfLastParagraph->isDescendantOf(newParent))
+ startOfLastParagraph = 0;
+
+ // Extend the range so that we can append wrapping nodes as well if they're containd within the paragraph
+ ExceptionCode ec = 0;
+ RefPtr<Range> selectedRange = createRange(document(), start, end, ec);
+ RefPtr<Range> extendedRange = extendRangeToWrappingNodes(selectedRange, selectedRange.get(), newParent->parentNode());
+ newParent->appendChild(extendedRange->extractContents(ec), ec);
+
+ // If the start of paragraph didn't change by appending nodes, we should insert br to seperate the paragraphs.
+ Node* startOfNewParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node();
+ if (startOfNewParagraph == startOfLastParagraph) {
+ if (insertionPoint)
+ newParent->insertBefore(createBreakElement(document()), insertionPoint->nextSibling(), ec);
+ else
+ newParent->appendChild(createBreakElement(document()), ec);
}
- RefPtr<Element> placeholder = createBreakElement(document());
- appendNode(placeholder, lastBlockquote);
- // Add another br before the placeholder if it collapsed.
- VisiblePosition visiblePos(Position(placeholder.get(), 0));
- if (!isStartOfParagraph(visiblePos))
- insertNodeBefore(createBreakElement(document()), placeholder);
- return placeholder.release();
+
+ // Remove unnecessary br from the place where we moved the paragraph from
+ removeUnnecessaryLineBreakAt(endOfParagraph);
+}
+
+void IndentOutdentCommand::removeUnnecessaryLineBreakAt(const Position& endOfParagraph)
+{
+ // If there is something in this paragraph, then don't remove br.
+ if (!isStartOfParagraph(endOfParagraph) || !isEndOfParagraph(endOfParagraph))
+ return;
+
+ // We only care about br at the end of paragraph
+ Node* br = endOfParagraph.node();
+ Node* parentNode = br->parentNode();
+
+ // If the node isn't br or the parent node is empty, then don't remove.
+ if (!br->hasTagName(brTag) || isVisiblyAdjacent(positionBeforeNode(parentNode), positionAfterNode(parentNode)))
+ return;
+
+ removeNodeAndPruneAncestors(br);
}
void IndentOutdentCommand::indentRegion()
@@ -112,16 +184,15 @@ void IndentOutdentCommand::indentRegion()
VisibleSelection selection = selectionForParagraphIteration(endingSelection());
VisiblePosition startOfSelection = selection.visibleStart();
VisiblePosition endOfSelection = selection.visibleEnd();
- int startIndex = indexForVisiblePosition(startOfSelection);
- int endIndex = indexForVisiblePosition(endOfSelection);
+ RefPtr<Range> selectedRange = selection.firstRange();
ASSERT(!startOfSelection.isNull());
ASSERT(!endOfSelection.isNull());
-
- // Special case empty root editable elements because there's nothing to split
+
+ // Special case empty unsplittable elements because there's nothing to split
// and there's nothing to move.
Position start = startOfSelection.deepEquivalent().downstream();
- if (start.node() == editableRootForPosition(start)) {
+ if (isAtUnsplittableElement(start)) {
RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
insertNodeAt(blockquote, start);
RefPtr<Element> placeholder = createBreakElement(document());
@@ -129,69 +200,42 @@ void IndentOutdentCommand::indentRegion()
setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
return;
}
-
- RefPtr<Element> previousListNode;
- RefPtr<Element> newListNode;
- RefPtr<Element> newBlockquote;
+
+ RefPtr<Element> blockquoteForNextIndent;
VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
while (endOfCurrentParagraph != endAfterSelection) {
// Iterate across the selected paragraphs...
VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
- RefPtr<Element> listNode = enclosingList(endOfCurrentParagraph.deepEquivalent().node());
- RefPtr<Element> insertionPoint;
- if (listNode) {
- RefPtr<Element> placeholder = createBreakElement(document());
- insertionPoint = placeholder;
- newBlockquote = 0;
- RefPtr<Element> listItem = createListItemElement(document());
- if (listNode == previousListNode) {
- // The previous paragraph was inside the same list, so add this list item to the list we already created
- appendNode(listItem, newListNode);
- appendNode(placeholder, listItem);
- } else {
- // Clone the list element, insert it before the current paragraph, and move the paragraph into it.
- RefPtr<Element> clonedList = listNode->cloneElementWithoutChildren();
- insertNodeBefore(clonedList, enclosingListChild(endOfCurrentParagraph.deepEquivalent().node()));
- appendNode(listItem, clonedList);
- appendNode(placeholder, listItem);
- newListNode = clonedList;
- previousListNode = listNode;
- }
- } else if (newBlockquote)
- // The previous paragraph was put into a new blockquote, so move this paragraph there as well
- insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote);
+ if (tryIndentingAsListItem(endOfCurrentParagraph))
+ blockquoteForNextIndent = 0;
else {
- // Create a new blockquote and insert it as a child of the root editable element. We accomplish
- // this by splitting all parents of the current paragraph up to that point.
- RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
- Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
-
- Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
- Node* nodeToSplitTo = enclosingCell ? enclosingCell : editableRootForPosition(start);
- RefPtr<Node> startOfNewBlock = splitTreeToNode(start.node(), nodeToSplitTo);
- insertNodeBefore(blockquote, startOfNewBlock);
- newBlockquote = blockquote;
- insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote);
- // Don't put the next paragraph in the blockquote we just created for this paragraph unless
- // the next paragraph is in the same cell.
- if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
- newBlockquote = 0;
+ VisiblePosition startOfCurrentParagraph = startOfParagraph(endOfCurrentParagraph);
+ Node* blockNode = enclosingBlock(endOfCurrentParagraph.deepEquivalent().node());
+ // extend the region so that it contains all the ancestor blocks within the selection
+ ExceptionCode ec;
+ Element* unsplittableNode = unsplittableElementForPosition(endOfCurrentParagraph.deepEquivalent());
+ RefPtr<Range> originalRange = createRange(document(), endOfCurrentParagraph, endOfCurrentParagraph, ec);
+ RefPtr<Range> extendedRange = extendRangeToWrappingNodes(originalRange, selectedRange.get(), unsplittableNode);
+ if (originalRange != extendedRange) {
+ ExceptionCode ec = 0;
+ endOfCurrentParagraph = endOfParagraph(extendedRange->endPosition().previous());
+ blockNode = enclosingBlock(extendedRange->commonAncestorContainer(ec));
+ }
+
+ endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+ indentIntoBlockquote(startOfCurrentParagraph, endOfCurrentParagraph, blockquoteForNextIndent, blockNode);
+ // blockquoteForNextIndent will be updated in the function
}
- moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true);
- // moveParagraph should not destroy content that contains endOfNextParagraph, but if it does, return here
- // to avoid a crash.
+ // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node()
+ // If somehow we did, return to prevent crashes.
if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) {
ASSERT_NOT_REACHED();
return;
}
endOfCurrentParagraph = endOfNextParagraph;
}
-
- RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
- RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
- if (startRange && endRange)
- setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
+
}
void IndentOutdentCommand::outdentParagraph()
@@ -200,7 +244,7 @@ void IndentOutdentCommand::outdentParagraph()
VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);
Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote);
- if (!enclosingNode || !isContentEditable(enclosingNode->parentNode())) // We can't outdent if there is no place to go!
+ if (!enclosingNode || !enclosingNode->parentNode()->isContentEditable()) // We can't outdent if there is no place to go!
return;
// Use InsertListCommand to remove the selection from the list
@@ -213,7 +257,7 @@ void IndentOutdentCommand::outdentParagraph()
return;
}
- // The selection is inside a blockquote
+ // The selection is inside a blockquote i.e. enclosingNode is a blockquote
VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0));
VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock);
VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(Position(enclosingNode, enclosingNode->childNodeCount()));
@@ -227,9 +271,9 @@ void IndentOutdentCommand::outdentParagraph()
// 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!
+ if (splitPointParent->hasTagName(blockquoteTag)
+ && !splitPoint->hasTagName(blockquoteTag)
+ && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go!
splitElement(static_cast<Element*>(splitPointParent), splitPoint);
}
}
@@ -244,10 +288,14 @@ void IndentOutdentCommand::outdentParagraph()
return;
}
- Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph);
+ Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().node());
RefPtr<Node> splitBlockquoteNode = enclosingNode;
if (enclosingBlockFlow != enclosingNode)
- splitBlockquoteNode = splitTreeToNode(enclosingBlockFlowElement(visibleStartOfParagraph), enclosingNode, true);
+ splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true);
+ else {
+ // We split the blockquote at where we start outdenting.
+ splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().node());
+ }
RefPtr<Node> placeholder = createBreakElement(document());
insertNodeBefore(placeholder, splitBlockquoteNode);
moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true);
diff --git a/WebCore/editing/IndentOutdentCommand.h b/WebCore/editing/IndentOutdentCommand.h
index bb1a1d2..104a0e7 100644
--- a/WebCore/editing/IndentOutdentCommand.h
+++ b/WebCore/editing/IndentOutdentCommand.h
@@ -46,10 +46,16 @@ private:
virtual void doApply();
virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; }
+ // FIXME: Does this belong in htmlediting.cpp?
+ bool isAtUnsplittableElement(const Position&) const;
+ void appendParagraphIntoNode(const VisiblePosition& start, const VisiblePosition& end, Node* newParent);
+ void removeUnnecessaryLineBreakAt(const Position& endOfParagraph);
+
void indentRegion();
void outdentRegion();
void outdentParagraph();
- PassRefPtr<Element> prepareBlockquoteLevelForInsertion(VisiblePosition&, RefPtr<Element>&);
+ bool tryIndentingAsListItem(const VisiblePosition&);
+ void indentIntoBlockquote(const VisiblePosition&, const VisiblePosition&, RefPtr<Element>& targetBlockquote, Node* nodeToSplitTo);
EIndentType m_typeOfAction;
int m_marginInPixels;
diff --git a/WebCore/editing/InsertNodeBeforeCommand.cpp b/WebCore/editing/InsertNodeBeforeCommand.cpp
index c3acd9d..4f60963 100644
--- a/WebCore/editing/InsertNodeBeforeCommand.cpp
+++ b/WebCore/editing/InsertNodeBeforeCommand.cpp
@@ -40,7 +40,7 @@ InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, P
ASSERT(m_refChild);
ASSERT(m_refChild->parentNode());
- ASSERT(enclosingNodeOfType(Position(m_refChild->parentNode(), 0), isContentEditable) || !m_refChild->parentNode()->attached());
+ ASSERT(m_refChild->parentNode()->isContentEditable() || !m_refChild->parentNode()->attached());
}
void InsertNodeBeforeCommand::doApply()
diff --git a/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/WebCore/editing/InsertParagraphSeparatorCommand.cpp
index 734d8fc..c5fbf0a 100644
--- a/WebCore/editing/InsertParagraphSeparatorCommand.cpp
+++ b/WebCore/editing/InsertParagraphSeparatorCommand.cpp
@@ -38,6 +38,7 @@
#include "Text.h"
#include "htmlediting.h"
#include "visible_units.h"
+#include "ApplyStyleCommand.h"
namespace WebCore {
@@ -63,7 +64,7 @@ void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Positi
if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
return;
- m_style = styleAtPosition(pos);
+ m_style = editingStyleAtPosition(pos, IncludeTypingStyle);
}
void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnclosingBlock)
@@ -79,8 +80,9 @@ void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnc
if (!m_style)
return;
+
+ prepareEditingStyleToApplyAt(m_style.get(), endingSelection().start());
- computedStyle(endingSelection().start().node())->diff(m_style.get());
if (m_style->length() > 0)
applyStyle(m_style.get());
}
@@ -170,6 +172,7 @@ void InsertParagraphSeparatorCommand::doApply()
// Handle case when position is in the last visible position in its block,
// including when the block is empty.
if (isLastInBlock) {
+ bool shouldApplyStyleAfterInsertion = true;
if (nestNewBlock) {
if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
// The block is empty. Create an empty block to
@@ -182,13 +185,18 @@ void InsertParagraphSeparatorCommand::doApply()
} 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);
+ if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) {
+ startBlock = static_cast<Element*>(highestBlockquote);
+ // When inserting the newline after the blockquote, we don't want to apply the original style after the insertion
+ shouldApplyStyleAfterInsertion = false;
+ }
+ insertNodeAfter(blockToInsert, startBlock);
}
appendBlockPlaceholder(blockToInsert);
setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
- applyStyleAfterInsertion(startBlock);
+ if (shouldApplyStyleAfterInsertion)
+ applyStyleAfterInsertion(startBlock);
return;
}
diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp
index bf6fd19..6b0af9b 100644
--- a/WebCore/editing/InsertTextCommand.cpp
+++ b/WebCore/editing/InsertTextCommand.cpp
@@ -106,22 +106,14 @@ bool InsertTextCommand::performTrivialReplace(const String& text, bool selectIns
return true;
}
-void InsertTextCommand::input(const String& originalText, bool selectInsertedText)
+void InsertTextCommand::input(const String& text, bool selectInsertedText)
{
- String text = originalText;
ASSERT(text.find('\n') == -1);
if (endingSelection().isNone())
return;
-
- if (RenderObject* renderer = endingSelection().start().node()->renderer())
- if (renderer->style()->collapseWhiteSpace())
- // Turn all spaces into non breaking spaces, to make sure that they are treated
- // literally, and aren't collapsed after insertion. They will be rebalanced
- // (turned into a sequence of regular and non breaking spaces) below.
- text.replace(' ', noBreakSpace);
-
+
// Delete the current selection.
// FIXME: This delete operation blows away the typing style.
if (endingSelection().isRange()) {
@@ -129,7 +121,7 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex
return;
deleteSelection(false, true, true, false);
}
-
+
Position startPosition(endingSelection().start());
Position placeholder;
@@ -184,7 +176,7 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex
// The insertion may require adjusting adjacent whitespace, if it is present.
rebalanceWhitespaceAt(endPosition);
// Rebalancing on both sides isn't necessary if we've inserted a space.
- if (originalText != " ")
+ if (text != " ")
rebalanceWhitespaceAt(startPosition);
m_charactersAdded += text.length();
diff --git a/WebCore/editing/RemoveFormatCommand.cpp b/WebCore/editing/RemoveFormatCommand.cpp
index 6d681ee..9243adc 100644
--- a/WebCore/editing/RemoveFormatCommand.cpp
+++ b/WebCore/editing/RemoveFormatCommand.cpp
@@ -35,6 +35,7 @@
#include "SelectionController.h"
#include "TextIterator.h"
#include "TypingCommand.h"
+#include "ApplyStyleCommand.h"
namespace WebCore {
@@ -55,8 +56,8 @@ void RemoveFormatCommand::doApply()
// Get the default style for this editable root, it's the style that we'll give the
// content that we're operating on.
Node* root = frame->selection()->rootEditableElement();
- RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle(root)->copyInheritableProperties();
-
+ RefPtr<CSSMutableStyleDeclaration> defaultStyle = editingStyleAtPosition(Position(root, 0));
+
// Delete the selected content.
// FIXME: We should be able to leave this to insertText, but its delete operation
// doesn't preserve the style we're about to set.
diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp
index c6da864..8ae083c 100644
--- a/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -60,7 +60,7 @@ enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
// --- ReplacementFragment helper class
-class ReplacementFragment : Noncopyable {
+class ReplacementFragment : public Noncopyable {
public:
ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const VisibleSelection&);
@@ -549,8 +549,9 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const
Node* sourceDocumentStyleSpan = topNode;
RefPtr<Node> copiedRangeStyleSpan = sourceDocumentStyleSpan->firstChild();
-
- RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = rangeCompliantEquivalent(insertionPos).computedStyle()->copyInheritableProperties();
+
+ RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = editingStyleAtPosition(rangeCompliantEquivalent(insertionPos));
+
String styleText = styleAtInsertionPos->cssText();
if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) {
@@ -606,8 +607,8 @@ void ReplaceSelectionCommand::handleStyleSpans()
// styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : nearestMailBlockquote(context);
if (blockquoteNode) {
- RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(blockquoteNode)->copyInheritableProperties();
- RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(blockquoteNode->parentNode())->copyInheritableProperties();
+ RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = editingStyleAtPosition(Position(blockquoteNode, 0));
+ RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleAtPosition(Position(blockquoteNode->parentNode(), 0));
parentStyle->diff(blockquoteStyle.get());
CSSMutableStyleDeclaration::const_iterator end = blockquoteStyle->end();
@@ -618,10 +619,10 @@ void ReplaceSelectionCommand::handleStyleSpans()
context = blockquoteNode->parentNode();
}
-
- RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties();
- contextStyle->diff(sourceDocumentStyle.get());
-
+
+ // This operation requires that only editing styles to be removed from sourceDocumentStyle.
+ prepareEditingStyleToApplyAt(sourceDocumentStyle.get(), Position(context, 0));
+
// Remove block properties in the span's style. This prevents properties that probably have no effect
// currently from affecting blocks later if the style is cloned for a new block element during a future
// editing operation.
@@ -655,9 +656,8 @@ void ReplaceSelectionCommand::handleStyleSpans()
// Remove redundant styles.
context = copiedRangeStyleSpan->parentNode();
- contextStyle = computedStyle(context)->copyInheritableProperties();
- contextStyle->diff(copiedRangeStyle.get());
-
+ prepareEditingStyleToApplyAt(copiedRangeStyle.get(), Position(context, 0));
+
// See the comments above about removing block properties.
copiedRangeStyle->removeBlockProperties();
@@ -725,7 +725,7 @@ void ReplaceSelectionCommand::doApply()
return;
if (m_matchStyle)
- m_insertionStyle = styleAtPosition(selection.start());
+ m_insertionStyle = editingStyleAtPosition(selection.start(), IncludeTypingStyle);
VisiblePosition visibleStart = selection.visibleStart();
VisiblePosition visibleEnd = selection.visibleEnd();
diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp
index d0427c0..b6874d6 100644
--- a/WebCore/editing/SelectionController.cpp
+++ b/WebCore/editing/SelectionController.cpp
@@ -100,6 +100,8 @@ void SelectionController::moveTo(const Position &base, const Position &extent, E
void SelectionController::setSelection(const VisibleSelection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered)
{
+ m_lastChangeWasHorizontalExtension = false;
+
if (m_isDragCaretController) {
invalidateCaretRect();
m_sel = s;
@@ -223,34 +225,30 @@ void SelectionController::nodeWillBeRemoved(Node *node)
void SelectionController::willBeModified(EAlteration alter, EDirection direction)
{
- switch (alter) {
- case MOVE:
- m_lastChangeWasHorizontalExtension = false;
+ if (alter != EXTEND)
+ return;
+ if (m_lastChangeWasHorizontalExtension)
+ return;
+
+ Position start = m_sel.start();
+ Position end = m_sel.end();
+ // FIXME: This is probably not correct for right and left when the direction is RTL.
+ switch (direction) {
+ case RIGHT:
+ case FORWARD:
+ m_sel.setBase(start);
+ m_sel.setExtent(end);
break;
- case EXTEND:
- if (!m_lastChangeWasHorizontalExtension) {
- m_lastChangeWasHorizontalExtension = true;
- Position start = m_sel.start();
- Position end = m_sel.end();
- switch (direction) {
- // FIXME: right for bidi?
- case RIGHT:
- case FORWARD:
- m_sel.setBase(start);
- m_sel.setExtent(end);
- break;
- case LEFT:
- case BACKWARD:
- m_sel.setBase(end);
- m_sel.setExtent(start);
- break;
- }
- }
+ case LEFT:
+ case BACKWARD:
+ m_sel.setBase(end);
+ m_sel.setExtent(start);
break;
}
}
-TextDirection SelectionController::directionOfEnclosingBlock() {
+TextDirection SelectionController::directionOfEnclosingBlock()
+{
Node* n = m_sel.extent().node();
Node* enclosingBlockNode = enclosingBlock(n);
if (!enclosingBlockNode)
@@ -559,8 +557,8 @@ bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranular
{
if (userTriggered) {
SelectionController trialSelectionController;
- trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
trialSelectionController.setSelection(m_sel);
+ trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
trialSelectionController.modify(alter, dir, granularity, false);
bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
@@ -633,6 +631,8 @@ bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranular
setNeedsLayout();
+ m_lastChangeWasHorizontalExtension = alter == EXTEND;
+
return true;
}
@@ -654,6 +654,7 @@ bool SelectionController::modify(EAlteration alter, int verticalDistance, bool u
if (userTriggered) {
SelectionController trialSelectionController;
trialSelectionController.setSelection(m_sel);
+ trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
trialSelectionController.modify(alter, verticalDistance, false);
bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
@@ -1267,7 +1268,7 @@ void SelectionController::focusedOrActiveStateChanged()
node->setNeedsStyleRecalc();
if (RenderObject* renderer = node->renderer())
if (renderer && renderer->style()->hasAppearance())
- theme()->stateChanged(renderer, FocusState);
+ renderer->theme()->stateChanged(renderer, FocusState);
}
// Secure keyboard entry is set by the active frame.
diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h
index bbd343c..4a13a30 100644
--- a/WebCore/editing/SelectionController.h
+++ b/WebCore/editing/SelectionController.h
@@ -38,7 +38,7 @@ class GraphicsContext;
class RenderObject;
class VisiblePosition;
-class SelectionController : Noncopyable {
+class SelectionController : public Noncopyable {
public:
enum EAlteration { MOVE, EXTEND };
enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT };
diff --git a/WebCore/editing/SmartReplaceICU.cpp b/WebCore/editing/SmartReplaceICU.cpp
index 18be647..9acd350 100644
--- a/WebCore/editing/SmartReplaceICU.cpp
+++ b/WebCore/editing/SmartReplaceICU.cpp
@@ -37,7 +37,8 @@
namespace WebCore {
-static void addAllCodePoints(USet* smartSet, const String& string) {
+static void addAllCodePoints(USet* smartSet, const String& string)
+{
const UChar* characters = string.characters();
for (size_t i = 0; i < string.length(); i++)
uset_add(smartSet, characters[i]);
diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp
index b311853..08fda16 100644
--- a/WebCore/editing/TextIterator.cpp
+++ b/WebCore/editing/TextIterator.cpp
@@ -42,6 +42,7 @@
#include "visible_units.h"
#if USE(ICU_UNICODE) && !UCONFIG_NO_COLLATION
+#include "TextBreakIteratorInternalICU.h"
#include <unicode/usearch.h>
#endif
@@ -56,7 +57,7 @@ using namespace HTMLNames;
// Keeps enough of the previous text to be able to search in the future, but no more.
// Non-breaking spaces are always equal to normal spaces.
// Case folding is also done if <isCaseSensitive> is false.
-class SearchBuffer : Noncopyable {
+class SearchBuffer : public Noncopyable {
public:
SearchBuffer(const String& target, bool isCaseSensitive);
~SearchBuffer();
@@ -97,6 +98,148 @@ private:
// --------
+static const unsigned bitsInWord = sizeof(unsigned) * 8;
+static const unsigned bitInWordMask = bitsInWord - 1;
+
+BitStack::BitStack()
+ : m_size(0)
+{
+}
+
+void BitStack::push(bool bit)
+{
+ unsigned index = m_size / bitsInWord;
+ unsigned shift = m_size & bitInWordMask;
+ if (!shift && index == m_words.size()) {
+ m_words.grow(index + 1);
+ m_words[index] = 0;
+ }
+ unsigned& word = m_words[index];
+ unsigned mask = 1U << shift;
+ if (bit)
+ word |= mask;
+ else
+ word &= ~mask;
+ ++m_size;
+}
+
+void BitStack::pop()
+{
+ if (m_size)
+ --m_size;
+}
+
+bool BitStack::top() const
+{
+ if (!m_size)
+ return false;
+ unsigned shift = (m_size - 1) & bitInWordMask;
+ return m_words.last() & (1U << shift);
+}
+
+unsigned BitStack::size() const
+{
+ return m_size;
+}
+
+// --------
+
+static inline Node* parentCrossingShadowBoundaries(Node* node)
+{
+ if (Node* parent = node->parentNode())
+ return parent;
+ return node->shadowParentNode();
+}
+
+#ifndef NDEBUG
+
+static unsigned depthCrossingShadowBoundaries(Node* node)
+{
+ unsigned depth = 0;
+ for (Node* parent = parentCrossingShadowBoundaries(node); parent; parent = parentCrossingShadowBoundaries(parent))
+ ++depth;
+ return depth;
+}
+
+#endif
+
+// This function is like Range::pastLastNode, except for the fact that it can climb up out of shadow trees.
+static Node* nextInPreOrderCrossingShadowBoundaries(Node* rangeEndContainer, int rangeEndOffset)
+{
+ if (!rangeEndContainer)
+ return 0;
+ if (rangeEndOffset >= 0 && !rangeEndContainer->offsetInCharacters()) {
+ if (Node* next = rangeEndContainer->childNode(rangeEndOffset))
+ return next;
+ }
+ for (Node* node = rangeEndContainer; node; node = parentCrossingShadowBoundaries(node)) {
+ if (Node* next = node->nextSibling())
+ return next;
+ }
+ return 0;
+}
+
+static Node* previousInPostOrderCrossingShadowBoundaries(Node* rangeStartContainer, int rangeStartOffset)
+{
+ if (!rangeStartContainer)
+ return 0;
+ if (rangeStartOffset > 0 && !rangeStartContainer->offsetInCharacters()) {
+ if (Node* previous = rangeStartContainer->childNode(rangeStartOffset - 1))
+ return previous;
+ }
+ for (Node* node = rangeStartContainer; node; node = parentCrossingShadowBoundaries(node)) {
+ if (Node* previous = node->previousSibling())
+ return previous;
+ }
+ return 0;
+}
+
+// --------
+
+static inline bool fullyClipsContents(Node* node)
+{
+ RenderObject* renderer = node->renderer();
+ if (!renderer || !renderer->isBox() || !renderer->hasOverflowClip())
+ return false;
+ return toRenderBox(renderer)->size().isEmpty();
+}
+
+static inline bool ignoresContainerClip(Node* node)
+{
+ RenderObject* renderer = node->renderer();
+ if (!renderer || renderer->isText())
+ return false;
+ EPosition position = renderer->style()->position();
+ return position == AbsolutePosition || position == FixedPosition;
+}
+
+static void pushFullyClippedState(BitStack& stack, Node* node)
+{
+ ASSERT(stack.size() == depthCrossingShadowBoundaries(node));
+
+ // Push true if this node full clips its contents, or if a parent already has fully
+ // clipped and this is not a node that ignores its container's clip.
+ stack.push(fullyClipsContents(node) || stack.top() && !ignoresContainerClip(node));
+}
+
+static void setUpFullyClippedStack(BitStack& stack, Node* node)
+{
+ // Put the nodes in a vector so we can iterate in reverse order.
+ Vector<Node*, 100> ancestry;
+ for (Node* parent = parentCrossingShadowBoundaries(node); parent; parent = parentCrossingShadowBoundaries(parent))
+ ancestry.append(parent);
+
+ // Call pushFullyClippedState on each node starting with the earliest ancestor.
+ size_t size = ancestry.size();
+ for (size_t i = 0; i < size; ++i)
+ pushFullyClippedState(stack, ancestry[size - i - 1]);
+ pushFullyClippedState(stack, node);
+
+ ASSERT(stack.size() == 1 + depthCrossingShadowBoundaries(node));
+}
+
+// --------
+
TextIterator::TextIterator()
: m_startContainer(0)
, m_startOffset(0)
@@ -112,8 +255,7 @@ TextIterator::TextIterator()
}
TextIterator::TextIterator(const Range* r, bool emitCharactersBetweenAllVisiblePositions, bool enterTextControls)
- : m_inShadowContent(false)
- , m_startContainer(0)
+ : m_startContainer(0)
, m_startOffset(0)
, m_endContainer(0)
, m_endOffset(0)
@@ -144,23 +286,17 @@ TextIterator::TextIterator(const Range* r, bool emitCharactersBetweenAllVisibleP
m_endContainer = endContainer;
m_endOffset = endOffset;
- for (Node* n = startContainer; n; n = n->parentNode()) {
- if (n->isShadowNode()) {
- m_inShadowContent = true;
- break;
- }
- }
-
// set up the current node for processing
m_node = r->firstNode();
- if (m_node == 0)
+ if (!m_node)
return;
+ setUpFullyClippedStack(m_fullyClippedStack, m_node);
m_offset = m_node == m_startContainer ? m_startOffset : 0;
m_handledNode = false;
m_handledChildren = false;
// calculate first out of bounds node
- m_pastEndNode = r->pastLastNode();
+ m_pastEndNode = nextInPreOrderCrossingShadowBoundaries(endContainer, endOffset);
// initialize node processing state
m_needAnotherNewline = false;
@@ -219,7 +355,7 @@ void TextIterator::advance()
return;
}
- RenderObject *renderer = m_node->renderer();
+ RenderObject* renderer = m_node->renderer();
if (!renderer) {
m_handledNode = true;
m_handledChildren = true;
@@ -241,27 +377,20 @@ void TextIterator::advance()
// find a new current node to handle in depth-first manner,
// calling exitNode() as we come back thru a parent node
- Node *next = m_handledChildren ? 0 : m_node->firstChild();
+ Node* next = m_handledChildren ? 0 : m_node->firstChild();
m_offset = 0;
if (!next) {
next = m_node->nextSibling();
if (!next) {
bool pastEnd = m_node->traverseNextNode() == m_pastEndNode;
- Node* parentNode = m_node->parentNode();
- if (!parentNode && m_inShadowContent) {
- m_inShadowContent = false;
- parentNode = m_node->shadowParentNode();
- }
+ Node* parentNode = parentCrossingShadowBoundaries(m_node);
while (!next && parentNode) {
if ((pastEnd && parentNode == m_endContainer) || m_endContainer->isDescendantOf(parentNode))
return;
bool haveRenderer = m_node->renderer();
m_node = parentNode;
- parentNode = m_node->parentNode();
- if (!parentNode && m_inShadowContent) {
- m_inShadowContent = false;
- parentNode = m_node->shadowParentNode();
- }
+ m_fullyClippedStack.pop();
+ parentNode = parentCrossingShadowBoundaries(m_node);
if (haveRenderer)
exitNode();
if (m_positionNode) {
@@ -272,10 +401,13 @@ void TextIterator::advance()
next = m_node->nextSibling();
}
}
+ m_fullyClippedStack.pop();
}
// set the new current node
m_node = next;
+ if (m_node)
+ pushFullyClippedState(m_fullyClippedStack, m_node);
m_handledNode = false;
m_handledChildren = false;
@@ -285,13 +417,16 @@ void TextIterator::advance()
}
}
-static inline bool compareBoxStart(const InlineTextBox *first, const InlineTextBox *second)
+static inline bool compareBoxStart(const InlineTextBox* first, const InlineTextBox* second)
{
return first->start() < second->start();
}
bool TextIterator::handleTextNode()
{
+ if (m_fullyClippedStack.top())
+ return false;
+
RenderText* renderer = toRenderText(m_node->renderer());
if (renderer->style()->visibility() != VISIBLE)
return false;
@@ -325,7 +460,7 @@ bool TextIterator::handleTextNode()
// Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text)
if (renderer->containsReversedText()) {
m_sortedTextBoxes.clear();
- for (InlineTextBox * textBox = renderer->firstTextBox(); textBox; textBox = textBox->nextTextBox()) {
+ for (InlineTextBox* textBox = renderer->firstTextBox(); textBox; textBox = textBox->nextTextBox()) {
m_sortedTextBoxes.append(textBox);
}
std::sort(m_sortedTextBoxes.begin(), m_sortedTextBoxes.end(), compareBoxStart);
@@ -339,7 +474,7 @@ bool TextIterator::handleTextNode()
void TextIterator::handleTextBox()
{
- RenderText *renderer = toRenderText(m_node->renderer());
+ RenderText* renderer = toRenderText(m_node->renderer());
String str = renderer->text();
int start = m_offset;
int end = (m_node == m_endContainer) ? m_endOffset : INT_MAX;
@@ -348,7 +483,7 @@ void TextIterator::handleTextBox()
int runStart = max(textBoxStart, start);
// Check for collapsed space at the start of this run.
- InlineTextBox *firstTextBox = renderer->containsReversedText() ? m_sortedTextBoxes[0] : renderer->firstTextBox();
+ InlineTextBox* firstTextBox = renderer->containsReversedText() ? m_sortedTextBoxes[0] : renderer->firstTextBox();
bool needSpace = m_lastTextNodeEndedWithCollapsedSpace
|| (m_textBox == firstTextBox && textBoxStart == runStart && runStart > 0);
if (needSpace && !isCollapsibleWhitespace(m_lastCharacter) && m_lastCharacter) {
@@ -365,7 +500,7 @@ void TextIterator::handleTextBox()
int runEnd = min(textBoxEnd, end);
// Determine what the next text box will be, but don't advance yet
- InlineTextBox *nextTextBox = 0;
+ InlineTextBox* nextTextBox = 0;
if (renderer->containsReversedText()) {
if (m_sortedTextBoxesPosition + 1 < m_sortedTextBoxes.size())
nextTextBox = m_sortedTextBoxes[m_sortedTextBoxesPosition + 1];
@@ -411,6 +546,9 @@ void TextIterator::handleTextBox()
bool TextIterator::handleReplacedElement()
{
+ if (m_fullyClippedStack.top())
+ return false;
+
RenderObject* renderer = m_node->renderer();
if (renderer->style()->visibility() != VISIBLE)
return false;
@@ -421,10 +559,12 @@ bool TextIterator::handleReplacedElement()
}
if (m_enterTextControls && renderer->isTextControl()) {
- m_node = toRenderTextControl(renderer)->innerTextElement();
- m_offset = 0;
- m_inShadowContent = true;
- return false;
+ if (HTMLElement* innerTextElement = toRenderTextControl(renderer)->innerTextElement()) {
+ m_node = innerTextElement->shadowTreeRootNode();
+ pushFullyClippedState(m_fullyClippedStack, m_node);
+ m_offset = 0;
+ return false;
+ }
}
m_haveEmitted = true;
@@ -459,7 +599,7 @@ static bool shouldEmitTabBeforeNode(Node* node)
return false;
// Want a tab before every cell other than the first one
- RenderTableCell* rc = static_cast<RenderTableCell*>(r);
+ RenderTableCell* rc = toRenderTableCell(r);
RenderTable* t = rc->table();
return t && (t->cellBefore(rc) || t->cellAbove(rc));
}
@@ -509,7 +649,7 @@ static bool shouldEmitNewlinesBeforeAndAfterNode(Node* node)
// Need to make an exception for table row elements, because they are neither
// "inline" or "RenderBlock", but we want newlines for them.
if (r->isTableRow()) {
- RenderTable* t = static_cast<RenderTableRow*>(r)->table();
+ RenderTable* t = toRenderTableRow(r)->table();
if (t && !t->isInline())
return true;
}
@@ -698,7 +838,7 @@ void TextIterator::exitNode()
emitCharacter(' ', baseNode->parentNode(), baseNode, 1, 1);
}
-void TextIterator::emitCharacter(UChar c, Node *textNode, Node *offsetBaseNode, int textStartOffset, int textEndOffset)
+void TextIterator::emitCharacter(UChar c, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset)
{
m_haveEmitted = true;
@@ -774,14 +914,14 @@ Node* TextIterator::node() const
// --------
-SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() : m_positionNode(0)
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator()
+ : m_positionNode(0)
{
}
-SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range *r)
+SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r)
+ : m_positionNode(0)
{
- m_positionNode = 0;
-
if (!r)
return;
@@ -806,6 +946,7 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range *r)
}
m_node = endNode;
+ setUpFullyClippedStack(m_fullyClippedStack, m_node);
m_offset = endOffset;
m_handledNode = false;
m_handledChildren = endOffset == 0;
@@ -822,15 +963,8 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range *r)
m_lastTextNode = 0;
m_lastCharacter = '\n';
-
- if (startOffset == 0 || !startNode->firstChild()) {
- m_pastStartNode = startNode->previousSibling();
- while (!m_pastStartNode && startNode->parentNode()) {
- startNode = startNode->parentNode();
- m_pastStartNode = startNode->previousSibling();
- }
- } else
- m_pastStartNode = startNode->childNode(startOffset - 1);
+
+ m_pastStartNode = previousInPostOrderCrossingShadowBoundaries(startNode, startOffset);
advance();
}
@@ -845,7 +979,7 @@ void SimplifiedBackwardsTextIterator::advance()
while (m_node && m_node != m_pastStartNode) {
// Don't handle node if we start iterating at [node, 0].
if (!m_handledNode && !(m_node == m_endNode && m_endOffset == 0)) {
- RenderObject *renderer = m_node->renderer();
+ RenderObject* renderer = m_node->renderer();
if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
// FIXME: What about CDATA_SECTION_NODE?
if (renderer->style()->visibility() == VISIBLE && m_offset > 0)
@@ -877,9 +1011,11 @@ void SimplifiedBackwardsTextIterator::advance()
// Exit all other containers.
next = m_node->previousSibling();
while (!next) {
- if (!m_node->parentNode())
+ Node* parentNode = parentCrossingShadowBoundaries(m_node);
+ if (!parentNode)
break;
- m_node = m_node->parentNode();
+ m_node = parentNode;
+ m_fullyClippedStack.pop();
exitNode();
if (m_positionNode) {
m_handledNode = true;
@@ -888,9 +1024,12 @@ void SimplifiedBackwardsTextIterator::advance()
}
next = m_node->previousSibling();
}
+ m_fullyClippedStack.pop();
}
m_node = next;
+ if (m_node)
+ pushFullyClippedState(m_fullyClippedStack, m_node);
m_offset = m_node ? caretMaxOffset(m_node) : 0;
m_handledNode = false;
m_handledChildren = false;
@@ -904,7 +1043,7 @@ bool SimplifiedBackwardsTextIterator::handleTextNode()
{
m_lastTextNode = m_node;
- RenderText *renderer = toRenderText(m_node->renderer());
+ RenderText* renderer = toRenderText(m_node->renderer());
String str = renderer->text();
if (!renderer->firstTextBox() && str.length() > 0)
@@ -938,29 +1077,25 @@ bool SimplifiedBackwardsTextIterator::handleNonTextNode()
{
// We can use a linefeed in place of a tab because this simple iterator is only used to
// find boundaries, not actual content. A linefeed breaks words, sentences, and paragraphs.
- if (shouldEmitNewlineForNode(m_node) ||
- shouldEmitNewlineAfterNode(m_node) ||
- shouldEmitTabBeforeNode(m_node)) {
+ if (shouldEmitNewlineForNode(m_node) || shouldEmitNewlineAfterNode(m_node) || shouldEmitTabBeforeNode(m_node)) {
unsigned index = m_node->nodeIndex();
- // The start of this emitted range is wrong, ensuring correctness would require
- // VisiblePositions and so would be slow. previousBoundary expects this.
+ // The start of this emitted range is wrong. Ensuring correctness would require
+ // VisiblePositions and so would be slow. previousBoundary expects this.
emitCharacter('\n', m_node->parentNode(), index + 1, index + 1);
}
-
return true;
}
void SimplifiedBackwardsTextIterator::exitNode()
{
- if (shouldEmitNewlineForNode(m_node) ||
- shouldEmitNewlineBeforeNode(m_node) ||
- shouldEmitTabBeforeNode(m_node))
- // The start of this emitted range is wrong, ensuring correctness would require
- // VisiblePositions and so would be slow. previousBoundary expects this.
+ if (shouldEmitNewlineForNode(m_node) || shouldEmitNewlineBeforeNode(m_node) || shouldEmitTabBeforeNode(m_node)) {
+ // The start of this emitted range is wrong. Ensuring correctness would require
+ // VisiblePositions and so would be slow. previousBoundary expects this.
emitCharacter('\n', m_node, 0, 0);
+ }
}
-void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node *node, int startOffset, int endOffset)
+void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node* node, int startOffset, int endOffset)
{
m_singleCharacterBuffer = c;
m_positionNode = node;
@@ -988,7 +1123,7 @@ CharacterIterator::CharacterIterator()
{
}
-CharacterIterator::CharacterIterator(const Range *r, bool emitCharactersBetweenAllVisiblePositions, bool enterTextControls)
+CharacterIterator::CharacterIterator(const Range* r, bool emitCharactersBetweenAllVisiblePositions, bool enterTextControls)
: m_offset(0)
, m_runOffset(0)
, m_atBreak(true)
@@ -1167,15 +1302,17 @@ void BackwardsCharacterIterator::advance(int count)
// --------
WordAwareIterator::WordAwareIterator()
-: m_previousText(0), m_didLookAhead(false)
+ : m_previousText(0)
+ , m_didLookAhead(false)
{
}
-WordAwareIterator::WordAwareIterator(const Range *r)
-: m_previousText(0), m_didLookAhead(false), m_textIterator(r)
+WordAwareIterator::WordAwareIterator(const Range* r)
+ : m_previousText(0)
+ , m_didLookAhead(true) // so we consider the first chunk from the text iterator
+ , m_textIterator(r)
{
- m_didLookAhead = true; // so we consider the first chunk from the text iterator
- advance(); // get in position over the first chunk of text
+ advance(); // get in position over the first chunk of text
}
// We're always in one of these modes:
@@ -1185,7 +1322,7 @@ WordAwareIterator::WordAwareIterator(const Range *r)
// (we looked ahead to the next chunk and found a word boundary)
// - We built up our own chunk of text from many chunks from the text iterator
-// FIXME: Perf could be bad for huge spans next to each other that don't fall on word boundaries
+// FIXME: Performance could be bad for huge spans next to each other that don't fall on word boundaries.
void WordAwareIterator::advance()
{
@@ -1256,8 +1393,40 @@ const UChar* WordAwareIterator::characters() const
// --------
+static inline UChar foldQuoteMark(UChar c)
+{
+ switch (c) {
+ case hebrewPunctuationGershayim:
+ case leftDoubleQuotationMark:
+ case rightDoubleQuotationMark:
+ return '"';
+ case hebrewPunctuationGeresh:
+ case leftSingleQuotationMark:
+ case rightSingleQuotationMark:
+ return '\'';
+ default:
+ return c;
+ }
+}
+
+static inline void foldQuoteMarks(String& s)
+{
+ s.replace(hebrewPunctuationGeresh, '\'');
+ s.replace(hebrewPunctuationGershayim, '"');
+ s.replace(leftDoubleQuotationMark, '"');
+ s.replace(leftSingleQuotationMark, '\'');
+ s.replace(rightDoubleQuotationMark, '"');
+ s.replace(rightSingleQuotationMark, '\'');
+}
+
#if USE(ICU_UNICODE) && !UCONFIG_NO_COLLATION
+static inline void foldQuoteMarks(UChar* data, size_t length)
+{
+ for (size_t i = 0; i < length; ++i)
+ data[i] = foldQuoteMark(data[i]);
+}
+
static const size_t minimumSearchBufferSize = 8192;
#ifndef NDEBUG
@@ -1269,13 +1438,9 @@ static UStringSearch* createSearcher()
// Provide a non-empty pattern and non-empty text so usearch_open will not fail,
// but it doesn't matter exactly what it is, since we don't perform any searches
// without setting both the pattern and the text.
-
- // Pass empty string for the locale for now to get the Unicode Collation Algorithm,
- // rather than something locale-specific.
-
UErrorCode status = U_ZERO_ERROR;
- UStringSearch* searcher = usearch_open(&newlineCharacter, 1, &newlineCharacter, 1, "", 0, &status);
- ASSERT(status == U_ZERO_ERROR);
+ UStringSearch* searcher = usearch_open(&newlineCharacter, 1, &newlineCharacter, 1, currentSearchLocaleID(), 0, &status);
+ ASSERT(status == U_ZERO_ERROR || status == U_USING_FALLBACK_WARNING || status == U_USING_DEFAULT_WARNING);
return searcher;
}
@@ -1307,7 +1472,12 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive)
{
ASSERT(!m_target.isEmpty());
- size_t targetLength = target.length();
+ // FIXME: We'd like to tailor the searcher to fold quote marks for us instead
+ // of doing it in a separate replacement pass here, but ICU doesn't offer a way
+ // to add tailoring on top of the locale-specific tailoring as of this writing.
+ foldQuoteMarks(m_target);
+
+ size_t targetLength = m_target.length();
m_buffer.reserveInitialCapacity(max(targetLength * 8, minimumSearchBufferSize));
m_overlap = m_buffer.capacity() / 4;
@@ -1347,9 +1517,11 @@ inline size_t SearchBuffer::append(const UChar* characters, size_t length)
m_buffer.shrink(m_overlap);
}
- size_t usableLength = min(m_buffer.capacity() - m_buffer.size(), length);
+ size_t oldLength = m_buffer.size();
+ size_t usableLength = min(m_buffer.capacity() - oldLength, length);
ASSERT(usableLength);
m_buffer.append(characters, usableLength);
+ foldQuoteMarks(m_buffer.data() + oldLength, usableLength);
return usableLength;
}
@@ -1416,6 +1588,7 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive)
{
ASSERT(!m_target.isEmpty());
m_target.replace(noBreakSpace, ' ');
+ foldQuoteMarks(m_target);
}
inline SearchBuffer::~SearchBuffer()
@@ -1435,7 +1608,7 @@ inline bool SearchBuffer::atBreak() const
inline void SearchBuffer::append(UChar c, bool isStart)
{
- m_buffer[m_cursor] = c == noBreakSpace ? ' ' : c;
+ m_buffer[m_cursor] = c == noBreakSpace ? ' ' : foldQuoteMark(c);
m_isCharacterStartBuffer[m_cursor] = isStart;
if (++m_cursor == m_target.length()) {
m_cursor = 0;
@@ -1507,7 +1680,7 @@ size_t SearchBuffer::length() const
// --------
-int TextIterator::rangeLength(const Range *r, bool forSelectionPreservation)
+int TextIterator::rangeLength(const Range* r, bool forSelectionPreservation)
{
int length = 0;
for (TextIterator it(r, forSelectionPreservation); !it.atEnd(); it.advance())
@@ -1522,7 +1695,7 @@ PassRefPtr<Range> TextIterator::subrange(Range* entireRange, int characterOffset
return characterSubrange(entireRangeIterator, characterOffset, characterCount);
}
-PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element *scope, int rangeLocation, int rangeLength, bool forSelectionPreservation)
+PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element* scope, int rangeLocation, int rangeLength, bool forSelectionPreservation)
{
RefPtr<Range> resultRange = scope->document()->createRange();
@@ -1742,11 +1915,6 @@ tryAgain:
PassRefPtr<Range> findPlainText(const Range* range, const String& target, bool forward, bool caseSensitive)
{
- // We can't search effectively for a string that's entirely made of collapsible
- // whitespace, so we won't even try. This also takes care of the empty string case.
- if (isAllCollapsibleWhitespace(target))
- return collapsedToBoundary(range, forward);
-
// First, find the text.
size_t matchStart;
size_t matchLength;
diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h
index f00bea4..44af3e5 100644
--- a/WebCore/editing/TextIterator.h
+++ b/WebCore/editing/TextIterator.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2004, 2006 Apple Computer, Inc. All rights reserved.
+ * Copyright (C) 2004, 2006, 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -49,6 +49,21 @@ String plainText(const Range*);
UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString);
PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool caseSensitive);
+class BitStack {
+public:
+ BitStack();
+
+ void push(bool);
+ void pop();
+
+ bool top() const;
+ unsigned size() const;
+
+private:
+ unsigned m_size;
+ Vector<unsigned, 1> m_words;
+};
+
// Iterates through the DOM range, returning all the text, and 0-length boundaries
// at points where replaced elements break up the text flow. The text comes back in
// chunks so as to optimize for performance of the iteration.
@@ -80,27 +95,27 @@ private:
bool handleReplacedElement();
bool handleNonTextNode();
void handleTextBox();
- void emitCharacter(UChar, Node *textNode, Node *offsetBaseNode, int textStartOffset, int textEndOffset);
- void emitText(Node *textNode, int textStartOffset, int textEndOffset);
+ void emitCharacter(UChar, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset);
+ void emitText(Node* textNode, int textStartOffset, int textEndOffset);
// Current position, not necessarily of the text being returned, but position
// as we walk through the DOM tree.
- Node *m_node;
+ Node* m_node;
int m_offset;
bool m_handledNode;
bool m_handledChildren;
- bool m_inShadowContent;
+ BitStack m_fullyClippedStack;
// The range.
- Node *m_startContainer;
+ Node* m_startContainer;
int m_startOffset;
- Node *m_endContainer;
+ Node* m_endContainer;
int m_endOffset;
- Node *m_pastEndNode;
+ Node* m_pastEndNode;
// The current text and its position, in the form to be returned from the iterator.
- Node *m_positionNode;
- mutable Node *m_positionOffsetBaseNode;
+ Node* m_positionNode;
+ mutable Node* m_positionOffsetBaseNode;
mutable int m_positionStartOffset;
mutable int m_positionEndOffset;
const UChar* m_textCharacters;
@@ -109,10 +124,10 @@ private:
// Used when there is still some pending text from the current node; when these
// are false and 0, we go back to normal iterating.
bool m_needAnotherNewline;
- InlineTextBox *m_textBox;
+ InlineTextBox* m_textBox;
// Used to do the whitespace collapsing logic.
- Node *m_lastTextNode;
+ Node* m_lastTextNode;
bool m_lastTextNodeEndedWithCollapsedSpace;
UChar m_lastCharacter;
@@ -135,12 +150,12 @@ private:
};
// Iterates through the DOM range, returning all the text, and 0-length boundaries
-// at points where replaced elements break up the text flow. The text comes back in
+// at points where replaced elements break up the text flow. The text comes back in
// chunks so as to optimize for performance of the iteration.
class SimplifiedBackwardsTextIterator {
public:
SimplifiedBackwardsTextIterator();
- explicit SimplifiedBackwardsTextIterator(const Range *);
+ explicit SimplifiedBackwardsTextIterator(const Range*);
bool atEnd() const { return !m_positionNode; }
void advance();
@@ -155,7 +170,7 @@ private:
bool handleTextNode();
bool handleReplacedElement();
bool handleNonTextNode();
- void emitCharacter(UChar, Node *Node, int startOffset, int endOffset);
+ void emitCharacter(UChar, Node*, int startOffset, int endOffset);
// Current position, not necessarily of the text being returned, but position
// as we walk through the DOM tree.
@@ -163,7 +178,8 @@ private:
int m_offset;
bool m_handledNode;
bool m_handledChildren;
-
+ BitStack m_fullyClippedStack;
+
// End of the range.
Node* m_startNode;
int m_startOffset;
@@ -194,7 +210,7 @@ private:
class CharacterIterator {
public:
CharacterIterator();
- explicit CharacterIterator(const Range* r, bool emitCharactersBetweenAllVisiblePositions = false, bool enterTextControls = false);
+ explicit CharacterIterator(const Range*, bool emitCharactersBetweenAllVisiblePositions = false, bool enterTextControls = false);
void advance(int numCharacters);
@@ -240,7 +256,7 @@ private:
class WordAwareIterator {
public:
WordAwareIterator();
- explicit WordAwareIterator(const Range *r);
+ explicit WordAwareIterator(const Range*);
bool atEnd() const { return !m_didLookAhead && m_textIterator.atEnd(); }
void advance();
diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp
index 5ce4e56..f5901d7 100644
--- a/WebCore/editing/TypingCommand.cpp
+++ b/WebCore/editing/TypingCommand.cpp
@@ -58,6 +58,7 @@ TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, con
m_killRing(killRing),
m_openedByBackwardDelete(false)
{
+ updatePreservesTypingStyle(m_commandType);
}
void TypingCommand::deleteSelection(Document* document, bool smartDelete)
@@ -309,8 +310,10 @@ void TypingCommand::markMisspellingsAfterTyping()
}
}
-void TypingCommand::typingAddedToOpenCommand()
+void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
{
+ updatePreservesTypingStyle(commandTypeForAddedTyping);
+
#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.
@@ -360,19 +363,19 @@ void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool select
applyCommandToComposite(command);
}
command->input(text, selectInsertedText);
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(InsertText);
}
void TypingCommand::insertLineBreak()
{
applyCommandToComposite(InsertLineBreakCommand::create(document()));
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(InsertLineBreak);
}
void TypingCommand::insertParagraphSeparator()
{
applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(InsertParagraphSeparator);
}
void TypingCommand::insertParagraphSeparatorInQuotedContent()
@@ -385,7 +388,7 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent()
}
applyCommandToComposite(BreakBlockquoteCommand::create(document()));
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
}
bool TypingCommand::makeEditableRootEmpty()
@@ -423,7 +426,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
// After breaking out of an empty mail blockquote, we still want continue with the deletion
// so actual content will get deleted, and not just the quote style.
if (breakOutOfEmptyMailBlockquotedParagraph())
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(DeleteKey);
m_smartDelete = false;
@@ -436,12 +439,12 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
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();
+ typingAddedToOpenCommand(DeleteKey);
return;
}
// When there are no visible positions in the editing root, delete its entire contents.
if (endingSelection().visibleStart().next(true).isNull() && makeEditableRootEmpty()) {
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(DeleteKey);
return;
}
}
@@ -457,7 +460,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
// If the caret is just after a table, select the table and don't delete anything.
} else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
setEndingSelection(VisibleSelection(Position(table, 0), endingSelection().start(), DOWNSTREAM));
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(DeleteKey);
return;
}
@@ -498,7 +501,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
setStartingSelection(selectionAfterUndo);
CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
setSmartDelete(false);
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(DeleteKey);
}
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
@@ -530,7 +533,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
// When deleting tables: Select the table first, then perform the deletion
if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.deprecatedEditingOffset() == 0) {
setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.node()), DOWNSTREAM));
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(ForwardDeleteKey);
return;
}
@@ -578,30 +581,32 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
setStartingSelection(selectionAfterUndo);
CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
setSmartDelete(false);
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(ForwardDeleteKey);
}
void TypingCommand::deleteSelection(bool smartDelete)
{
CompositeEditCommand::deleteSelection(smartDelete);
- typingAddedToOpenCommand();
+ typingAddedToOpenCommand(DeleteSelection);
}
-bool TypingCommand::preservesTypingStyle() const
+void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
{
- switch (m_commandType) {
+ switch (commandType) {
case DeleteSelection:
case DeleteKey:
case ForwardDeleteKey:
case InsertParagraphSeparator:
case InsertLineBreak:
- return true;
+ m_preservesTypingStyle = true;
+ return;
case InsertParagraphSeparatorInQuotedContent:
case InsertText:
- return false;
+ m_preservesTypingStyle = false;
+ return;
}
ASSERT_NOT_REACHED();
- return false;
+ m_preservesTypingStyle = false;
}
bool TypingCommand::isTypingCommand() const
diff --git a/WebCore/editing/TypingCommand.h b/WebCore/editing/TypingCommand.h
index 2c52447..7c89a7c 100644
--- a/WebCore/editing/TypingCommand.h
+++ b/WebCore/editing/TypingCommand.h
@@ -79,10 +79,11 @@ private:
virtual void doApply();
virtual EditAction editingAction() const;
virtual bool isTypingCommand() const;
- virtual bool preservesTypingStyle() const;
+ virtual bool preservesTypingStyle() const { return m_preservesTypingStyle; }
+ void updatePreservesTypingStyle(ETypingCommand);
void markMisspellingsAfterTyping();
- void typingAddedToOpenCommand();
+ void typingAddedToOpenCommand(ETypingCommand);
bool makeEditableRootEmpty();
ETypingCommand m_commandType;
@@ -92,6 +93,7 @@ private:
bool m_smartDelete;
TextGranularity m_granularity;
bool m_killRing;
+ bool m_preservesTypingStyle;
// Undoing a series of backward deletes will restore a selection around all of the
// characters that were deleted, but only if the typing command being undone
diff --git a/WebCore/editing/gtk/SelectionControllerGtk.cpp b/WebCore/editing/gtk/SelectionControllerGtk.cpp
index 52fbbab..f3bd4bc 100644
--- a/WebCore/editing/gtk/SelectionControllerGtk.cpp
+++ b/WebCore/editing/gtk/SelectionControllerGtk.cpp
@@ -30,11 +30,11 @@ namespace WebCore {
void SelectionController::notifyAccessibilityForSelectionChange()
{
if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull()) {
- RenderObject* focusedNode = m_sel.start().node()->renderer();
+ RenderObject* focusedNode = m_sel.end().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());
+ g_signal_emit_by_name(wrapper, "text-caret-moved", m_sel.end().computeOffsetInContainerNode());
if (m_sel.isRange())
g_signal_emit_by_name(wrapper, "text-selection-changed");
diff --git a/WebCore/editing/haiku/EditorHaiku.cpp b/WebCore/editing/haiku/EditorHaiku.cpp
new file mode 100644
index 0000000..17fde1f
--- /dev/null
+++ b/WebCore/editing/haiku/EditorHaiku.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2007 Ryan Leavengood <leavengood@gmail.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * 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 "Editor.h"
+
+#include "ClipboardAccessPolicy.h"
+#include "Clipboard.h"
+
+#include "ClipboardHaiku.h"
+
+
+namespace WebCore {
+
+PassRefPtr<Clipboard> Editor::newGeneralClipboard(ClipboardAccessPolicy policy)
+{
+ return new ClipboardHaiku(policy, false);
+}
+
+} // namespace WebCore
+
diff --git a/WebCore/editing/htmlediting.cpp b/WebCore/editing/htmlediting.cpp
index 7b51295..4124c48 100644
--- a/WebCore/editing/htmlediting.cpp
+++ b/WebCore/editing/htmlediting.cpp
@@ -84,6 +84,7 @@ bool canHaveChildrenForEditing(const Node* node)
!node->hasTagName(embedTag) &&
!node->hasTagName(appletTag) &&
!node->hasTagName(selectTag) &&
+ !node->hasTagName(datagridTag) &&
#if ENABLE(WML)
!node->hasTagName(WMLNames::doTag) &&
#endif
@@ -206,9 +207,18 @@ Element* editableRootForPosition(const Position& p)
return node->rootEditableElement();
}
-bool isContentEditable(const Node* node)
+// Finds the enclosing element until which the tree can be split.
+// When a user hits ENTER, he/she won't expect this element to be split into two.
+// You may pass it as the second argument of splitTreeToNode.
+Element* unsplittableElementForPosition(const Position& p)
{
- return node->isContentEditable();
+ // Since enclosingNodeOfType won't search beyond the highest root editable node,
+ // this code works even if the closest table cell was outside of the root editable node.
+ Element* enclosingCell = static_cast<Element*>(enclosingNodeOfType(p, &isTableCell, true));
+ if (enclosingCell)
+ return enclosingCell;
+
+ return editableRootForPosition(p);
}
Position nextCandidate(const Position& position)
@@ -564,16 +574,79 @@ Node* isLastPositionBeforeTable(const VisiblePosition& visiblePosition)
Position positionBeforeNode(const Node* node)
{
- // FIXME: This should ASSERT(node->parentNode())
+ ASSERT(node);
+ // FIXME: Should ASSERT(node->parentNode()) but doing so results in editing/deleting/delete-ligature-001.html crashing
return Position(node->parentNode(), node->nodeIndex());
}
Position positionAfterNode(const Node* node)
{
- // FIXME: This should ASSERT(node->parentNode())
+ ASSERT(node);
+ // FIXME: Should ASSERT(node->parentNode()) but doing so results in editing/deleting/delete-ligature-001.html crashing
return Position(node->parentNode(), node->nodeIndex() + 1);
}
+// Returns the visible position at the beginning of a node
+VisiblePosition visiblePositionBeforeNode(Node* node)
+{
+ ASSERT(node);
+ if (node->childNodeCount())
+ return VisiblePosition(node, 0, DOWNSTREAM);
+ ASSERT(node->parentNode());
+ return positionBeforeNode(node);
+}
+
+// Returns the visible position at the ending of a node
+VisiblePosition visiblePositionAfterNode(Node* node)
+{
+ ASSERT(node);
+ if (node->childNodeCount())
+ return VisiblePosition(node, node->childNodeCount(), DOWNSTREAM);
+ ASSERT(node->parentNode());
+ return positionAfterNode(node);
+}
+
+// Create a range object with two visible positions, start and end.
+// create(PassRefPtr<Document>, const Position&, const Position&); will use deprecatedEditingOffset
+// Use this function instead of create a regular range object (avoiding editing offset).
+PassRefPtr<Range> createRange(PassRefPtr<Document> document, const VisiblePosition& start, const VisiblePosition& end, ExceptionCode& ec)
+{
+ ec = 0;
+ RefPtr<Range> selectedRange = Range::create(document);
+ selectedRange->setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), ec);
+ if (!ec)
+ selectedRange->setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), ec);
+ return selectedRange.release();
+}
+
+// Extend rangeToExtend to include nodes that wraps range and visibly starts and ends inside or at the boudnaries of maximumRange
+// e.g. if the original range spaned "hello" in <div>hello</div>, then this function extends the range to contain div's around it.
+// Call this function before copying / moving paragraphs to contain all wrapping nodes.
+// This function stops extending the range immediately below rootNode; i.e. the extended range can contain a child node of rootNode
+// but it can never contain rootNode itself.
+PassRefPtr<Range> extendRangeToWrappingNodes(PassRefPtr<Range> range, const Range* maximumRange, const Node* rootNode)
+{
+ ASSERT(range);
+ ASSERT(maximumRange);
+
+ ExceptionCode ec = 0;
+ Node* ancestor = range->commonAncestorContainer(ec);// find the cloeset common ancestor
+ Node* highestNode = 0;
+ // traverse through ancestors as long as they are contained within the range, content-editable, and below rootNode (could be =0).
+ while (ancestor && ancestor->isContentEditable() && isNodeVisiblyContainedWithin(ancestor, maximumRange) && ancestor != rootNode) {
+ highestNode = ancestor;
+ ancestor = ancestor->parentNode();
+ }
+
+ if (!highestNode)
+ return range;
+
+ // Create new range with the highest editable node contained within the range
+ RefPtr<Range> extendedRange = Range::create(range->ownerDocument());
+ extendedRange->selectNode(highestNode, ec);
+ return extendedRange.release();
+}
+
bool isListElement(Node *n)
{
return (n && (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag)));
@@ -586,7 +659,7 @@ Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName)
Node* root = highestEditableRoot(p);
for (Node* n = p.node(); n; n = n->parentNode()) {
- if (root && !isContentEditable(n))
+ if (root && !n->isContentEditable())
continue;
if (n->hasTagName(tagName))
return n;
@@ -606,7 +679,7 @@ Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*),
for (Node* n = p.node(); n; n = n->parentNode()) {
// Don't return a non-editable node if the input position was editable, since
// the callers from editing will no doubt want to perform editing inside the returned node.
- if (root && !isContentEditable(n) && onlyReturnEditableNodes)
+ if (root && !n->isContentEditable() && onlyReturnEditableNodes)
continue;
if ((*nodeIsOfType)(n))
return n;
@@ -664,7 +737,7 @@ HTMLElement* enclosingList(Node* node)
return 0;
}
-Node* enclosingListChild(Node *node)
+HTMLElement* enclosingListChild(Node *node)
{
if (!node)
return 0;
@@ -675,7 +748,7 @@ Node* enclosingListChild(Node *node)
// FIXME: This function is inappropriately named if it starts with node instead of node->parentNode()
for (Node* n = node; n && n->parentNode(); n = n->parentNode()) {
if (n->hasTagName(liTag) || isListElement(n->parentNode()))
- return n;
+ return static_cast<HTMLElement*>(n);
if (n == root || isTableCell(n))
return 0;
}
@@ -737,6 +810,18 @@ HTMLElement* outermostEnclosingList(Node* node)
return list;
}
+bool canMergeLists(Element* firstList, Element* secondList)
+{
+ if (!firstList || !secondList)
+ return false;
+
+ return firstList->hasTagName(secondList->tagQName())// make sure the list types match (ol vs. ul)
+ && firstList->isContentEditable() && secondList->isContentEditable()// both lists are editable
+ && firstList->rootEditableElement() == secondList->rootEditableElement()// don't cross editing boundaries
+ && isVisiblyAdjacent(positionAfterNode(firstList), positionBeforeNode(secondList));
+ // Make sure there is no visible content between this li and the previous list
+}
+
Node* highestAncestor(Node* node)
{
ASSERT(node);
@@ -961,7 +1046,7 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection& original
}
-int indexForVisiblePosition(VisiblePosition& visiblePosition)
+int indexForVisiblePosition(const VisiblePosition& visiblePosition)
{
if (visiblePosition.isNull())
return 0;
@@ -970,6 +1055,29 @@ int indexForVisiblePosition(VisiblePosition& visiblePosition)
return TextIterator::rangeLength(range.get(), true);
}
+// Determines whether two positions are visibly next to each other (first then second)
+// while ignoring whitespaces and unrendered nodes
+bool isVisiblyAdjacent(const Position& first, const Position& second)
+{
+ return VisiblePosition(first) == VisiblePosition(second.upstream());
+}
+
+// Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range.
+// Call this function to determine whether a node is visibly fit inside selectedRange
+bool isNodeVisiblyContainedWithin(Node* node, const Range* selectedRange)
+{
+ ASSERT(node);
+ ASSERT(selectedRange);
+ // If the node is inside the range, then it surely is contained within
+ ExceptionCode ec = 0;
+ if (selectedRange->compareNode(node, ec) == Range::NODE_INSIDE)
+ return true;
+
+ // If the node starts and ends at where selectedRange starts and ends, the node is contained within
+ return visiblePositionBeforeNode(node) == selectedRange->startPosition()
+ && visiblePositionAfterNode(node) == selectedRange->endPosition();
+}
+
PassRefPtr<Range> avoidIntersectionWithNode(const Range* range, Node* node)
{
if (!range)
diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h
index 374b512..f98b149 100644
--- a/WebCore/editing/htmlediting.h
+++ b/WebCore/editing/htmlediting.h
@@ -28,6 +28,7 @@
#include <wtf/Forward.h>
#include "HTMLNames.h"
+#include "ExceptionCode.h"
namespace WebCore {
@@ -53,7 +54,6 @@ 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&);
Position nextVisuallyDistinctCandidate(const Position&);
Position previousCandidate(const Position&);
@@ -61,6 +61,7 @@ Position previousVisuallyDistinctCandidate(const Position&);
bool isEditablePosition(const Position&);
bool isRichlyEditablePosition(const Position&);
Element* editableRootForPosition(const Position&);
+Element* unsplittableElementForPosition(const Position&);
bool isBlock(const Node*);
Node* enclosingBlock(Node*);
@@ -71,6 +72,10 @@ const String& nonBreakingSpaceString();
Position positionBeforeNode(const Node*);
Position positionAfterNode(const Node*);
+VisiblePosition visiblePositionBeforeNode(Node*);
+VisiblePosition visiblePositionAfterNode(Node*);
+PassRefPtr<Range> createRange(PassRefPtr<Document>, const VisiblePosition& start, const VisiblePosition& end, ExceptionCode&);
+PassRefPtr<Range> extendRangeToWrappingNodes(PassRefPtr<Range> rangeToExtend, const Range* maximumRange, const Node* rootNode);
PassRefPtr<Range> avoidIntersectionWithNode(const Range*, Node*);
VisibleSelection avoidIntersectionWithNode(const VisibleSelection&, Node*);
@@ -123,7 +128,8 @@ Node* enclosingAnchorElement(const Position&);
bool isListElement(Node*);
HTMLElement* enclosingList(Node*);
HTMLElement* outermostEnclosingList(Node*);
-Node* enclosingListChild(Node*);
+HTMLElement* enclosingListChild(Node*);
+bool canMergeLists(Element* firstList, Element* secondList);
Node* highestAncestor(Node*);
bool isTableElement(Node*);
bool isTableCell(const Node*);
@@ -133,8 +139,9 @@ bool lineBreakExistsAtVisiblePosition(const VisiblePosition&);
VisibleSelection selectionForParagraphIteration(const VisibleSelection&);
-int indexForVisiblePosition(VisiblePosition&);
-
+int indexForVisiblePosition(const VisiblePosition&);
+bool isVisiblyAdjacent(const Position& first, const Position& second);
+bool isNodeVisiblyContainedWithin(Node*, const Range*);
}
#endif
diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp
index d6fe1ce..14ce7f6 100644
--- a/WebCore/editing/markup.cpp
+++ b/WebCore/editing/markup.cpp
@@ -58,6 +58,7 @@
#include "htmlediting.h"
#include "visible_units.h"
#include <wtf/StdLibExtras.h>
+#include "ApplyStyleCommand.h"
using namespace std;
@@ -291,20 +292,16 @@ static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style
Node* blockquote = nearestMailBlockquote(node);
if (!blockquote || !blockquote->parentNode())
return;
-
- RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(blockquote->parentNode(), 0).computedStyle()->copyInheritableProperties();
- RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle()->copyInheritableProperties();
- parentStyle->diff(blockquoteStyle.get());
- blockquoteStyle->diff(style);
+
+ removeStylesAddedByNode(style, blockquote);
}
static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
{
if (!document || !document->documentElement())
return;
-
- RefPtr<CSSMutableStyleDeclaration> documentStyle = computedStyle(document->documentElement())->copyInheritableProperties();
- documentStyle->diff(style);
+
+ prepareEditingStyleToApplyAt(style, Position(document->documentElement(), 0));
}
static bool shouldAddNamespaceElem(const Element* elem)
@@ -692,7 +689,7 @@ static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl
return style.release();
}
-static bool propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* style, int propertyID)
+static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propertyID)
{
if (!style)
return false;
@@ -704,8 +701,11 @@ static bool propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* style, int
return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;
}
-static bool elementHasTextDecorationProperty(const Node* node)
+static bool isElementPresentational(const Node* node)
{
+ if (node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag) ||
+ node->hasTagName(iTag) || node->hasTagName(emTag) || node->hasTagName(bTag) || node->hasTagName(strongTag))
+ return true;
RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
if (!style)
return false;
@@ -763,6 +763,25 @@ static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CS
style->getPropertyCSSValue(CSSPropertyBackgroundColor);
}
+static void addStyleMarkup(Vector<String>& preMarkups, Vector<String>& postMarkups, CSSStyleDeclaration* style, Document* document, bool isBlock = false)
+{
+ // All text-decoration-related elements should have been treated as special ancestors
+ // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here
+ ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect));
+ DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
+ DEFINE_STATIC_LOCAL(const String, divClose, ("</div>"));
+ DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
+ DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
+ Vector<UChar> openTag;
+ append(openTag, isBlock ? divStyle : styleSpanOpen);
+ appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument());
+ openTag.append('\"');
+ openTag.append('>');
+ preMarkups.append(String::adopt(openTag));
+
+ postMarkups.append(isBlock ? divClose : styleSpanClose);
+}
+
// 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)
@@ -776,8 +795,6 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
if (!document)
return "";
- bool documentIsHTML = document->isHTMLDocument();
-
// Disable the delete button so it's elements are not serialized into the markup,
// but make sure neither endpoint is inside the delete user interface.
Frame* frame = document->frame();
@@ -927,12 +944,12 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
if (isMailBlockquote(ancestor))
specialCommonAncestor = ancestor;
}
-
+
Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
if (checkAncestor->renderer()) {
- RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties();
- if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect))
- specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty);
+ Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(Position(checkAncestor, 0), &isElementPresentational);
+ if (newSpecialCommonAncestor)
+ specialCommonAncestor = newSpecialCommonAncestor;
}
// If a single tab is selected, commonAncestor will be a text node inside a tab span.
@@ -966,18 +983,8 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
if (!fullySelectedRootStyle->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
fullySelectedRootStyle->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
- if (fullySelectedRootStyle->length()) {
- Vector<UChar> openTag;
- DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
- append(openTag, divStyle);
- appendAttributeValue(openTag, fullySelectedRootStyle->cssText(), documentIsHTML);
- openTag.append('\"');
- openTag.append('>');
- preMarkups.append(String::adopt(openTag));
-
- DEFINE_STATIC_LOCAL(const String, divCloseTag, ("</div>"));
- markups.append(divCloseTag);
- }
+ if (fullySelectedRootStyle->length())
+ addStyleMarkup(preMarkups, markups, fullySelectedRootStyle.get(), document, true);
} else {
// 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.
@@ -993,14 +1000,11 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
break;
}
}
-
- DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
- DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
-
+
// Add a wrapper span with the styles that all of the nodes in the markup inherit.
Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
if (parentOfLastClosed && parentOfLastClosed->renderer()) {
- RefPtr<CSSMutableStyleDeclaration> style = computedStyle(parentOfLastClosed)->copyInheritableProperties();
+ RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(Position(parentOfLastClosed, 0));
// Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
// us differentiate those styles from ones that the user has applied. This helps us
@@ -1016,33 +1020,18 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
if (convertBlocksToInlines)
style->removeBlockProperties();
- if (style->length() > 0) {
- Vector<UChar> openTag;
- append(openTag, styleSpanOpen);
- appendAttributeValue(openTag, style->cssText(), documentIsHTML);
- openTag.append('\"');
- openTag.append('>');
- preMarkups.append(String::adopt(openTag));
-
- markups.append(styleSpanClose);
- }
+ if (style->length() > 0)
+ addStyleMarkup(preMarkups, markups, style.get(), document);
}
if (lastClosed && lastClosed != document->documentElement()) {
// Add a style span with the document's default styles. We add these in a separate
// span so that at paste time we can differentiate between document defaults and user
// applied styles.
- RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle(document->documentElement())->copyInheritableProperties();
-
- if (defaultStyle->length() > 0) {
- Vector<UChar> openTag;
- append(openTag, styleSpanOpen);
- appendAttributeValue(openTag, defaultStyle->cssText(), documentIsHTML);
- openTag.append('\"');
- openTag.append('>');
- preMarkups.append(String::adopt(openTag));
- markups.append(styleSpanClose);
- }
+ RefPtr<CSSMutableStyleDeclaration> defaultStyle = editingStyleAtPosition(Position(document->documentElement(), 0));
+
+ if (defaultStyle->length() > 0)
+ addStyleMarkup(preMarkups, markups, defaultStyle.get(), document);
}
// FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
diff --git a/WebCore/editing/visible_units.cpp b/WebCore/editing/visible_units.cpp
index 02e9fb8..869c893 100644
--- a/WebCore/editing/visible_units.cpp
+++ b/WebCore/editing/visible_units.cpp
@@ -1178,9 +1178,6 @@ VisiblePosition logicalStartOfLine(const VisiblePosition& c)
{
VisiblePosition visPos = logicalStartPositionForLine(c);
- if (visPos.isNull())
- return c.honorEditableBoundaryAtOrAfter(visPos);
-
return c.honorEditableBoundaryAtOrAfter(visPos);
}