summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp366
-rw-r--r--WebCore/editing/ApplyStyleCommand.h7
-rw-r--r--WebCore/editing/Editor.cpp227
-rw-r--r--WebCore/editing/Editor.h23
-rw-r--r--WebCore/editing/EditorCommand.cpp15
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp8
-rw-r--r--WebCore/editing/SelectionController.cpp1
-rw-r--r--WebCore/editing/TextIterator.cpp2
-rw-r--r--WebCore/editing/TypingCommand.cpp4
-rw-r--r--WebCore/editing/markup.cpp175
10 files changed, 464 insertions, 364 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp
index 0a02e7a..4385a16 100644
--- a/WebCore/editing/ApplyStyleCommand.cpp
+++ b/WebCore/editing/ApplyStyleCommand.cpp
@@ -909,6 +909,16 @@ void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsp
}
}
+static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
+{
+ for (Node* n = startNode; n && n != enclosingNode; n = n->parent()) {
+ if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
+ return n;
+ }
+
+ return 0;
+}
+
void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
{
Node* startDummySpanAncestor = 0;
@@ -952,56 +962,51 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
endDummySpanAncestor = dummySpanAncestorForNode(end.node());
}
+ // Remove style from the selection.
+ // Use the upstream position of the start for removing style.
+ // This will ensure we remove all traces of the relevant styles from the selection
+ // and prevent us from adding redundant ones, as described in:
+ // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
+ Position removeStart = start.upstream();
int unicodeBidi = getIdentifierValue(style, CSSPropertyUnicodeBidi);
int direction = 0;
- HTMLElement* startUnsplitAncestor = 0;
- HTMLElement* endUnsplitAncestor = 0;
+ RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding;
if (unicodeBidi) {
// Leave alone an ancestor that provides the desired single level embedding, if there is one.
if (unicodeBidi == CSSValueEmbed)
direction = getIdentifierValue(style, CSSPropertyDirection);
- startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, direction);
- endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, direction);
+ HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, direction);
+ HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, direction);
removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor);
removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor);
- }
- // Remove style from the selection.
- // Use the upstream position of the start for removing style.
- // This will ensure we remove all traces of the relevant styles from the selection
- // and prevent us from adding redundant ones, as described in:
- // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
- Position removeStart = start.upstream();
- Position embeddingRemoveStart = removeStart;
- Position embeddingRemoveEnd = end;
- if (unicodeBidi) {
// Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
+ Position embeddingRemoveStart = removeStart;
if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
+
+ Position embeddingRemoveEnd = end;
if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
- }
-
- if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) {
- RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
- embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
- embeddingStyle->setProperty(CSSPropertyDirection, direction);
- if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
- removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
- RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
- styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
- styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
- removeInlineStyle(styleWithoutEmbedding, removeStart, end);
- } else
- removeInlineStyle(style, removeStart, end);
+ if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
+ RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
+ embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
+ embeddingStyle->setProperty(CSSPropertyDirection, direction);
+ if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
+ removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
+ styleWithoutEmbedding = style->copy();
+ styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
+ styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
+ }
+ }
+ removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
start = startPosition();
end = endPosition();
if (splitStart) {
- bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
- if (mergedStart) {
+ if (mergeStartWithPreviousIfIdentical(start, end)) {
start = startPosition();
end = endPosition();
}
@@ -1018,41 +1023,33 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
// to check a computed style
updateLayout();
- Position embeddingApplyStart = start;
- Position embeddingApplyEnd = end;
+ RefPtr<CSSMutableStyleDeclaration> styleToApply = style;
if (unicodeBidi) {
// Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
- Node* startEnclosingBlock = enclosingBlock(start.node());
- for (Node* n = start.node(); n != startEnclosingBlock; n = n->parent()) {
- if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) {
- embeddingApplyStart = positionInParentAfterNode(n);
- break;
- }
- }
+ Node* embeddingStartNode = highestEmbeddingAncestor(start.node(), enclosingBlock(start.node()));
+ Node* embeddingEndNode = highestEmbeddingAncestor(end.node(), enclosingBlock(end.node()));
- Node* endEnclosingBlock = enclosingBlock(end.node());
- for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) {
- if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) {
- embeddingApplyEnd = positionInParentBeforeNode(n);
- break;
- }
- }
- }
+ if (embeddingStartNode || embeddingEndNode) {
+ Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
+ Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
+ ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
- if (embeddingApplyStart != start || embeddingApplyEnd != end) {
- if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) {
RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
embeddingStyle->setProperty(CSSPropertyDirection, direction);
applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
+
+ if (styleWithoutEmbedding)
+ styleToApply = styleWithoutEmbedding;
+ else {
+ styleToApply = style->copy();
+ styleToApply->removeProperty(CSSPropertyUnicodeBidi);
+ styleToApply->removeProperty(CSSPropertyDirection);
+ }
}
+ }
- RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
- styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
- styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
- applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end);
- } else
- applyInlineStyleToRange(style, start, end);
+ applyInlineStyleToRange(styleToApply.get(), start, end);
// Remove dummy style spans created by splitting text elements.
cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
@@ -1135,94 +1132,113 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl
}
}
-bool ApplyStyleCommand::shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const
-{
- // Honor text-decorations-in-effect
- RefPtr<CSSValue> textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
- if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
- textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyTextDecoration);
-
- // When there is no text decorations to apply, remove any one of u, s, & strike
- if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
- return true;
-
- // Remove node if it implicitly adds style not present in styleToApply
- CSSValueList* valueList = static_cast<CSSValueList*>(textDecorationsToApply.get());
- RefPtr<CSSPrimitiveValue> value = CSSPrimitiveValue::createIdentifier(textDecorationAddedByTag);
- return !valueList->hasValue(value.get());
-}
-
-// This function maps from styling tags to CSS styles. Used for knowing which
-// styling tags should be removed when toggling styles.
-bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style)
-{
- CSSMutableStyleDeclaration::const_iterator end = style->end();
- for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
- const CSSProperty& property = *it;
- // FIXME: This should probably be re-written to lookup the tagname in a
- // hash and match against an expected property/value pair.
- switch (property.id()) {
- case CSSPropertyFontWeight:
- // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational
- if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag))
- return !equalIgnoringCase(property.value()->cssText(), "bold") || !elem->hasChildNodes();
- break;
- case CSSPropertyVerticalAlign:
- if (elem->hasLocalName(subTag))
- return !equalIgnoringCase(property.value()->cssText(), "sub") || !elem->hasChildNodes();
- if (elem->hasLocalName(supTag))
- return !equalIgnoringCase(property.value()->cssText(), "sup") || !elem->hasChildNodes();
- break;
- case CSSPropertyFontStyle:
- // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational
- if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag))
- return !equalIgnoringCase(property.value()->cssText(), "italic") || !elem->hasChildNodes();
- break;
- case CSSPropertyTextDecoration:
- case CSSPropertyWebkitTextDecorationsInEffect:
- if (elem->hasLocalName(uTag))
- return shouldRemoveTextDecorationTag(style, CSSValueUnderline) || !elem->hasChildNodes();
- else if (elem->hasLocalName(sTag) || elem->hasTagName(strikeTag))
- return shouldRemoveTextDecorationTag(style,CSSValueLineThrough) || !elem->hasChildNodes();
- }
- }
- return false;
-}
-
bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element, InlineStyleRemovalMode mode)
{
ASSERT(style);
ASSERT(element);
- bool removed = false;
-
if (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) {
- removed = true;
if (mode == RemoveAttributesAndElements)
removeNodePreservingChildren(element);
+ return true;
}
- if (implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(element, style)) {
+ bool removed = false;
+ if (removeImplicitlyStyledElement(style, element, mode))
removed = true;
- if (mode == RemoveAttributesAndElements)
- replaceWithSpanOrRemoveIfWithoutAttributes(element);
- }
if (!element->inDocument())
return removed;
// If the node was converted to a span, the span may still contain relevant
// styles which must be removed (e.g. <b style='font-weight: bold'>)
- if (removeHTMLFontStyle(style, element, mode))
- removed = true;
- if (removeHTMLBidiEmbeddingStyle(style, element, mode))
- removed = true;
if (removeCSSStyle(style, element, mode))
removed = true;
return removed;
}
+
+enum EPushDownType { ShouldBePushedDown, ShouldNotBePushedDown };
+struct HTMLEquivalent {
+ int propertyID;
+ bool isValueList;
+ int primitiveId;
+ const QualifiedName* element;
+ const QualifiedName* attribute;
+ EPushDownType pushDownType;
+};
+
+static const HTMLEquivalent HTMLEquivalents[] = {
+ { CSSPropertyFontWeight, false, CSSValueBold, &bTag, 0, ShouldBePushedDown },
+ { CSSPropertyFontWeight, false, CSSValueBold, &strongTag, 0, ShouldBePushedDown },
+ { CSSPropertyVerticalAlign, false, CSSValueSub, &subTag, 0, ShouldBePushedDown },
+ { CSSPropertyVerticalAlign, false, CSSValueSuper, &supTag, 0, ShouldBePushedDown },
+ { CSSPropertyFontStyle, false, CSSValueItalic, &iTag, 0, ShouldBePushedDown },
+ { CSSPropertyFontStyle, false, CSSValueItalic, &emTag, 0, ShouldBePushedDown },
+
+ // text-decorations should be CSSValueList
+ { CSSPropertyTextDecoration, true, CSSValueUnderline, &uTag, 0, ShouldBePushedDown },
+ { CSSPropertyTextDecoration, true, CSSValueLineThrough, &sTag, 0, ShouldBePushedDown },
+ { CSSPropertyTextDecoration, true, CSSValueLineThrough, &strikeTag, 0, ShouldBePushedDown },
+ { CSSPropertyWebkitTextDecorationsInEffect, true, CSSValueUnderline, &uTag, 0, ShouldBePushedDown },
+ { CSSPropertyWebkitTextDecorationsInEffect, true, CSSValueLineThrough, &sTag, 0, ShouldBePushedDown },
+ { CSSPropertyWebkitTextDecorationsInEffect, true, CSSValueLineThrough, &strikeTag, 0, ShouldBePushedDown },
+
+ // FIXME: font attributes should only be removed if values were different
+ { CSSPropertyColor, false, CSSValueInvalid, &fontTag, &colorAttr, ShouldBePushedDown },
+ { CSSPropertyFontFamily, false, CSSValueInvalid, &fontTag, &faceAttr, ShouldBePushedDown },
+ { CSSPropertyFontSize, false, CSSValueInvalid, &fontTag, &sizeAttr, ShouldBePushedDown },
+
+ // unicode-bidi and direction are pushed down separately so don't push down with other styles.
+ { CSSPropertyUnicodeBidi, false, CSSValueInvalid, 0, &dirAttr, ShouldNotBePushedDown },
+ { CSSPropertyDirection, false, CSSValueInvalid, 0, &dirAttr, ShouldNotBePushedDown },
+};
+
+bool ApplyStyleCommand::removeImplicitlyStyledElement(CSSMutableStyleDeclaration* style, HTMLElement* element, InlineStyleRemovalMode mode, CSSMutableStyleDeclaration* extractedStyle)
+{
+ // Current implementation does not support stylePushedDown when mode == RemoveNone because of early exit.
+ ASSERT(!extractedStyle || mode != RemoveNone);
+ bool removed = false;
+ for (size_t i = 0; i < sizeof(HTMLEquivalents) / sizeof(HTMLEquivalent); i++) {
+ const HTMLEquivalent& equivalent = HTMLEquivalents[i];
+ ASSERT(equivalent.element || equivalent.attribute);
+ if ((extractedStyle && equivalent.pushDownType == ShouldNotBePushedDown)
+ || (equivalent.element && !element->hasTagName(*equivalent.element))
+ || (equivalent.attribute && !element->hasAttribute(*equivalent.attribute)))
+ continue;
+
+ RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(equivalent.propertyID);
+ if (!styleValue)
+ continue;
+ RefPtr<CSSPrimitiveValue> mapValue = CSSPrimitiveValue::createIdentifier(equivalent.primitiveId);
+
+ if (equivalent.isValueList && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(mapValue.get()))
+ continue; // If CSS value assumes CSSValueList, then only skip if the value was present in style to apply.
+ else if (styleValue->cssText() == mapValue->cssText())
+ continue; // If CSS value is primitive, then skip if they are equal.
+
+ if (extractedStyle) {
+ if (equivalent.primitiveId == CSSValueInvalid)
+ extractedStyle->setProperty(equivalent.propertyID, element->getAttribute(*equivalent.attribute));
+ else
+ extractedStyle->setProperty(equivalent.propertyID, mapValue->cssText());
+ }
+ if (mode == RemoveNone)
+ return true;
+
+ removed = true;
+ if (!equivalent.attribute) {
+ replaceWithSpanOrRemoveIfWithoutAttributes(element);
+ break;
+ }
+ removeNodeAttribute(element, *equivalent.attribute);
+ if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyleStyleSpan(element))
+ removeNodePreservingChildren(element);
+ }
+ return removed;
+}
+
void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
{
bool removeNode = false;
@@ -1247,66 +1263,6 @@ void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&
}
}
-bool ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem, InlineStyleRemovalMode mode)
-{
- ASSERT(style);
- ASSERT(elem);
-
- if (!elem->hasLocalName(fontTag))
- return false;
-
- bool removed = false;
- CSSMutableStyleDeclaration::const_iterator end = style->end();
- for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
- const QualifiedName* attrToRemove = 0;
- switch ((*it).id()) {
- case CSSPropertyColor:
- attrToRemove = &colorAttr;
- break;
- case CSSPropertyFontFamily:
- attrToRemove = &faceAttr;
- break;
- case CSSPropertyFontSize:
- attrToRemove = &sizeAttr;
- break;
- }
-
- if (attrToRemove) {
- removed = true;
- if (mode == RemoveAttributesAndElements)
- removeNodeAttribute(elem, *attrToRemove);
- }
- }
-
- if (isEmptyFontTag(elem) && mode == RemoveAttributesAndElements)
- removeNodePreservingChildren(elem);
-
- return removed;
-}
-
-bool ApplyStyleCommand::removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem, InlineStyleRemovalMode mode)
-{
- ASSERT(style);
- ASSERT(elem);
-
- if (!elem->hasAttribute(dirAttr))
- return false;
-
- if (!style->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection))
- return false;
-
- if (mode == RemoveNone)
- return true;
-
- removeNodeAttribute(elem, dirAttr);
-
- // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test.
- if (isUnstyledStyleSpan(elem))
- removeNodePreservingChildren(elem);
-
- return true;
-}
-
bool ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem, InlineStyleRemovalMode mode)
{
ASSERT(style);
@@ -1364,7 +1320,7 @@ HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(CSSMut
return result;
}
-PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPushDown(Node* node, bool isStyledElement, const Vector<int>& properties)
+PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPushDown(CSSMutableStyleDeclaration* styleToApply, Node* node, bool isStyledElement)
{
ASSERT(node);
ASSERT(node->isElementNode());
@@ -1380,8 +1336,16 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPu
return style.release();
}
- if (!style)
- return 0;
+ if (!style) {
+ style = CSSMutableStyleDeclaration::create();
+ removeImplicitlyStyledElement(styleToApply, element, RemoveAttributesAndElements, style.get());
+ return style.release();
+ }
+
+ Vector<int> properties;
+ CSSMutableStyleDeclaration::const_iterator end = styleToApply->end();
+ for (CSSMutableStyleDeclaration::const_iterator it = styleToApply->begin(); it != end; ++it)
+ properties.append(it->id());
style = style->copyPropertiesInSet(properties.data(), properties.size());
for (size_t i = 0; i < properties.size(); i++) {
@@ -1396,6 +1360,8 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPu
if (isSpanWithoutAttributesOrUnstyleStyleSpan(element))
removeNodePreservingChildren(element);
+ removeImplicitlyStyledElement(styleToApply, element, RemoveAttributesAndElements, style.get());
+
return style.release();
}
@@ -1406,15 +1372,14 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, CSSMutableStyleDe
if (!style || !style->length() || !node->renderer())
return;
- // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
- // FIXME: applyInlineStyleToRange should be used here instead.
- if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
+ RefPtr<CSSMutableStyleDeclaration> newInlineStyle = style;
+ if (node->isHTMLElement()) {
HTMLElement* element = static_cast<HTMLElement*>(node);
CSSMutableStyleDeclaration* existingInlineStyle = element->inlineStyleDecl();
// Avoid overriding existing styles of node
if (existingInlineStyle) {
- RefPtr<CSSMutableStyleDeclaration> newInlineStyle = existingInlineStyle->copy();
+ newInlineStyle = existingInlineStyle->copy();
CSSMutableStyleDeclaration::const_iterator end = style->end();
for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
ExceptionCode ec;
@@ -1441,22 +1406,23 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, CSSMutableStyleDe
}
}
}
+ }
+ }
- setNodeAttribute(element, styleAttr, newInlineStyle->cssText());
- } else
- setNodeAttribute(element, styleAttr, style->cssText());
-
+ // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
+ // FIXME: applyInlineStyleToRange should be used here instead.
+ if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
+ setNodeAttribute(static_cast<HTMLElement*>(node), styleAttr, newInlineStyle->cssText());
return;
}
if (node->renderer()->isText() && static_cast<RenderText*>(node->renderer())->isAllCollapsibleWhitespace())
return;
- // FIXME: addInlineStyleIfNeeded may override the style of node
// We can't wrap node with the styled element here because new styled element will never be removed if we did.
// If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
// then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
- addInlineStyleIfNeeded(style, node, node, DoNotAddStyledElement);
+ addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
}
void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration* style, Node* targetNode)
@@ -1465,11 +1431,6 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration
if (!highestAncestor)
return;
- Vector<int> properties;
- CSSMutableStyleDeclaration::const_iterator end = style->end();
- for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it)
- properties.append(it->id());
-
// The outer loop is traversing the tree vertically from highestAncestor to targetNode
Node* current = highestAncestor;
while (current != targetNode) {
@@ -1481,7 +1442,7 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration
RefPtr<StyledElement> styledElement;
if (current->isStyledElement() && m_styledInlineElement && current->hasTagName(m_styledInlineElement->tagQName()))
styledElement = static_cast<StyledElement*>(current);
- RefPtr<CSSMutableStyleDeclaration> styleToPushDown = extractInlineStyleToPushDown(current, styledElement, properties);
+ RefPtr<CSSMutableStyleDeclaration> styleToPushDown = extractInlineStyleToPushDown(style, current, styledElement);
// The inner loop will go through children on each level
// FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
@@ -1533,6 +1494,13 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
}
Position pushDownStart = start.downstream();
+ // If the pushDownStart is at the end of a text node, then this node is not fully selected.
+ // Move it to the next deep quivalent position to avoid removing the style from this node.
+ // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
+ Node* pushDownStartContainer = pushDownStart.containerNode();
+ if (pushDownStartContainer && pushDownStartContainer->isTextNode()
+ && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
+ pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
Position pushDownEnd = end.upstream();
pushDownInlineStyleAroundNode(style.get(), pushDownStart.node());
pushDownInlineStyleAroundNode(style.get(), pushDownEnd.node());
diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h
index abe4909..969384a 100644
--- a/WebCore/editing/ApplyStyleCommand.h
+++ b/WebCore/editing/ApplyStyleCommand.h
@@ -71,16 +71,13 @@ private:
CSSMutableStyleDeclaration* style() const { return m_style.get(); }
// style-removal helpers
- bool shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const;
- bool implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement*, CSSMutableStyleDeclaration*);
bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
inline bool shouldRemoveInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element) {return removeInlineStyleFromElement(style, element, RemoveNone);}
+ bool removeImplicitlyStyledElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode, CSSMutableStyleDeclaration* extractedStyle = 0);
void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&);
- bool removeHTMLFontStyle(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
- bool removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
bool removeCSSStyle(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
HTMLElement* highestAncestorWithConflictingInlineStyle(CSSMutableStyleDeclaration*, Node*);
- PassRefPtr<CSSMutableStyleDeclaration> extractInlineStyleToPushDown(Node*, bool isStyledElement, const Vector<int>&);
+ PassRefPtr<CSSMutableStyleDeclaration> extractInlineStyleToPushDown(CSSMutableStyleDeclaration*, Node*, bool isStyledElement);
void applyInlineStyleToPushDown(Node*, CSSMutableStyleDeclaration *style);
void pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration*, Node*);
void removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>, const Position& start, const Position& end);
diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp
index 3dd6e27..117292c 100644
--- a/WebCore/editing/Editor.cpp
+++ b/WebCore/editing/Editor.cpp
@@ -918,6 +918,56 @@ TriState Editor::selectionHasStyle(CSSStyleDeclaration* style) const
return state;
}
+
+static bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
+{
+ RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
+ if (!cssValue)
+ return true;
+
+ if (!cssValue->isPrimitiveValue())
+ return false;
+ CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(cssValue.get());
+
+ if (value->primitiveType() == CSSPrimitiveValue::CSS_RGBCOLOR)
+ return !alphaChannel(value->getRGBA32Value());
+
+ return value->getIdent() == CSSValueTransparent;
+}
+
+String Editor::selectionStartCSSPropertyValue(int propertyID)
+{
+ Node* nodeToRemove;
+ RefPtr<CSSStyleDeclaration> selectionStyle = m_frame->selectionComputedStyle(nodeToRemove);
+ if (!selectionStyle)
+ return String();
+
+ String value = selectionStyle->getPropertyValue(propertyID);
+
+ if (nodeToRemove) {
+ ExceptionCode ec = 0;
+ nodeToRemove->remove(ec);
+ ASSERT(!ec);
+ }
+
+ // If background color is transparent, traverse parent nodes until we hit a different value or document root
+ // Also, if the selection is a range, ignore the background color at the start of selection,
+ // and find the background color of the common ancestor.
+ if (propertyID == CSSPropertyBackgroundColor && (m_frame->selection()->isRange() || hasTransparentBackgroundColor(selectionStyle.get()))) {
+ RefPtr<Range> range(m_frame->selection()->toNormalizedRange());
+ ExceptionCode ec = 0;
+ for (Node* ancestor = range->commonAncestorContainer(ec); ancestor; ancestor = ancestor->parentNode()) {
+ selectionStyle = computedStyle(ancestor);
+ if (!hasTransparentBackgroundColor(selectionStyle.get())) {
+ value = selectionStyle->getPropertyValue(CSSPropertyBackgroundColor);
+ break;
+ }
+ }
+ }
+
+ return value;
+}
+
void Editor::indent()
{
applyCommand(IndentOutdentCommand::create(m_frame->document(), IndentOutdentCommand::Indent));
@@ -1003,11 +1053,16 @@ Editor::Editor(Frame* frame)
// This is off by default, since most editors want this behavior (this matches IE but not FF).
, m_shouldStyleWithCSS(false)
, m_killRing(adoptPtr(new KillRing))
-{
+ , m_correctionPanelTimer(this, &Editor::correctionPanelTimerFired)
+{
}
Editor::~Editor()
{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ if (client())
+ client()->dismissCorrectionPanel(true);
+#endif
}
void Editor::clear()
@@ -2301,22 +2356,29 @@ bool Editor::spellingPanelIsShowing()
void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
{
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
- bool markSpelling = isContinuousSpellCheckingEnabled();
- bool markGrammar = markSpelling && isGrammarCheckingEnabled();
- bool performTextCheckingReplacements = isAutomaticQuoteSubstitutionEnabled()
- || isAutomaticLinkDetectionEnabled()
- || isAutomaticDashSubstitutionEnabled()
- || isAutomaticTextReplacementEnabled()
- || (markSpelling && isAutomaticSpellingCorrectionEnabled());
- if (!markSpelling && !performTextCheckingReplacements)
+ TextCheckingOptions textCheckingOptions = 0;
+ if (isContinuousSpellCheckingEnabled())
+ textCheckingOptions |= MarkSpelling;
+
+ if (isAutomaticQuoteSubstitutionEnabled()
+ || isAutomaticLinkDetectionEnabled()
+ || isAutomaticDashSubstitutionEnabled()
+ || isAutomaticTextReplacementEnabled()
+ || ((textCheckingOptions & MarkSpelling) && isAutomaticSpellingCorrectionEnabled()))
+ textCheckingOptions |= PerformReplacement;
+
+ if (!textCheckingOptions & (MarkSpelling | PerformReplacement))
return;
-
+
+ if (isGrammarCheckingEnabled())
+ textCheckingOptions |= MarkGrammar;
+
VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary));
- if (markGrammar) {
+ if (textCheckingOptions & MarkGrammar) {
VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p));
- markAllMisspellingsAndBadGrammarInRanges(true, adjacentWords.toNormalizedRange().get(), true, selectedSentence.toNormalizedRange().get(), performTextCheckingReplacements);
+ markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get());
} else {
- markAllMisspellingsAndBadGrammarInRanges(markSpelling, adjacentWords.toNormalizedRange().get(), false, adjacentWords.toNormalizedRange().get(), performTextCheckingReplacements);
+ markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), adjacentWords.toNormalizedRange().get());
}
#else
if (!isContinuousSpellCheckingEnabled())
@@ -2460,13 +2522,18 @@ static inline bool isAmbiguousBoundaryCharacter(UChar character)
return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim;
}
-void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range* spellingRange, bool markGrammar, Range* grammarRange, bool performTextCheckingReplacements)
+void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCheckingOptions, Range* spellingRange, Range* grammarRange)
{
+ bool shouldMarkSpelling = textCheckingOptions & MarkSpelling;
+ bool shouldMarkGrammar = textCheckingOptions & MarkGrammar;
+ bool shouldPerformReplacement = textCheckingOptions & PerformReplacement;
+ bool shouldShowCorrectionPanel = textCheckingOptions & ShowCorrectionPanel;
+
// This function is called with selections already expanded to word boundaries.
ExceptionCode ec = 0;
- if (!client() || !spellingRange || (markGrammar && !grammarRange))
+ if (!client() || !spellingRange || (shouldMarkGrammar && !grammarRange))
return;
-
+
// If we're not in an editable node, bail.
Node* editableNode = spellingRange->startContainer();
if (!editableNode || !editableNode->isContentEditable())
@@ -2489,8 +2556,8 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
bool adjustSelectionForParagraphBoundaries = false;
String paragraphString;
RefPtr<Range> paragraphRange;
-
- if (markGrammar) {
+
+ if (shouldMarkGrammar) {
// The spelling range should be contained in the paragraph-aligned extension of the grammar range.
paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString);
RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition());
@@ -2501,10 +2568,10 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
}
spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange);
paragraphLength = paragraphString.length();
- if (paragraphLength <= 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset)))
+ if (paragraphLength <= 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!shouldMarkGrammar || grammarRangeStartOffset >= grammarRangeEndOffset)))
return;
-
- if (performTextCheckingReplacements) {
+
+ if (shouldPerformReplacement) {
if (m_frame->selection()->selectionType() == VisibleSelection::CaretSelection) {
// Attempt to save the caret position so we can restore it later if needed
RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), paragraphRange->startPosition());
@@ -2520,14 +2587,16 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
}
}
}
-
+
Vector<TextCheckingResult> results;
uint64_t checkingTypes = 0;
- if (markSpelling)
+ if (shouldMarkSpelling)
checkingTypes |= TextCheckingTypeSpelling;
- if (markGrammar)
+ if (shouldMarkGrammar)
checkingTypes |= TextCheckingTypeGrammar;
- if (performTextCheckingReplacements) {
+ if (shouldShowCorrectionPanel)
+ checkingTypes |= TextCheckingTypeCorrection;
+ if (shouldPerformReplacement) {
if (isAutomaticLinkDetectionEnabled())
checkingTypes |= TextCheckingTypeLink;
if (isAutomaticQuoteSubstitutionEnabled())
@@ -2536,20 +2605,26 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
checkingTypes |= TextCheckingTypeDash;
if (isAutomaticTextReplacementEnabled())
checkingTypes |= TextCheckingTypeReplacement;
- if (markSpelling && isAutomaticSpellingCorrectionEnabled())
+ if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
checkingTypes |= TextCheckingTypeCorrection;
}
client()->checkTextOfParagraph(paragraphString.characters(), paragraphLength, checkingTypes, results);
-
+
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ // If this checking is only for showing correction panel, we shouldn't bother to mark misspellings.
+ if (shouldShowCorrectionPanel)
+ shouldMarkSpelling = false;
+#endif
+
for (unsigned i = 0; i < results.size(); i++) {
const TextCheckingResult* result = &results[i];
int resultLocation = result->location + offsetDueToReplacement;
int resultLength = result->length;
- if (markSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingRangeStartOffset && resultLocation + resultLength <= spellingRangeEndOffset) {
+ if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingRangeStartOffset && resultLocation + resultLength <= spellingRangeEndOffset) {
ASSERT(resultLength > 0 && resultLocation >= 0);
RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, resultLocation - spellingRangeStartOffset, resultLength);
misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
- } else if (markGrammar && result->type == TextCheckingTypeGrammar && resultLocation < grammarRangeEndOffset && resultLocation + resultLength > grammarRangeStartOffset) {
+ } else if (shouldMarkGrammar && result->type == TextCheckingTypeGrammar && resultLocation < grammarRangeEndOffset && resultLocation + resultLength > grammarRangeStartOffset) {
ASSERT(resultLength > 0 && resultLocation >= 0);
for (unsigned j = 0; j < result->details.size(); j++) {
const GrammarDetail* detail = &result->details[j];
@@ -2559,7 +2634,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
grammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
}
}
- } else if (performTextCheckingReplacements && resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingRangeStartOffset
+ } else if ((shouldPerformReplacement || shouldShowCorrectionPanel) && resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingRangeStartOffset
&& (result->type == TextCheckingTypeLink
|| result->type == TextCheckingTypeQuote
|| result->type == TextCheckingTypeDash
@@ -2590,7 +2665,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
size_t markerCount = markers.size();
for (size_t i = 0; i < markerCount; ++i) {
const DocumentMarker& marker = markers[i];
- if (marker.type == DocumentMarker::Replacement && static_cast<int>(marker.startOffset) < endOffset && static_cast<int>(marker.endOffset) > startOffset) {
+ if ((marker.type == DocumentMarker::Replacement || marker.type == DocumentMarker::RejectedCorrection) && static_cast<int>(marker.startOffset) < endOffset && static_cast<int>(marker.endOffset) > startOffset) {
doReplacement = false;
break;
}
@@ -2598,7 +2673,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
break;
}
}
- if (doReplacement && selectionToReplace != m_frame->selection()->selection()) {
+ if (doReplacement && !shouldShowCorrectionPanel && selectionToReplace != m_frame->selection()->selection()) {
if (m_frame->shouldChangeSelection(selectionToReplace)) {
m_frame->selection()->setSelection(selectionToReplace);
selectionChanged = true;
@@ -2606,30 +2681,52 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range*
doReplacement = false;
}
}
+
+ String replacedString;
if (doReplacement) {
if (result->type == TextCheckingTypeLink) {
restoreSelectionAfterChange = false;
if (canEditRichly())
applyCommand(CreateLinkCommand::create(m_frame->document(), result->replacement));
} else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
- String replacedString;
if (result->type == TextCheckingTypeCorrection)
replacedString = plainText(rangeToReplace.get());
- replaceSelectionWithText(result->replacement, false, false);
- spellingRangeEndOffset += replacementLength - resultLength;
- offsetDueToReplacement += replacementLength - resultLength;
- if (resultLocation < selectionOffset)
- selectionOffset += replacementLength - resultLength;
- if (result->type == TextCheckingTypeCorrection) {
- // Add a marker so that corrections can easily be undone and won't be re-corrected.
- RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength);
- replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString);
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ if (shouldShowCorrectionPanel && resultLocation + resultLength == spellingRangeEndOffset && result->type == TextCheckingTypeCorrection) {
+ // We only show the correction panel on the last word.
+ Vector<FloatQuad> textQuads;
+ rangeToReplace->textQuads(textQuads);
+ Vector<FloatQuad>::const_iterator end = textQuads.end();
+ FloatRect totalBoundingBox;
+ for (Vector<FloatQuad>::const_iterator it = textQuads.begin(); it < end; ++it)
+ totalBoundingBox.unite(it->boundingBox());
+ m_rangeToBeReplacedByCorrection = rangeToReplace;
+ m_stringToBeReplacedByCorrection = replacedString;
+ client()->showCorrectionPanel(totalBoundingBox, m_stringToBeReplacedByCorrection, result->replacement, this);
+ doReplacement = false;
+ }
+#endif
+ if (doReplacement) {
+ replaceSelectionWithText(result->replacement, false, false);
+ spellingRangeEndOffset += replacementLength - resultLength;
+ offsetDueToReplacement += replacementLength - resultLength;
+ if (resultLocation < selectionOffset)
+ selectionOffset += replacementLength - resultLength;
+ if (result->type == TextCheckingTypeCorrection) {
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ if (client())
+ client()->dismissCorrectionPanel(true);
+#endif
+ // Add a marker so that corrections can easily be undone and won't be re-corrected.
+ RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength);
+ replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString);
+ }
}
}
}
}
}
-
+
if (selectionChanged) {
// Restore the caret position if we have made any replacements
setEnd(paragraphRange.get(), endOfParagraph(startOfNextParagraph(paragraphRange->startPosition())));
@@ -2671,7 +2768,10 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
if (!isContinuousSpellCheckingEnabled())
return;
- markAllMisspellingsAndBadGrammarInRanges(true, spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get(), false);
+ TextCheckingOptions textCheckingOptions = MarkSpelling;
+ if (markGrammar && isGrammarCheckingEnabled())
+ textCheckingOptions |= MarkGrammar;
+ markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get());
#else
RefPtr<Range> firstMisspellingRange;
markMisspellings(spellingSelection, firstMisspellingRange);
@@ -2680,6 +2780,45 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec
#endif
}
+void Editor::correctionPanelTimerFired(Timer<Editor>*)
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
+ VisibleSelection selection(frame()->selection()->selection());
+ VisiblePosition start(selection.start(), selection.affinity());
+ VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
+ VisibleSelection adjacentWords = VisibleSelection(p, start);
+ markAllMisspellingsAndBadGrammarInRanges(MarkSpelling | ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
+#endif
+}
+
+void Editor::handleRejectedCorrection()
+{
+ Range* replacedRange = m_rangeToBeReplacedByCorrection.get();
+ if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
+ return;
+
+ replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_stringToBeReplacedByCorrection);
+}
+
+void Editor::startCorrectionPanelTimer()
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ static const double correctionPanelTimerInterval = 0.3;
+ if (client())
+ client()->dismissCorrectionPanel(true);
+ if (isAutomaticSpellingCorrectionEnabled())
+ m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
+#endif
+}
+
+void Editor::handleCancelOperation()
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ if (client())
+ client()->dismissCorrectionPanel(false);
+#endif
+}
+
PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
{
Document* document = m_frame->documentAtPoint(windowPoint);
diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h
index 83aef7e..78e89b4 100644
--- a/WebCore/editing/Editor.h
+++ b/WebCore/editing/Editor.h
@@ -119,6 +119,7 @@ public:
void respondToChangedContents(const VisibleSelection& endingSelection);
TriState selectionHasStyle(CSSStyleDeclaration*) const;
+ String selectionStartCSSPropertyValue(int propertyID);
const SimpleFontData* fontForSelection(bool&) const;
WritingDirection textDirectionForSelection(bool&) const;
@@ -223,7 +224,15 @@ public:
void toggleAutomaticTextReplacement();
bool isAutomaticSpellingCorrectionEnabled();
void toggleAutomaticSpellingCorrection();
- void markAllMisspellingsAndBadGrammarInRanges(bool markSpelling, Range* spellingRange, bool markGrammar, Range* grammarRange, bool performTextCheckingReplacements);
+ enum TextCheckingOptionFlags {
+ MarkSpelling = 1 << 0,
+ MarkGrammar = 1 << 1,
+ PerformReplacement = 1 << 2,
+ ShowCorrectionPanel = 1 << 3,
+ };
+ typedef unsigned TextCheckingOptions;
+
+ void markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions, Range* spellingRange, Range* grammarRange);
void changeBackToReplacedString(const String& replacedString);
#endif
void advanceToNextMisspelling(bool startBeforeSelection = false);
@@ -293,9 +302,13 @@ public:
bool insideVisibleArea(const IntPoint&) const;
bool insideVisibleArea(Range*) const;
PassRefPtr<Range> nextVisibleRange(Range*, const String&, bool forward, bool caseFlag, bool wrapFlag);
-
+
void addToKillRing(Range*, bool prepend);
+ void handleCancelOperation();
+ void startCorrectionPanelTimer();
+ void handleRejectedCorrection();
+
void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle);
void pasteAsPlainText(const String&, bool smartReplace);
@@ -317,6 +330,9 @@ private:
bool m_shouldStartNewKillRingSequence;
bool m_shouldStyleWithCSS;
OwnPtr<KillRing> m_killRing;
+ RefPtr<Range> m_rangeToBeReplacedByCorrection;
+ String m_stringToBeReplacedByCorrection;
+ Timer<Editor> m_correctionPanelTimer;
bool canDeleteRange(Range*) const;
bool canSmartReplaceWithPasteboard(Pasteboard*);
@@ -334,8 +350,9 @@ private:
PassRefPtr<Range> firstVisibleRange(const String&, bool caseFlag);
PassRefPtr<Range> lastVisibleRange(const String&, bool caseFlag);
-
+
void changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle);
+ void correctionPanelTimerFired(Timer<Editor>*);
Node* findEventTargetFromSelection() const;
};
diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp
index eb89593..4b8da0e 100644
--- a/WebCore/editing/EditorCommand.cpp
+++ b/WebCore/editing/EditorCommand.cpp
@@ -239,7 +239,9 @@ static TriState stateStyle(Frame* frame, int propertyID, const char* desiredValu
static String valueStyle(Frame* frame, int propertyID)
{
- return frame->selectionStartStylePropertyValue(propertyID);
+ // FIXME: Rather than retrieving the style at the start of the current selection,
+ // we should retrieve the style present throughout the selection for non-Mac platforms.
+ return frame->editor()->selectionStartCSSPropertyValue(propertyID);
}
static TriState stateTextWritingDirection(Frame* frame, WritingDirection direction)
@@ -1067,6 +1069,14 @@ static bool executeYankAndSelect(Frame* frame, Event*, EditorCommandSource, cons
return true;
}
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+static bool executeCancelOperation(Frame* frame, Event*, EditorCommandSource, const String&)
+{
+ frame->editor()->handleCancelOperation();
+ return true;
+}
+#endif
+
// Supported functions
static bool supported(Frame*, EditorCommandSource)
@@ -1453,6 +1463,9 @@ static const CommandMap& createCommandMap()
{ "Unselect", { executeUnselect, supported, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
{ "Yank", { executeYank, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
{ "YankAndSelect", { executeYankAndSelect, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ { "CancelOperation", { executeCancelOperation, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
+#endif
};
// These unsupported commands are listed here since they appear in the Microsoft
diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp
index d497f30..38f3ed2 100644
--- a/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -992,6 +992,12 @@ void ReplaceSelectionCommand::doApply()
if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote)) {
VisiblePosition destination = startOfInsertedContent.previous();
VisiblePosition startOfParagraphToMove = startOfInsertedContent;
+ // We need to handle the case where we need to merge the end
+ // but our destination node is inside an inline that is the last in the block.
+ // We insert a placeholder before the newly inserted content to avoid being merged into the inline.
+ Node* destinationNode = destination.deepEquivalent().node();
+ if (m_shouldMergeEnd && destinationNode != destinationNode->enclosingInlineElement() && destinationNode->enclosingInlineElement()->nextSibling())
+ insertNodeBefore(createBreakElement(document()), refNode.get());
// Merging the the first paragraph of inserted content with the content that came
// before the selection that was pasted into would also move content after
@@ -1115,7 +1121,7 @@ bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePositi
return false;
// Remove the br if it is collapsed away and so is unnecessary.
- if (!document()->inStrictMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
+ if (!document()->inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
return true;
// A br that was originally holding a line open should be displaced by inserted content or turned into a line break.
diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp
index 3672e3e..97dde55 100644
--- a/WebCore/editing/SelectionController.cpp
+++ b/WebCore/editing/SelectionController.cpp
@@ -118,6 +118,7 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi
m_selection = s;
m_caretRectNeedsUpdate = true;
invalidateCaretRect();
+ updateCaretRect();
return;
}
if (!m_frame) {
diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp
index 57f9a3c..daba80e 100644
--- a/WebCore/editing/TextIterator.cpp
+++ b/WebCore/editing/TextIterator.cpp
@@ -522,7 +522,7 @@ void TextIterator::handleTextBox()
}
String str = renderer->text();
unsigned start = m_offset;
- unsigned end = (m_node == m_endContainer) ? m_endOffset : UINT_MAX;
+ unsigned end = (m_node == m_endContainer) ? static_cast<unsigned>(m_endOffset) : UINT_MAX;
while (m_textBox) {
unsigned textBoxStart = m_textBox->start();
unsigned runStart = max(textBoxStart, start);
diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp
index ac865e5..81a6d5c 100644
--- a/WebCore/editing/TypingCommand.cpp
+++ b/WebCore/editing/TypingCommand.cpp
@@ -307,6 +307,10 @@ void TypingCommand::markMisspellingsAfterTyping()
VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
if (p1 != p2)
document()->frame()->editor()->markMisspellingsAfterTypingToPosition(p1);
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ else
+ document()->frame()->editor()->startCorrectionPanelTimer();
+#endif
}
}
diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp
index 1baf1bc..7bf85a4 100644
--- a/WebCore/editing/markup.cpp
+++ b/WebCore/editing/markup.cpp
@@ -91,6 +91,57 @@ private:
QualifiedName m_name;
String m_value;
};
+
+enum EntityMask {
+ EntityNone = 0x0000,
+ EntityAmp = 0x0001,
+ EntityLt = 0x0002,
+ EntityGt = 0x0004,
+ EntityQuot = 0x0008,
+ EntityNbsp = 0x0010,
+
+ EntityMaskInCDATA = EntityNone,
+ EntityMaskInPCDATA = EntityAmp | EntityLt | EntityGt,
+ EntityMaskInHTMLPCDATA = EntityMaskInPCDATA | EntityNbsp,
+ EntityMaskInAttributeValue = EntityAmp | EntityLt | EntityGt | EntityQuot,
+ EntityMaskInHTMLAttributeValue = EntityMaskInAttributeValue | EntityNbsp,
+};
+
+struct EntityDescription {
+ UChar entity;
+ const String& reference;
+ EntityMask mask;
+};
+
+static void appendCharactersReplacingEntities(Vector<UChar>& out, const UChar* content, size_t length, EntityMask entityMask)
+{
+ DEFINE_STATIC_LOCAL(const String, ampReference, ("&amp;"));
+ DEFINE_STATIC_LOCAL(const String, ltReference, ("&lt;"));
+ DEFINE_STATIC_LOCAL(const String, gtReference, ("&gt;"));
+ DEFINE_STATIC_LOCAL(const String, quotReference, ("&quot;"));
+ DEFINE_STATIC_LOCAL(const String, nbspReference, ("&nbsp;"));
+
+ static const EntityDescription entityMaps[] = {
+ { '&', ampReference, EntityAmp },
+ { '<', ltReference, EntityLt },
+ { '>', gtReference, EntityGt },
+ { '"', quotReference, EntityQuot },
+ { noBreakSpace, nbspReference, EntityNbsp },
+ };
+
+ size_t positionAfterLastEntity = 0;
+ for (size_t i = 0; i < length; i++) {
+ for (size_t m = 0; m < sizeof(entityMaps) / sizeof(EntityDescription); m++) {
+ if (content[i] == entityMaps[m].entity && entityMaps[m].mask & entityMask) {
+ out.append(content + positionAfterLastEntity, i - positionAfterLastEntity);
+ append(out, entityMaps[m].reference);
+ positionAfterLastEntity = i + 1;
+ break;
+ }
+ }
+ }
+ out.append(content + positionAfterLastEntity, length - positionAfterLastEntity);
+}
typedef HashMap<AtomicStringImpl*, AtomicStringImpl*> Namespaces;
@@ -113,12 +164,10 @@ public:
String takeResults();
private:
- void appendAttributeValue(Vector<UChar>& result, const String& attribute, bool escapeNBSP);
- String escapeContentText(const String&, bool escapeNBSP);
+ void appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML);
void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString);
String stringValueForRange(const Node*, const Range*);
- pair<const UChar*, size_t> ucharRange(const Node*, const Range *);
- void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t>& range);
+ void appendNodeValue(Vector<UChar>& out, const Node*, const Range*, EntityMask);
String renderedText(const Node*, const Range*);
bool shouldAddNamespaceElement(const Element*);
bool shouldAddNamespaceAttribute(const Attribute*, Namespaces&);
@@ -221,100 +270,10 @@ String MarkupAccumulator::takeResults()
return String::adopt(result);
}
-void MarkupAccumulator::appendAttributeValue(Vector<UChar>& result, const String& attribute, bool escapeNBSP)
+void MarkupAccumulator::appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML)
{
- const UChar* uchars = attribute.characters();
- unsigned len = attribute.length();
- unsigned lastCopiedFrom = 0;
-
- DEFINE_STATIC_LOCAL(const String, ampEntity, ("&amp;"));
- DEFINE_STATIC_LOCAL(const String, gtEntity, ("&gt;"));
- DEFINE_STATIC_LOCAL(const String, ltEntity, ("&lt;"));
- DEFINE_STATIC_LOCAL(const String, quotEntity, ("&quot;"));
- DEFINE_STATIC_LOCAL(const String, nbspEntity, ("&nbsp;"));
-
- for (unsigned i = 0; i < len; ++i) {
- UChar c = uchars[i];
- switch (c) {
- case '&':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, ampEntity);
- lastCopiedFrom = i + 1;
- break;
- case '<':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, ltEntity);
- lastCopiedFrom = i + 1;
- break;
- case '>':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, gtEntity);
- lastCopiedFrom = i + 1;
- break;
- case '"':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, quotEntity);
- lastCopiedFrom = i + 1;
- break;
- case noBreakSpace:
- if (!escapeNBSP)
- break;
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, nbspEntity);
- lastCopiedFrom = i + 1;
- }
- }
-
- result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
-}
-
-static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
-{
- const UChar* uchars = range.first;
- unsigned len = range.second;
- unsigned lastCopiedFrom = 0;
-
- DEFINE_STATIC_LOCAL(const String, ampEntity, ("&amp;"));
- DEFINE_STATIC_LOCAL(const String, gtEntity, ("&gt;"));
- DEFINE_STATIC_LOCAL(const String, ltEntity, ("&lt;"));
- DEFINE_STATIC_LOCAL(const String, nbspEntity, ("&nbsp;"));
-
- for (unsigned i = 0; i < len; ++i) {
- UChar c = uchars[i];
- switch (c) {
- case '&':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, ampEntity);
- lastCopiedFrom = i + 1;
- break;
- case '<':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, ltEntity);
- lastCopiedFrom = i + 1;
- break;
- case '>':
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, gtEntity);
- lastCopiedFrom = i + 1;
- break;
- case noBreakSpace:
- if (!escapeNBSP)
- break;
- result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
- append(result, nbspEntity);
- lastCopiedFrom = i + 1;
- break;
- }
- }
-
- result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
-}
-
-String MarkupAccumulator::escapeContentText(const String& in, bool escapeNBSP)
-{
- Vector<UChar> buffer;
- appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
- return String::adopt(buffer);
+ appendCharactersReplacingEntities(result, attribute.characters(), attribute.length(),
+ documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
}
void MarkupAccumulator::appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
@@ -355,7 +314,7 @@ String MarkupAccumulator::stringValueForRange(const Node* node, const Range* ran
return str;
}
-pair<const UChar*, size_t> MarkupAccumulator::ucharRange(const Node* node, const Range* range)
+void MarkupAccumulator::appendNodeValue(Vector<UChar>& out, const Node* node, const Range* range, EntityMask entityMask)
{
String str = node->nodeValue();
const UChar* characters = str.characters();
@@ -372,12 +331,7 @@ pair<const UChar*, size_t> MarkupAccumulator::ucharRange(const Node* node, const
}
}
- return make_pair(characters, length);
-}
-
-void MarkupAccumulator::appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t>& range)
-{
- result.append(range.first, range.second);
+ appendCharactersReplacingEntities(out, characters, length, entityMask);
}
String MarkupAccumulator::renderedText(const Node* node, const Range* range)
@@ -492,19 +446,20 @@ void MarkupAccumulator::appendText(Vector<UChar>& out, Text* text)
parentName = &static_cast<Element*>(text->parentElement())->tagQName();
if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag)) {
- appendUCharRange(out, ucharRange(text, m_range));
+ appendNodeValue(out, text, m_range, EntityMaskInCDATA);
return;
}
if (!shouldAnnotate() || (parentName && *parentName == textareaTag)) {
- appendEscapedContent(out, ucharRange(text, m_range), text->document()->isHTMLDocument());
+ appendNodeValue(out, text, m_range, text->document()->isHTMLDocument() ? EntityMaskInHTMLPCDATA : EntityMaskInPCDATA);
return;
}
bool useRenderedText = !enclosingNodeWithTag(Position(text, 0), selectTag);
- String markup = escapeContentText(useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range), false);
- markup = convertHTMLTextToInterchangeFormat(markup, text);
- append(out, markup);
+ String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range);
+ Vector<UChar> buffer;
+ appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA);
+ append(out, convertHTMLTextToInterchangeFormat(String::adopt(buffer), text));
}
void MarkupAccumulator::appendComment(Vector<UChar>& out, const String& comment)
@@ -1355,7 +1310,7 @@ String urlToMarkup(const KURL& url, const String& title)
append(markup, "<a href=\"");
append(markup, url.string());
append(markup, "\">");
- appendEscapedContent(markup, make_pair(title.characters(), title.length()), false);
+ appendCharactersReplacingEntities(markup, title.characters(), title.length(), EntityMaskInPCDATA);
append(markup, "</a>");
return String::adopt(markup);
}