diff options
Diffstat (limited to 'WebCore/editing')
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.cpp | 366 | ||||
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.h | 7 | ||||
-rw-r--r-- | WebCore/editing/Editor.cpp | 227 | ||||
-rw-r--r-- | WebCore/editing/Editor.h | 23 | ||||
-rw-r--r-- | WebCore/editing/EditorCommand.cpp | 15 | ||||
-rw-r--r-- | WebCore/editing/ReplaceSelectionCommand.cpp | 8 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.cpp | 1 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/TypingCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/markup.cpp | 175 |
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, ("&")); + DEFINE_STATIC_LOCAL(const String, ltReference, ("<")); + DEFINE_STATIC_LOCAL(const String, gtReference, (">")); + DEFINE_STATIC_LOCAL(const String, quotReference, (""")); + DEFINE_STATIC_LOCAL(const String, nbspReference, (" ")); + + 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, ("&")); - DEFINE_STATIC_LOCAL(const String, gtEntity, (">")); - DEFINE_STATIC_LOCAL(const String, ltEntity, ("<")); - DEFINE_STATIC_LOCAL(const String, quotEntity, (""")); - DEFINE_STATIC_LOCAL(const String, nbspEntity, (" ")); - - 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, ("&")); - DEFINE_STATIC_LOCAL(const String, gtEntity, (">")); - DEFINE_STATIC_LOCAL(const String, ltEntity, ("<")); - DEFINE_STATIC_LOCAL(const String, nbspEntity, (" ")); - - 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); } |