diff options
Diffstat (limited to 'WebCore/editing')
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.cpp | 297 | ||||
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.h | 8 | ||||
-rw-r--r-- | WebCore/editing/CompositeEditCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/CompositeEditCommand.h | 3 | ||||
-rw-r--r-- | WebCore/editing/Editor.cpp | 60 | ||||
-rw-r--r-- | WebCore/editing/Editor.h | 7 | ||||
-rw-r--r-- | WebCore/editing/HTMLInterchange.h | 3 | ||||
-rw-r--r-- | WebCore/editing/RemoveCSSPropertyCommand.cpp | 16 | ||||
-rw-r--r-- | WebCore/editing/RemoveCSSPropertyCommand.h | 9 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.cpp | 155 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.h | 25 | ||||
-rw-r--r-- | WebCore/editing/htmlediting.h | 1 | ||||
-rw-r--r-- | WebCore/editing/markup.cpp | 167 | ||||
-rw-r--r-- | WebCore/editing/markup.h | 1 | ||||
-rw-r--r-- | WebCore/editing/visible_units.cpp | 32 |
15 files changed, 427 insertions, 361 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index d865468..a9d1509 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -51,6 +51,26 @@ namespace WebCore { using namespace HTMLNames; +static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style) +{ + RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor); + if (!colorValue) + return Color::transparent; + + ASSERT(colorValue->isPrimitiveValue()); + + CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get()); + RGBA32 rgba = 0; + if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) { + CSSParser::parseColor(rgba, colorValue->cssText()); + // Need to take care of named color such as green and black + // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed. + } else + rgba = primitiveColor->getRGBA32Value(); + + return rgba; +} + class StyleChange { public: explicit StyleChange(CSSStyleDeclaration*, const Position&); @@ -142,7 +162,7 @@ void StyleChange::reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style->removeProperty(CSSPropertyTextDecoration); } -static int getIdentifierValue(CSSMutableStyleDeclaration* style, int propertyID) +static int getIdentifierValue(CSSStyleDeclaration* style, int propertyID) { if (!style) return 0; @@ -209,17 +229,8 @@ void StyleChange::extractTextStyles(CSSMutableStyleDeclaration* style) break; } - if (RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor)) { - ASSERT(colorValue->isPrimitiveValue()); - CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get()); - RGBA32 rgba = 0; - if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) { - CSSParser::parseColor(rgba, colorValue->cssText()); - // Need to take care of named color such as green and black - // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed. - } else - rgba = primitiveColor->getRGBA32Value(); - m_applyFontColor = Color(rgba).name(); + if (style->getPropertyCSSValue(CSSPropertyColor)) { + m_applyFontColor = Color(getRGBAFontColor(style)).name(); style->removeProperty(CSSPropertyColor); } @@ -382,6 +393,9 @@ RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDecla if (fontWeightIsBold(result.get()) == fontWeightIsBold(computedStyle)) result->removeProperty(CSSPropertyFontWeight); + if (getRGBAFontColor(result.get()) == getRGBAFontColor(computedStyle)) + result->removeProperty(CSSPropertyColor); + return result; } @@ -477,7 +491,7 @@ void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* nod parentStyle->diff(style.get()); style->diff(editingStyle); } - + ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel) : CompositeEditCommand(document) , m_style(style->makeMutable()) @@ -793,7 +807,7 @@ void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor) } } -HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, RefPtr<CSSPrimitiveValue> allowedDirection) +HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, int allowedDirection) { // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection. // In that case, we return the unsplit ancestor. Otherwise, we return 0. @@ -803,16 +817,13 @@ HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b Node* highestAncestorWithUnicodeBidi = 0; Node* nextHighestAncestorWithUnicodeBidi = 0; - RefPtr<CSSPrimitiveValue> highestAncestorUnicodeBidi; + int highestAncestorUnicodeBidi = 0; for (Node* n = node->parent(); n != block; n = n->parent()) { - RefPtr<CSSValue> unicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (unicodeBidi) { - ASSERT(unicodeBidi->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) { - highestAncestorUnicodeBidi = static_cast<CSSPrimitiveValue*>(unicodeBidi.get()); - nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi; - highestAncestorWithUnicodeBidi = n; - } + int unicodeBidi = getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi); + if (unicodeBidi && unicodeBidi != CSSValueNormal) { + highestAncestorUnicodeBidi = unicodeBidi; + nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi; + highestAncestorWithUnicodeBidi = n; } } @@ -821,16 +832,14 @@ HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b HTMLElement* unsplitAncestor = 0; - if (allowedDirection && highestAncestorUnicodeBidi->getIdent() != CSSValueBidiOverride) { - RefPtr<CSSValue> highestAncestorDirection = computedStyle(highestAncestorWithUnicodeBidi)->getPropertyCSSValue(CSSPropertyDirection); - ASSERT(highestAncestorDirection->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(highestAncestorDirection.get())->getIdent() == allowedDirection->getIdent() && highestAncestorWithUnicodeBidi->isHTMLElement()) { - if (!nextHighestAncestorWithUnicodeBidi) - return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); + if (allowedDirection && highestAncestorUnicodeBidi != CSSValueBidiOverride + && getIdentifierValue(computedStyle(highestAncestorWithUnicodeBidi).get(), CSSPropertyDirection) == allowedDirection + && highestAncestorWithUnicodeBidi->isHTMLElement()) { + if (!nextHighestAncestorWithUnicodeBidi) + return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); - unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); - highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; - } + unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); + highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; } // Split every ancestor through highest ancestor with embedding. @@ -852,39 +861,34 @@ void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsp if (!block) return; - Node* n = node->parent(); - while (n != block && n != unsplitAncestor) { - Node* parent = n->parent(); - if (!n->isStyledElement()) { - n = parent; + Node* parent = 0; + for (Node* n = node->parent(); n != block && n != unsplitAncestor; n = parent) { + parent = n->parent(); + if (!n->isStyledElement()) continue; - } StyledElement* element = static_cast<StyledElement*>(n); - RefPtr<CSSValue> unicodeBidi = computedStyle(element)->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (unicodeBidi) { - ASSERT(unicodeBidi->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) { - // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration, - // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. - // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and - // otherwise it sets the property in the inline style declaration. - if (element->hasAttribute(dirAttr)) { - // FIXME: If this is a BDO element, we should probably just remove it if it has no - // other attributes, like we (should) do with B and I elements. - removeNodeAttribute(element, dirAttr); - } else { - RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy(); - inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); - inlineStyle->removeProperty(CSSPropertyDirection); - setNodeAttribute(element, styleAttr, inlineStyle->cssText()); - // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test. - if (isUnstyledStyleSpan(element)) - removeNodePreservingChildren(element); - } - } + int unicodeBidi = getIdentifierValue(computedStyle(element).get(), CSSPropertyUnicodeBidi); + if (!unicodeBidi || unicodeBidi == CSSValueNormal) + continue; + + // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration, + // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. + // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and + // otherwise it sets the property in the inline style declaration. + if (element->hasAttribute(dirAttr)) { + // FIXME: If this is a BDO element, we should probably just remove it if it has no + // other attributes, like we (should) do with B and I elements. + removeNodeAttribute(element, dirAttr); + } else { + RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy(); + inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); + inlineStyle->removeProperty(CSSPropertyDirection); + setNodeAttribute(element, styleAttr, inlineStyle->cssText()); + // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test. + if (isUnstyledStyleSpan(element)) + removeNodePreservingChildren(element); } - n = parent; } } @@ -931,21 +935,16 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) endDummySpanAncestor = dummySpanAncestorForNode(end.node()); } - RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); - RefPtr<CSSValue> direction; + int unicodeBidi = getIdentifierValue(style, CSSPropertyUnicodeBidi); + int direction = 0; HTMLElement* startUnsplitAncestor = 0; HTMLElement* endUnsplitAncestor = 0; if (unicodeBidi) { - RefPtr<CSSPrimitiveValue> allowedDirection; - ASSERT(unicodeBidi->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() == CSSValueEmbed) { - // Leave alone an ancestor that provides the desired single level embedding, if there is one. - direction = style->getPropertyCSSValue(CSSPropertyDirection); - ASSERT(direction->isPrimitiveValue()); - allowedDirection = static_cast<CSSPrimitiveValue*>(direction.get()); - } - startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, allowedDirection); - endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, allowedDirection); + // 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); removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor); removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor); } @@ -969,7 +968,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) { RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create(); embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed); - embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); + embeddingStyle->setProperty(CSSPropertyDirection, direction); if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0) removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd); @@ -1008,29 +1007,17 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) // 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()) { - RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (ancestorUnicodeBidi) { - ASSERT(ancestorUnicodeBidi->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) { - embeddingApplyStart = positionInParentAfterNode(n); - break; - } - } + if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) { + embeddingApplyStart = positionInParentAfterNode(n); + break; } } Node* endEnclosingBlock = enclosingBlock(end.node()); for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) { - if (n->isHTMLElement()) { - RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (ancestorUnicodeBidi) { - ASSERT(ancestorUnicodeBidi->isPrimitiveValue()); - if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) { - embeddingApplyEnd = positionInParentBeforeNode(n); - break; - } - } + if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) { + embeddingApplyEnd = positionInParentBeforeNode(n); + break; } } } @@ -1039,7 +1026,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) { RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create(); embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed); - embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); + embeddingStyle->setProperty(CSSPropertyDirection, direction); applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd); } @@ -1318,9 +1305,9 @@ bool ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLEl removed = true; if (mode == RemoveNone) return true; - removeCSSProperty(decl, propertyID); + removeCSSProperty(elem, propertyID); if (propertyID == CSSPropertyUnicodeBidi && !decl->getPropertyValue(CSSPropertyDirection).isEmpty()) - removeCSSProperty(decl, CSSPropertyDirection); + removeCSSProperty(elem, CSSPropertyDirection); } } @@ -1380,32 +1367,7 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractTextDecorationS RefPtr<CSSValue> property = style->getPropertyCSSValue(CSSPropertyTextDecoration); if (property && !equalIgnoringCase(property->cssText(), "none")) - removeCSSProperty(style.get(), CSSPropertyTextDecoration); - - return textDecorationStyle.release(); -} - -PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node* node) -{ - ASSERT(node); - ASSERT(node->isElementNode()); - - // non-html elements not handled yet - if (!node->isHTMLElement()) - return 0; - - RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node); - ASSERT(nodeStyle); - - int properties[1] = { CSSPropertyTextDecoration }; - RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = nodeStyle->copyPropertiesInSet(properties, 1); - - RefPtr<CSSValue> property = nodeStyle->getPropertyCSSValue(CSSPropertyTextDecoration); - if (property && !equalIgnoringCase(property->cssText(), "none")) { - RefPtr<CSSMutableStyleDeclaration> newStyle = textDecorationStyle->copy(); - newStyle->setProperty(CSSPropertyTextDecoration, "none"); - applyTextDecorationStyle(node, newStyle.get()); - } + removeCSSProperty(element, CSSPropertyTextDecoration); return textDecorationStyle.release(); } @@ -1443,7 +1405,7 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl surroundNodeRangeWithElement(node, node, createHTMLElement(document(), sTag)); } -void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, bool forceNegate) +void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode) { ASSERT(targetNode); Node* highestAncestor = highestAncestorWithTextDecoration(targetNode); @@ -1455,7 +1417,7 @@ void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, while (current != targetNode) { ASSERT(current); ASSERT(current->contains(targetNode)); - RefPtr<CSSMutableStyleDeclaration> decoration = forceNegate ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current); + RefPtr<CSSMutableStyleDeclaration> decoration = extractTextDecorationStyle(current); // The inner loop will go through children on each level Node* child = current->firstChild(); @@ -1476,34 +1438,6 @@ void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, } } -void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end) -{ - // We need to work in two passes. First we push down any inline - // styles that set text decoration. Then we look for any remaining - // styles (caused by stylesheets) and explicitly negate text - // decoration while pushing down. - - pushDownTextDecorationStyleAroundNode(start.node(), false); - updateLayout(); - pushDownTextDecorationStyleAroundNode(start.node(), true); - - pushDownTextDecorationStyleAroundNode(end.node(), false); - updateLayout(); - pushDownTextDecorationStyleAroundNode(end.node(), true); -} - -// FIXME: Why does this exist? Callers should either use lastOffsetForEditing or lastOffsetInNode -static int maxRangeOffset(Node *n) -{ - if (n->offsetInCharacters()) - return n->maxCharacterOffset(); - - if (n->isElementNode()) - return n->childNodeCount(); - - return 1; -} - void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style, const Position &start, const Position &end) { ASSERT(start.isNotNull()); @@ -1515,7 +1449,8 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); if (textDecorationSpecialProperty) { - pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream()); + pushDownTextDecorationStyleAroundNode(start.downstream().node()); + pushDownTextDecorationStyleAroundNode(end.upstream().node()); style = style->copy(); style->setProperty(CSSPropertyTextDecoration, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect)); } @@ -1545,8 +1480,8 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> // Since elem must have been fully selected, and it is at the end // of the selection, it is clear we can set the new e offset to // the max range offset of prev. - ASSERT(e.deprecatedEditingOffset() >= maxRangeOffset(e.node())); - e = Position(prev, maxRangeOffset(prev)); + ASSERT(e.deprecatedEditingOffset() >= lastOffsetForEditing(e.node())); + e = Position(prev, lastOffsetForEditing(prev)); } } } @@ -1808,33 +1743,6 @@ void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen setNodeAttribute(block, styleAttr, cssText); } -static bool fontColorChangesComputedStyle(const Color& computedStyleColor, StyleChange styleChange) -{ - if (styleChange.applyFontColor()) { - if (Color(styleChange.fontColor()) != computedStyleColor) - return true; - } - return false; -} - -static bool fontSizeChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange) -{ - if (styleChange.applyFontSize()) { - if (styleChange.fontSize().toInt() != computedStyle->fontSize()) - return true; - } - return false; -} - -static bool fontFaceChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange) -{ - if (styleChange.applyFontFace()) { - if (computedStyle->fontDescription().family().family().string() != styleChange.fontFace()) - return true; - } - return false; -} - void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode) { if (m_removeOnly) @@ -1842,27 +1750,18 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style StyleChange styleChange(style, Position(startNode, 0)); - // // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. - // if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) { RefPtr<Element> fontElement = createFontElement(document()); - RenderStyle* computedStyle = startNode->computedStyle(); - - // We only want to insert a font element if it will end up changing the style of the - // text somehow. Otherwise it will be a garbage node that will create problems for us - // most notably when we apply a blockquote style for a message reply. - if (fontColorChangesComputedStyle(computedStyle->color(), styleChange) - || fontFaceChangesComputedStyle(computedStyle, styleChange) - || fontSizeChangesComputedStyle(computedStyle, styleChange)) { - if (styleChange.applyFontColor()) - fontElement->setAttribute(colorAttr, styleChange.fontColor()); - if (styleChange.applyFontFace()) - fontElement->setAttribute(faceAttr, styleChange.fontFace()); - if (styleChange.applyFontSize()) - fontElement->setAttribute(sizeAttr, styleChange.fontSize()); - surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); - } + + if (styleChange.applyFontColor()) + fontElement->setAttribute(colorAttr, styleChange.fontColor()); + if (styleChange.applyFontFace()) + fontElement->setAttribute(faceAttr, styleChange.fontFace()); + if (styleChange.applyFontSize()) + fontElement->setAttribute(sizeAttr, styleChange.fontSize()); + + surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); } if (styleChange.cssStyle().length()) { diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h index 0aa8110..86c24da 100644 --- a/WebCore/editing/ApplyStyleCommand.h +++ b/WebCore/editing/ApplyStyleCommand.h @@ -82,11 +82,9 @@ private: bool nodeFullySelected(Node*, const Position& start, const Position& end) const; bool nodeFullyUnselected(Node*, const Position& start, const Position& end) const; PassRefPtr<CSSMutableStyleDeclaration> extractTextDecorationStyle(Node*); - PassRefPtr<CSSMutableStyleDeclaration> extractAndNegateTextDecorationStyle(Node*); void applyTextDecorationStyle(Node*, CSSMutableStyleDeclaration *style); - void pushDownTextDecorationStyleAroundNode(Node*, bool forceNegate); - void pushDownTextDecorationStyleAtBoundaries(const Position& start, const Position& end); - + void pushDownTextDecorationStyleAroundNode(Node*); + // style-application helpers void applyBlockStyle(CSSMutableStyleDeclaration*); void applyRelativeFontStyleChange(CSSMutableStyleDeclaration*); @@ -108,7 +106,7 @@ private: float computedFontSize(const Node*); void joinChildTextNodes(Node*, const Position& start, const Position& end); - HTMLElement* splitAncestorsWithUnicodeBidi(Node*, bool before, RefPtr<CSSPrimitiveValue> allowedDirection); + HTMLElement* splitAncestorsWithUnicodeBidi(Node*, bool before, int allowedDirection); void removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor); void updateStartEnd(const Position& newStart, const Position& newEnd); diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index c5a8959..5ec87d6 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -370,9 +370,9 @@ void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bo applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); } -void CompositeEditCommand::removeCSSProperty(PassRefPtr<CSSMutableStyleDeclaration> style, CSSPropertyID property) +void CompositeEditCommand::removeCSSProperty(PassRefPtr<StyledElement> element, CSSPropertyID property) { - applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), style, property)); + applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), element, property)); } void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute) diff --git a/WebCore/editing/CompositeEditCommand.h b/WebCore/editing/CompositeEditCommand.h index 0cceaaa..9d69925 100644 --- a/WebCore/editing/CompositeEditCommand.h +++ b/WebCore/editing/CompositeEditCommand.h @@ -34,6 +34,7 @@ namespace WebCore { class CSSStyleDeclaration; class HTMLElement; +class StyledElement; class Text; class CompositeEditCommand : public EditCommand { @@ -68,7 +69,7 @@ protected: void rebalanceWhitespace(); void rebalanceWhitespaceAt(const Position&); void prepareWhitespaceAtPositionForSplit(Position&); - void removeCSSProperty(PassRefPtr<CSSMutableStyleDeclaration>, CSSPropertyID); + void removeCSSProperty(PassRefPtr<StyledElement>, CSSPropertyID); void removeNodeAttribute(PassRefPtr<Element>, const QualifiedName& attribute); void removeChildrenInRange(PassRefPtr<Node>, unsigned from, unsigned to); virtual void removeNode(PassRefPtr<Node>); diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index 44d8d5f..7a1ad4d 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -66,6 +66,7 @@ #include "Settings.h" #include "Sound.h" #include "Text.h" +#include "TextEvent.h" #include "TextIterator.h" #include "TypingCommand.h" #include "UserTypingGestureIndicator.h" @@ -127,6 +128,26 @@ void Editor::handleInputMethodKeydown(KeyboardEvent* event) c->handleInputMethodKeydown(event); } +bool Editor::handleTextEvent(TextEvent* event) +{ + if (event->isPaste()) { + if (event->pastingFragment()) + replaceSelectionWithFragment(event->pastingFragment(), false, event->shouldSmartReplace(), event->shouldMatchStyle()); + else + replaceSelectionWithText(event->data(), false, event->shouldSmartReplace()); + return true; + } + + String data = event->data(); + if (data == "\n") { + if (event->isLineBreak()) + return insertLineBreak(); + return insertParagraphSeparator(); + } + + return insertTextWithoutSendingTextEvent(data, false, event); +} + bool Editor::canEdit() const { return m_frame->selection()->isContentEditable(); @@ -282,11 +303,29 @@ void Editor::deleteSelectionWithSmartDelete(bool smartDelete) applyCommand(DeleteSelectionCommand::create(m_frame->document(), smartDelete)); } +void Editor::pasteAsPlainText(const String& pastingText, bool smartReplace) +{ + Node* target = findEventTargetFromSelection(); + if (!target) + return; + ExceptionCode ec = 0; + target->dispatchEvent(TextEvent::createForPlainTextPaste(m_frame->domWindow(), pastingText, smartReplace), ec); +} + +void Editor::pasteAsFragment(PassRefPtr<DocumentFragment> pastingFragment, bool smartReplace, bool matchStyle) +{ + Node* target = findEventTargetFromSelection(); + if (!target) + return; + ExceptionCode ec = 0; + target->dispatchEvent(TextEvent::createForFragmentPaste(m_frame->domWindow(), pastingFragment, smartReplace, matchStyle), ec); +} + void Editor::pasteAsPlainTextWithPasteboard(Pasteboard* pasteboard) { String text = pasteboard->plainText(m_frame); if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted)) - replaceSelectionWithText(text, false, canSmartReplaceWithPasteboard(pasteboard)); + pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard)); } void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText) @@ -295,7 +334,7 @@ void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText) bool chosePlainText; RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, chosePlainText); if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted)) - replaceSelectionWithFragment(fragment, false, canSmartReplaceWithPasteboard(pasteboard), chosePlainText); + pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), chosePlainText); } bool Editor::canSmartReplaceWithPasteboard(Pasteboard* pasteboard) @@ -706,12 +745,9 @@ void Editor::clearLastEditCommand() // the event handler NOT setting the return value to false bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPolicy policy) { - Node* target = m_frame->selection()->start().element(); - if (!target) - target = m_frame->document()->body(); + Node* target = findEventTargetFromSelection(); if (!target) return true; - target = target->shadowAncestorNode(); RefPtr<Clipboard> clipboard = newGeneralClipboard(policy, m_frame); @@ -726,6 +762,16 @@ bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPoli return !noDefaultProcessing; } +Node* Editor::findEventTargetFromSelection() const +{ + Node* target = m_frame->selection()->start().element(); + if (!target) + target = m_frame->document()->body(); + if (!target) + return 0; + return target->shadowAncestorNode(); +} + void Editor::applyStyle(CSSStyleDeclaration* style, EditAction editingAction) { switch (m_frame->selection()->selectionType()) { @@ -2359,7 +2405,7 @@ bool Editor::spellCheckingEnabledInFocusedNode() const const Node* node = frame()->document()->focusedNode(); while (node) { if (node->isElementNode()) { - const WebCore::AtomicString& value = static_cast<const Element*>(node)->getAttribute(spellcheckAttr); + const WTF::AtomicString& value = static_cast<const Element*>(node)->getAttribute(spellcheckAttr); if (equalIgnoringCase(value, "true")) return true; if (equalIgnoringCase(value, "false")) diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h index db249e7..13e6df5 100644 --- a/WebCore/editing/Editor.h +++ b/WebCore/editing/Editor.h @@ -49,6 +49,7 @@ class KillRing; class Pasteboard; class SimpleFontData; class Text; +class TextEvent; struct CompositionUnderline { CompositionUnderline() @@ -77,6 +78,7 @@ public: void handleKeyboardEvent(KeyboardEvent*); void handleInputMethodKeydown(KeyboardEvent*); + bool handleTextEvent(TextEvent*); bool canEdit() const; bool canEditRichly() const; @@ -293,6 +295,10 @@ public: PassRefPtr<Range> nextVisibleRange(Range*, const String&, bool forward, bool caseFlag, bool wrapFlag); void addToKillRing(Range*, bool prepend); + + void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle); + void pasteAsPlainText(const String&, bool smartReplace); + private: Frame* m_frame; OwnPtr<DeleteButtonController> m_deleteButtonController; @@ -326,6 +332,7 @@ private: PassRefPtr<Range> lastVisibleRange(const String&, bool caseFlag); void changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle); + Node* findEventTargetFromSelection() const; }; inline void Editor::setStartNewKillRingSequence(bool flag) diff --git a/WebCore/editing/HTMLInterchange.h b/WebCore/editing/HTMLInterchange.h index 3b68efb..4029ea2 100644 --- a/WebCore/editing/HTMLInterchange.h +++ b/WebCore/editing/HTMLInterchange.h @@ -26,9 +26,10 @@ #ifndef HTMLInterchange_h #define HTMLInterchange_h +#include <wtf/Forward.h> + namespace WebCore { -class String; class Text; #define AppleInterchangeNewline "Apple-interchange-newline" diff --git a/WebCore/editing/RemoveCSSPropertyCommand.cpp b/WebCore/editing/RemoveCSSPropertyCommand.cpp index 17870a7..8b37db8 100644 --- a/WebCore/editing/RemoveCSSPropertyCommand.cpp +++ b/WebCore/editing/RemoveCSSPropertyCommand.cpp @@ -31,25 +31,27 @@ namespace WebCore { -RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(Document* document, PassRefPtr<CSSMutableStyleDeclaration> style, CSSPropertyID property) +RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(Document* document, PassRefPtr<StyledElement> element, CSSPropertyID property) : SimpleEditCommand(document) - , m_style(style) + , m_element(element) , m_property(property) , m_important(false) { - ASSERT(m_style); + ASSERT(m_element); } void RemoveCSSPropertyCommand::doApply() { - m_oldValue = m_style->getPropertyValue(m_property); - m_important = m_style->getPropertyPriority(m_property); - m_style->removeProperty(m_property); + CSSMutableStyleDeclaration* style = m_element->inlineStyleDecl(); + m_oldValue = style->getPropertyValue(m_property); + m_important = style->getPropertyPriority(m_property); + style->removeProperty(m_property); } void RemoveCSSPropertyCommand::doUnapply() { - m_style->setProperty(m_property, m_oldValue, m_important); + CSSMutableStyleDeclaration* style = m_element->inlineStyleDecl(); + style->setProperty(m_property, m_oldValue, m_important); } } // namespace WebCore diff --git a/WebCore/editing/RemoveCSSPropertyCommand.h b/WebCore/editing/RemoveCSSPropertyCommand.h index 836f9d7..46e0498 100644 --- a/WebCore/editing/RemoveCSSPropertyCommand.h +++ b/WebCore/editing/RemoveCSSPropertyCommand.h @@ -29,23 +29,24 @@ #include "EditCommand.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" +#include "StyledElement.h" namespace WebCore { class RemoveCSSPropertyCommand : public SimpleEditCommand { public: - static PassRefPtr<RemoveCSSPropertyCommand> create(Document* document, PassRefPtr<CSSMutableStyleDeclaration> style, CSSPropertyID property) + static PassRefPtr<RemoveCSSPropertyCommand> create(Document* document, PassRefPtr<StyledElement> element, CSSPropertyID property) { - return adoptRef(new RemoveCSSPropertyCommand(document, style, property)); + return adoptRef(new RemoveCSSPropertyCommand(document, element, property)); } private: - RemoveCSSPropertyCommand(Document*, PassRefPtr<CSSMutableStyleDeclaration>, CSSPropertyID property); + RemoveCSSPropertyCommand(Document*, PassRefPtr<StyledElement>, CSSPropertyID property); virtual void doApply(); virtual void doUnapply(); - RefPtr<CSSMutableStyleDeclaration> m_style; + RefPtr<StyledElement> m_element; CSSPropertyID m_property; String m_oldValue; bool m_important; diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp index f3bd9df..9589bff 100644 --- a/WebCore/editing/TextIterator.cpp +++ b/WebCore/editing/TextIterator.cpp @@ -37,6 +37,7 @@ #include "RenderTableCell.h" #include "RenderTableRow.h" #include "RenderTextControl.h" +#include "RenderTextFragment.h" #include "VisiblePosition.h" #include "visible_units.h" @@ -253,10 +254,13 @@ TextIterator::TextIterator() , m_positionNode(0) , m_textCharacters(0) , m_textLength(0) + , m_remainingTextBox(0) + , m_firstLetterText(0) , m_lastCharacter(0) , m_emitsCharactersBetweenAllVisiblePositions(false) , m_entersTextControls(false) , m_emitsTextWithoutTranscoding(false) + , m_handledFirstLetter(false) { } @@ -268,10 +272,16 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior) , m_positionNode(0) , m_textCharacters(0) , m_textLength(0) + , m_remainingTextBox(0) + , m_firstLetterText(0) , m_emitsCharactersBetweenAllVisiblePositions(behavior & TextIteratorEmitsCharactersBetweenAllVisiblePositions) , m_entersTextControls(behavior & TextIteratorEntersTextControls) , m_emitsTextWithoutTranscoding(behavior & TextIteratorEmitsTextsWithoutTranscoding) + , m_handledFirstLetter(false) { + // FIXME: should support TextIteratorEndsAtEditingBoundary http://webkit.org/b/43609 + ASSERT(behavior != TextIteratorEndsAtEditingBoundary); + if (!r) return; @@ -344,6 +354,12 @@ void TextIterator::advance() return; } + if (!m_textBox && m_remainingTextBox) { + m_textBox = m_remainingTextBox; + m_remainingTextBox = 0; + m_firstLetterText = 0; + m_offset = 0; + } // handle remembered text box if (m_textBox) { handleTextBox(); @@ -417,6 +433,8 @@ void TextIterator::advance() pushFullyClippedState(m_fullyClippedStack, m_node); m_handledNode = false; m_handledChildren = false; + m_handledFirstLetter = false; + m_firstLetterText = 0; // how would this ever be? if (m_positionNode) @@ -435,8 +453,6 @@ bool TextIterator::handleTextNode() return false; RenderText* renderer = toRenderText(m_node->renderer()); - if (renderer->style()->visibility() != VISIBLE) - return false; m_lastTextNode = m_node; String str = renderer->text(); @@ -444,10 +460,22 @@ bool TextIterator::handleTextNode() // handle pre-formatted text if (!renderer->style()->collapseWhiteSpace()) { int runStart = m_offset; - if (m_lastTextNodeEndedWithCollapsedSpace) { + if (m_lastTextNodeEndedWithCollapsedSpace && hasVisibleTextNode(renderer)) { emitCharacter(' ', m_node, 0, runStart, runStart); return false; } + if (!m_handledFirstLetter && renderer->isTextFragment()) { + handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); + if (m_firstLetterText) { + String firstLetter = m_firstLetterText->text(); + emitText(m_node, m_firstLetterText, m_offset, firstLetter.length()); + m_firstLetterText = 0; + m_textBox = 0; + return false; + } + } + if (renderer->style()->visibility() != VISIBLE) + return false; int strLength = str.length(); int end = (m_node == m_endContainer) ? m_endOffset : INT_MAX; int runEnd = min(strLength, end); @@ -460,6 +488,15 @@ bool TextIterator::handleTextNode() } if (!renderer->firstTextBox() && str.length() > 0) { + if (!m_handledFirstLetter && renderer->isTextFragment()) { + handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); + if (m_firstLetterText) { + handleTextBox(); + return false; + } + } + if (renderer->style()->visibility() != VISIBLE) + return false; m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space return true; } @@ -475,13 +512,19 @@ bool TextIterator::handleTextNode() } m_textBox = renderer->containsReversedText() ? (m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]) : renderer->firstTextBox(); + if (!m_handledFirstLetter && renderer->isTextFragment() && !m_offset) + handleTextNodeFirstLetter(static_cast<RenderTextFragment*>(renderer)); handleTextBox(); return true; } void TextIterator::handleTextBox() { - RenderText* renderer = toRenderText(m_node->renderer()); + RenderText* renderer = m_firstLetterText ? m_firstLetterText : toRenderText(m_node->renderer()); + if (renderer->style()->visibility() != VISIBLE) { + m_textBox = 0; + return; + } String str = renderer->text(); int start = m_offset; int end = (m_node == m_endContainer) ? m_endOffset : INT_MAX; @@ -527,7 +570,7 @@ void TextIterator::handleTextBox() subrunEnd = runEnd; m_offset = subrunEnd; - emitText(m_node, runStart, subrunEnd); + emitText(m_node, renderer, runStart, subrunEnd); } // If we are doing a subrun that doesn't go to the end of the text box, @@ -549,6 +592,33 @@ void TextIterator::handleTextBox() if (renderer->containsReversedText()) ++m_sortedTextBoxesPosition; } + if (!m_textBox && m_remainingTextBox) { + m_textBox = m_remainingTextBox; + m_remainingTextBox = 0; + m_firstLetterText = 0; + m_offset = 0; + handleTextBox(); + } +} + +void TextIterator::handleTextNodeFirstLetter(RenderTextFragment* renderer) +{ + if (renderer->firstLetter()) { + RenderObject* r = renderer->firstLetter(); + if (r->style()->visibility() != VISIBLE) + return; + for (RenderObject *currChild = r->firstChild(); currChild; currChild->nextSibling()) { + if (currChild->isText()) { + RenderText* firstLetter = toRenderText(currChild); + m_handledFirstLetter = true; + m_remainingTextBox = m_textBox; + m_textBox = firstLetter->firstTextBox(); + m_firstLetterText = firstLetter; + return; + } + } + } + m_handledFirstLetter = true; } bool TextIterator::handleReplacedElement() @@ -597,6 +667,18 @@ bool TextIterator::handleReplacedElement() return true; } +bool TextIterator::hasVisibleTextNode(RenderText* renderer) +{ + if (renderer->style()->visibility() == VISIBLE) + return true; + if (renderer->isTextFragment()) { + RenderTextFragment* fragment = static_cast<RenderTextFragment*>(renderer); + if (fragment->firstLetter() && fragment->firstLetter()->style()->visibility() == VISIBLE) + return true; + } + return false; +} + static bool shouldEmitTabBeforeNode(Node* node) { RenderObject* r = node->renderer(); @@ -888,9 +970,9 @@ void TextIterator::emitCharacter(UChar c, Node* textNode, Node* offsetBaseNode, m_lastCharacter = c; } -void TextIterator::emitText(Node* textNode, int textStartOffset, int textEndOffset) +void TextIterator::emitText(Node* textNode, RenderObject* renderObject, int textStartOffset, int textEndOffset) { - RenderText* renderer = toRenderText(m_node->renderer()); + RenderText* renderer = toRenderText(renderObject); m_text = m_emitsTextWithoutTranscoding ? renderer->textWithoutTranscoding() : renderer->text(); ASSERT(m_text.characters()); @@ -906,6 +988,11 @@ void TextIterator::emitText(Node* textNode, int textStartOffset, int textEndOffs m_hasEmitted = true; } +void TextIterator::emitText(Node* textNode, int textStartOffset, int textEndOffset) +{ + emitText(textNode, m_node->renderer(), textStartOffset, textEndOffset); +} + PassRefPtr<Range> TextIterator::range() const { // use the current run information, if we have it @@ -944,13 +1031,19 @@ Node* TextIterator::node() const // -------- SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() - : m_positionNode(0) + : m_behavior(TextIteratorDefaultBehavior) + , m_node(0) + , m_positionNode(0) { } -SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r) - : m_positionNode(0) +SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r, TextIteratorBehavior behavior) + : m_behavior(behavior) + , m_node(0) + , m_positionNode(0) { + ASSERT(m_behavior == TextIteratorDefaultBehavior || m_behavior == TextIteratorEndsAtEditingBoundary); + if (!r) return; @@ -974,7 +1067,7 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r) } } - m_node = endNode; + setCurrentNode(endNode); setUpFullyClippedStack(m_fullyClippedStack, m_node); m_offset = endOffset; m_handledNode = false; @@ -1038,12 +1131,9 @@ void SimplifiedBackwardsTextIterator::advance() } } // Exit all other containers. - next = m_node->previousSibling(); - while (!next) { - Node* parentNode = parentCrossingShadowBoundaries(m_node); - if (!parentNode) + while (!m_node->previousSibling()) { + if (!setCurrentNode(parentCrossingShadowBoundaries(m_node))) break; - m_node = parentNode; m_fullyClippedStack.pop(); exitNode(); if (m_positionNode) { @@ -1051,14 +1141,17 @@ void SimplifiedBackwardsTextIterator::advance() m_handledChildren = true; return; } - next = m_node->previousSibling(); } + + next = m_node->previousSibling(); m_fullyClippedStack.pop(); } - m_node = next; - if (m_node) + if (m_node && setCurrentNode(next)) pushFullyClippedState(m_fullyClippedStack, m_node); + else + clearCurrentNode(); + // For the purpose of word boundary detection, // we should iterate all visible text and trailing (collapsed) whitespaces. m_offset = m_node ? maxOffsetIncludingCollapsedSpaces(m_node) : 0; @@ -1137,6 +1230,26 @@ void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node* node, int sta m_lastCharacter = c; } +bool SimplifiedBackwardsTextIterator::crossesEditingBoundary(Node* node) const +{ + return m_node && m_node->isContentEditable() != node->isContentEditable(); +} + +bool SimplifiedBackwardsTextIterator::setCurrentNode(Node* node) +{ + if (!node) + return false; + if (m_behavior == TextIteratorEndsAtEditingBoundary && crossesEditingBoundary(node)) + return false; + m_node = node; + return true; +} + +void SimplifiedBackwardsTextIterator::clearCurrentNode() +{ + m_node = 0; +} + PassRefPtr<Range> SimplifiedBackwardsTextIterator::range() const { if (m_positionNode) @@ -1262,11 +1375,11 @@ BackwardsCharacterIterator::BackwardsCharacterIterator() { } -BackwardsCharacterIterator::BackwardsCharacterIterator(const Range* range) +BackwardsCharacterIterator::BackwardsCharacterIterator(const Range* range, TextIteratorBehavior behavior) : m_offset(0) , m_runOffset(0) , m_atBreak(true) - , m_textIterator(range) + , m_textIterator(range, behavior) { while (!atEnd() && !m_textIterator.length()) m_textIterator.advance(); diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h index ba381b9..805e060 100644 --- a/WebCore/editing/TextIterator.h +++ b/WebCore/editing/TextIterator.h @@ -32,6 +32,9 @@ namespace WebCore { +class RenderText; +class RenderTextFragment; + // FIXME: Can't really answer this question correctly without knowing the white-space mode. // FIXME: Move this somewhere else in the editing directory. It doesn't belong here. inline bool isCollapsibleWhitespace(UChar c) @@ -73,6 +76,7 @@ enum TextIteratorBehavior { TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0, TextIteratorEntersTextControls = 1 << 1, TextIteratorEmitsTextsWithoutTranscoding = 1 << 2, + TextIteratorEndsAtEditingBoundary = 1 << 3 }; class TextIterator { @@ -102,7 +106,10 @@ private: bool handleReplacedElement(); bool handleNonTextNode(); void handleTextBox(); + void handleTextNodeFirstLetter(RenderTextFragment*); + bool hasVisibleTextNode(RenderText*); void emitCharacter(UChar, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset); + void emitText(Node* textNode, RenderObject* renderObject, int textStartOffset, int textEndOffset); void emitText(Node* textNode, int textStartOffset, int textEndOffset); // Current position, not necessarily of the text being returned, but position @@ -134,6 +141,11 @@ private: // are false and 0, we go back to normal iterating. bool m_needsAnotherNewline; InlineTextBox* m_textBox; + // Used when iteration over :first-letter text to save pointer to + // remaining text box. + InlineTextBox* m_remainingTextBox; + // Used to point to RenderText object for :first-letter. + RenderText *m_firstLetterText; // Used to do the whitespace collapsing logic. Node* m_lastTextNode; @@ -159,6 +171,8 @@ private: // Used when we want texts for copying, pasting, and transposing. bool m_emitsTextWithoutTranscoding; + // Used when deciding text fragment created by :first-letter should be looked into. + bool m_handledFirstLetter; }; // Iterates through the DOM range, returning all the text, and 0-length boundaries @@ -167,7 +181,7 @@ private: class SimplifiedBackwardsTextIterator { public: SimplifiedBackwardsTextIterator(); - explicit SimplifiedBackwardsTextIterator(const Range*); + explicit SimplifiedBackwardsTextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); bool atEnd() const { return !m_positionNode; } void advance(); @@ -183,7 +197,11 @@ private: bool handleReplacedElement(); bool handleNonTextNode(); void emitCharacter(UChar, Node*, int startOffset, int endOffset); - + bool crossesEditingBoundary(Node*) const; + bool setCurrentNode(Node*); + void clearCurrentNode(); + + TextIteratorBehavior m_behavior; // Current position, not necessarily of the text being returned, but position // as we walk through the DOM tree. Node* m_node; @@ -247,7 +265,7 @@ private: class BackwardsCharacterIterator { public: BackwardsCharacterIterator(); - explicit BackwardsCharacterIterator(const Range*); + explicit BackwardsCharacterIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); void advance(int); @@ -256,6 +274,7 @@ public: PassRefPtr<Range> range() const; private: + TextIteratorBehavior m_behavior; int m_offset; int m_runOffset; bool m_atBreak; diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h index e60ada9..8612440 100644 --- a/WebCore/editing/htmlediting.h +++ b/WebCore/editing/htmlediting.h @@ -39,7 +39,6 @@ class HTMLElement; class Node; class Position; class Range; -class String; class VisiblePosition; class VisibleSelection; diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp index 22e700a..26989c3 100644 --- a/WebCore/editing/markup.cpp +++ b/WebCore/editing/markup.cpp @@ -778,7 +778,7 @@ public: DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\"")); DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>")); Vector<UChar> openTag; - WebCore::append(openTag, isBlock ? divStyle : styleSpanOpen); + WTF::append(openTag, isBlock ? divStyle : styleSpanOpen); appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument()); openTag.append('\"'); openTag.append('>'); @@ -805,10 +805,10 @@ public: result.reserveInitialCapacity(length); for (size_t i = preCount; i > 0; --i) - WebCore::append(result, preMarkups[i - 1]); + WTF::append(result, preMarkups[i - 1]); for (size_t i = 0; i < postCount; ++i) - WebCore::append(result, postMarkups[i]); + WTF::append(result, postMarkups[i]); return String::adopt(result); } @@ -818,6 +818,84 @@ private: Vector<String> postMarkups; }; +static Node* serializeNodes(MarkupAccumulatorWrapper& accumulator, Node* startNode, Node* pastEnd, Vector<Node*>* nodes, const Range* range, EAnnotateForInterchange annotate, EAbsoluteURLs absoluteURLs) +{ + Vector<Node*> ancestorsToClose; + Node* next; + Node* lastClosed = 0; + for (Node* n = startNode; n != pastEnd; n = next) { + // According to <rdar://problem/5730668>, it is possible for n to blow + // past pastEnd and become null here. This shouldn't be possible. + // This null check will prevent crashes (but create too much markup) + // and the ASSERT will hopefully lead us to understanding the problem. + ASSERT(n); + if (!n) + break; + + next = n->traverseNextNode(); + bool openedTag = false; + + if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) + // Don't write out empty block containers that aren't fully selected. + continue; + + if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) { + next = n->traverseNextSibling(); + // Don't skip over pastEnd. + if (pastEnd && pastEnd->isDescendantOf(n)) + next = pastEnd; + } else { + // Add the node to the markup if we're not skipping the descendants + accumulator.insertOpenTag(n, range, annotate, absoluteURLs); + if (nodes) + nodes->append(n); + + // If node has no children, close the tag now. + if (!n->childNodeCount()) { + accumulator.insertEndTag(n); + lastClosed = n; + } else { + openedTag = true; + ancestorsToClose.append(n); + } + } + + // If we didn't insert open tag and there's no more siblings or we're at the end of the traversal, take care of ancestors. + // FIXME: What happens if we just inserted open tag and reached the end? + if (!openedTag && (!n->nextSibling() || next == pastEnd)) { + // Close up the ancestors. + while (!ancestorsToClose.isEmpty()) { + Node* ancestor = ancestorsToClose.last(); + if (next != pastEnd && next->isDescendantOf(ancestor)) + break; + // Not at the end of the range, close ancestors up to sibling of next node. + accumulator.insertEndTag(ancestor); + lastClosed = ancestor; + ancestorsToClose.removeLast(); + } + + // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. + Node* nextParent = next ? next->parentNode() : 0; + if (next != pastEnd && n != nextParent) { + Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; + for (Node *parent = lastAncestorClosedOrSelf->parent(); parent && parent != nextParent; parent = parent->parentNode()) { + // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: + if (!parent->renderer()) + continue; + // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: + ASSERT(startNode->isDescendantOf(parent)); + accumulator.wrapWithNode(parent, range, annotate, absoluteURLs); + if (nodes) + nodes->append(parent); + lastClosed = parent; + } + } + } + } + + return lastClosed; +} + // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString() String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines, EAbsoluteURLs absoluteURLs) @@ -856,9 +934,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc MarkupAccumulatorWrapper accumulator; Node* pastEnd = updatedRange->pastLastNode(); - Node* lastClosed = 0; - Vector<Node*> ancestorsToClose; - + Node* startNode = updatedRange->firstNode(); VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY); VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY); @@ -879,84 +955,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc } } - Node* next; - for (Node* n = startNode; n != pastEnd; n = next) { - // According to <rdar://problem/5730668>, it is possible for n to blow - // past pastEnd and become null here. This shouldn't be possible. - // This null check will prevent crashes (but create too much markup) - // and the ASSERT will hopefully lead us to understanding the problem. - ASSERT(n); - if (!n) - break; - - next = n->traverseNextNode(); - bool skipDescendants = false; - bool addMarkupForNode = true; - - if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) { - skipDescendants = true; - addMarkupForNode = false; - next = n->traverseNextSibling(); - // Don't skip over pastEnd. - if (pastEnd && pastEnd->isDescendantOf(n)) - next = pastEnd; - } - - if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) - // Don't write out empty block containers that aren't fully selected. - continue; - - // Add the node to the markup. - if (addMarkupForNode) { - - accumulator.insertOpenTag(n, updatedRange.get(), annotate, absoluteURLs); - if (nodes) - nodes->append(n); - } - - if (n->firstChild() == 0 || skipDescendants) { - // Node has no children, or we are skipping it's descendants, add its close tag now. - if (addMarkupForNode) { - accumulator.insertEndTag(n); - lastClosed = n; - } - - // Check if the node is the last leaf of a tree. - if (!n->nextSibling() || next == pastEnd) { - if (!ancestorsToClose.isEmpty()) { - // Close up the ancestors. - do { - Node *ancestor = ancestorsToClose.last(); - if (next != pastEnd && next->isDescendantOf(ancestor)) - break; - // Not at the end of the range, close ancestors up to sibling of next node. - accumulator.insertEndTag(ancestor); - lastClosed = ancestor; - ancestorsToClose.removeLast(); - } while (!ancestorsToClose.isEmpty()); - } - - // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. - Node* nextParent = next ? next->parentNode() : 0; - if (next != pastEnd && n != nextParent) { - Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; - for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) { - // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: - if (!parent->renderer()) - continue; - // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: - ASSERT(startNode->isDescendantOf(parent)); - accumulator.wrapWithNode(parent, updatedRange.get(), annotate, absoluteURLs); - if (nodes) - nodes->append(parent); - lastClosed = parent; - } - } - } - } else if (addMarkupForNode && !skipDescendants) - // We added markup for this node, and we're descending into it. Set it to close eventually. - ancestorsToClose.append(n); - } + Node* lastClosed = serializeNodes(accumulator, startNode, pastEnd, nodes, range, annotate, absoluteURLs); // Include ancestors that aren't completely inside the range but are required to retain // the structure and appearance of the copied markup. diff --git a/WebCore/editing/markup.h b/WebCore/editing/markup.h index 5428097..dbf8b80 100644 --- a/WebCore/editing/markup.h +++ b/WebCore/editing/markup.h @@ -38,7 +38,6 @@ namespace WebCore { class KURL; class Node; class Range; - class String; enum EChildrenOnly { IncludeNode, ChildrenOnly }; enum EAbsoluteURLs { DoNotResolveURLs, AbsoluteURLs }; diff --git a/WebCore/editing/visible_units.cpp b/WebCore/editing/visible_units.cpp index dd48406..f84fec0 100644 --- a/WebCore/editing/visible_units.cpp +++ b/WebCore/editing/visible_units.cpp @@ -75,20 +75,11 @@ typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsign static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); - Node *n = pos.node(); - if (!n) - return VisiblePosition(); - Document *d = n->document(); - Node *de = d->documentElement(); - if (!de) - return VisiblePosition(); - Node *boundary = n->enclosingBlockFlowElement(); + Node* boundary = pos.parentEditingBoundary(); if (!boundary) return VisiblePosition(); - bool isContentEditable = boundary->isContentEditable(); - while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) - boundary = boundary->parentNode(); + Document* d = boundary->document(); Position start = rangeCompliantEquivalent(Position(boundary, 0)); Position end = rangeCompliantEquivalent(pos); RefPtr<Range> searchRange = Range::create(d); @@ -121,7 +112,7 @@ static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch if (ec) return VisiblePosition(); - SimplifiedBackwardsTextIterator it(searchRange.get()); + SimplifiedBackwardsTextIterator it(searchRange.get(), TextIteratorEndsAtEditingBoundary); unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; bool needMoreContext = false; @@ -156,7 +147,7 @@ static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch pos = Position(node, next); else { // Use the character iterator to translate the next value into a DOM position. - BackwardsCharacterIterator charIt(searchRange.get()); + BackwardsCharacterIterator charIt(searchRange.get(), TextIteratorEndsAtEditingBoundary); charIt.advance(string.size() - suffixLength - next); pos = charIt.range()->endPosition(); } @@ -168,20 +159,11 @@ static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); - Node *n = pos.node(); - if (!n) - return VisiblePosition(); - Document *d = n->document(); - Node *de = d->documentElement(); - if (!de) - return VisiblePosition(); - Node *boundary = n->enclosingBlockFlowElement(); + Node* boundary = pos.parentEditingBoundary(); if (!boundary) return VisiblePosition(); - bool isContentEditable = boundary->isContentEditable(); - while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) - boundary = boundary->parentNode(); + Document* d = boundary->document(); RefPtr<Range> searchRange(d->createRange()); Position start(rangeCompliantEquivalent(pos)); @@ -1019,7 +1001,7 @@ VisiblePosition startOfDocument(const VisiblePosition &c) VisiblePosition endOfDocument(const Node* node) { - if (!node || !node->document()) + if (!node || !node->document() || !node->document()->documentElement()) return VisiblePosition(); Element* doc = node->document()->documentElement(); |