diff options
author | Steve Block <steveblock@google.com> | 2011-05-18 13:36:51 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-24 15:38:28 +0100 |
commit | 2fc2651226baac27029e38c9d6ef883fa32084db (patch) | |
tree | e396d4bf89dcce6ed02071be66212495b1df1dec /Source/WebCore/editing | |
parent | b3725cedeb43722b3b175aaeff70552e562d2c94 (diff) | |
download | external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.zip external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.tar.gz external_webkit-2fc2651226baac27029e38c9d6ef883fa32084db.tar.bz2 |
Merge WebKit at r78450: Initial merge by git.
Change-Id: I6d3e5f1f868ec266a0aafdef66182ddc3f265dc1
Diffstat (limited to 'Source/WebCore/editing')
30 files changed, 496 insertions, 340 deletions
diff --git a/Source/WebCore/editing/ApplyStyleCommand.cpp b/Source/WebCore/editing/ApplyStyleCommand.cpp index 39350b9..ccade74 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.cpp +++ b/Source/WebCore/editing/ApplyStyleCommand.cpp @@ -113,7 +113,7 @@ public: private: void init(PassRefPtr<CSSStyleDeclaration>, const Position&); void reconcileTextDecorationProperties(CSSMutableStyleDeclaration*); - void extractTextStyles(Document*, CSSMutableStyleDeclaration*, bool shouldUseFixedFontDefautlSize); + void extractTextStyles(Document*, CSSMutableStyleDeclaration*, bool shouldUseFixedFontDefaultSize); String m_cssStyle; bool m_applyBold; @@ -205,7 +205,31 @@ static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const C } } -void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefautlSize) +static bool isCSSValueLength(CSSPrimitiveValue* value) +{ + return value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC; +} + +int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode) +{ + if (isCSSValueLength(value)) { + int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX); + int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize); + // Use legacy font size only if pixel value matches exactly to that of legacy font size. + int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall; + if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize) + return legacyFontSize; + + return 0; + } + + if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge) + return value->getIdent() - CSSValueXSmall + 1; + + return 0; +} + +void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefaultSize) { ASSERT(style); @@ -260,20 +284,10 @@ void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclarati if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) { if (!fontSize->isPrimitiveValue()) style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size. - else { - CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(fontSize.get()); - if (value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC) { - int pixelFontSize = value->getFloatValue(CSSPrimitiveValue::CSS_PX); - int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefautlSize); - // Use legacy font size only if pixel value matches exactly to that of legacy font size. - if (CSSStyleSelector::fontSizeForKeyword(document, legacyFontSize - 1 + CSSValueXSmall, shouldUseFixedFontDefautlSize) == pixelFontSize) { - m_applyFontSize = String::number(legacyFontSize); - style->removeProperty(CSSPropertyFontSize); - } - } else if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge) { - m_applyFontSize = String::number(value->getIdent() - CSSValueXSmall + 1); - style->removeProperty(CSSPropertyFontSize); - } + else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()), + shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) { + m_applyFontSize = String::number(legacyFontSize); + style->removeProperty(CSSPropertyFontSize); } } } @@ -522,18 +536,17 @@ void ApplyStyleCommand::doApply() // Apply the block-centric properties of the style. RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties(); if (!blockStyle->isEmpty()) - applyBlockStyle(blockStyle->style()); + applyBlockStyle(blockStyle.get()); // Apply any remaining styles to the inline elements. if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) { - RefPtr<CSSMutableStyleDeclaration> style = m_style->style() ? m_style->style() : CSSMutableStyleDeclaration::create(); applyRelativeFontStyleChange(m_style.get()); - applyInlineStyle(style.get()); + applyInlineStyle(m_style.get()); } break; } case ForceBlockProperties: // Force all properties to be applied as block styles. - applyBlockStyle(m_style->style()); + applyBlockStyle(m_style.get()); break; } } @@ -543,7 +556,7 @@ EditAction ApplyStyleCommand::editingAction() const return m_editingAction; } -void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) +void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) { // update document layout once before removing styles // so that we avoid the expense of updating before each and every call @@ -578,7 +591,7 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { - StyleChange styleChange(style, paragraphStart.deepEquivalent()); + StyleChange styleChange(style->style(), paragraphStart.deepEquivalent()); if (styleChange.cssStyle().length() || m_removeOnly) { RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node()); if (!m_removeOnly) { @@ -588,9 +601,9 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) } ASSERT(block->isHTMLElement()); if (block->isHTMLElement()) { - removeCSSStyle(style, static_cast<HTMLElement*>(block.get())); + removeCSSStyle(style->style(), toHTMLElement(block.get())); if (!m_removeOnly) - addBlockStyle(styleChange, static_cast<HTMLElement*>(block.get())); + addBlockStyle(styleChange, toHTMLElement(block.get())); } if (nextParagraphStart.isOrphan()) @@ -677,7 +690,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) // Only work on fully selected nodes. if (!nodeFullySelected(node, start, end)) continue; - element = static_cast<HTMLElement*>(node); + element = toHTMLElement(node); } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { // Last styled node was not parent node of this text node, but we wish to style this // text node. To make this possible, add a style span to surround this text node. @@ -741,7 +754,7 @@ void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor) } } -HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, int allowedDirection) +HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection 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. @@ -766,13 +779,16 @@ HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b HTMLElement* unsplitAncestor = 0; - if (allowedDirection && highestAncestorUnicodeBidi != CSSValueBidiOverride - && getIdentifierValue(computedStyle(highestAncestorWithUnicodeBidi).get(), CSSPropertyDirection) == allowedDirection - && highestAncestorWithUnicodeBidi->isHTMLElement()) { + WritingDirection highestAncestorDirection; + if (allowedDirection != NaturalWritingDirection + && highestAncestorUnicodeBidi != CSSValueBidiOverride + && highestAncestorWithUnicodeBidi->isHTMLElement() + && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection) + && highestAncestorDirection == allowedDirection) { if (!nextHighestAncestorWithUnicodeBidi) - return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); + return toHTMLElement(highestAncestorWithUnicodeBidi); - unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); + unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi); highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; } @@ -836,7 +852,7 @@ static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode) return 0; } -void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) +void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) { Node* startDummySpanAncestor = 0; Node* endDummySpanAncestor = 0; @@ -858,7 +874,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) // split the start node and containing element if the selection starts inside of it bool splitStart = isValidCaretPositionInTextNode(start); if (splitStart) { - if (shouldSplitTextElement(start.node()->parentElement(), style)) + if (shouldSplitTextElement(start.node()->parentElement(), style->style())) splitTextElementAtStart(start, end); else splitTextAtStart(start, end); @@ -870,7 +886,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) // split the end node and containing element if the selection ends inside of it bool splitEnd = isValidCaretPositionInTextNode(end); if (splitEnd) { - if (shouldSplitTextElement(end.node()->parentElement(), style)) + if (shouldSplitTextElement(end.node()->parentElement(), style->style())) splitTextElementAtEnd(start, end); else splitTextAtEnd(start, end); @@ -885,15 +901,16 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) // 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; - RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding; + int unicodeBidi = getIdentifierValue(style->style(), CSSPropertyUnicodeBidi); + RefPtr<EditingStyle> styleWithoutEmbedding; + RefPtr<EditingStyle> embeddingStyle; if (unicodeBidi) { + WritingDirection textDirection = NaturalWritingDirection; + style->textDirection(textDirection); + // Leave alone an ancestor that provides the desired single level embedding, if there is one. - if (unicodeBidi == CSSValueEmbed) - direction = getIdentifierValue(style, CSSPropertyDirection); - HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, direction); - HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, direction); + HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, textDirection); + HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, textDirection); removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor); removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor); @@ -907,18 +924,15 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream(); 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); + embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); + + if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0) + removeInlineStyle(embeddingStyle->style(), embeddingRemoveStart, embeddingRemoveEnd); } } - removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end); + removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding->style() : style->style(), removeStart, end); start = startPosition(); end = endPosition(); if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan()) @@ -942,7 +956,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) // to check a computed style updateLayout(); - RefPtr<CSSMutableStyleDeclaration> styleToApply = style; + RefPtr<CSSMutableStyleDeclaration> styleToApply = style->isEmpty() ? CSSMutableStyleDeclaration::create() : style->style(); if (unicodeBidi) { // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them. Node* embeddingStartNode = highestEmbeddingAncestor(start.node(), enclosingBlock(start.node())); @@ -953,18 +967,13 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end; ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()); - RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create(); - embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed); - embeddingStyle->setProperty(CSSPropertyDirection, direction); - fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd); - - if (styleWithoutEmbedding) - styleToApply = styleWithoutEmbedding; - else { - styleToApply = style->copy(); - styleToApply->removeProperty(CSSPropertyUnicodeBidi); - styleToApply->removeProperty(CSSPropertyDirection); + if (!embeddingStyle) { + styleWithoutEmbedding = style->copy(); + embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); } + fixRangeAndApplyInlineStyle(embeddingStyle->style(), embeddingApplyStart, embeddingApplyEnd); + + styleToApply = styleWithoutEmbedding->style(); } } @@ -1040,7 +1049,7 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* if (pastEndNode && pastEndNode->isDescendantOf(node)) break; // Add to this element's inline style and skip over its contents. - HTMLElement* element = static_cast<HTMLElement*>(node); + HTMLElement* element = toHTMLElement(node); RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy(); inlineStyle->merge(style); setNodeAttribute(element, styleAttr, inlineStyle->cssText()); @@ -1110,7 +1119,7 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec RefPtr<Node> previousSibling = node->previousSibling(); RefPtr<Node> nextSibling = node->nextSibling(); RefPtr<ContainerNode> parent = node->parentNode(); - removeInlineStyleFromElement(style, static_cast<HTMLElement*>(node.get()), RemoveAlways); + removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways); if (!node->inDocument()) { // FIXME: We might need to update the start and the end of current selection here but need a test. if (runStart == node) @@ -1125,7 +1134,6 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, CSSMutableStyleDeclaration* extractedStyle) { - ASSERT(style); ASSERT(element); if (!element->parentNode() || !element->parentNode()->isContentEditable()) @@ -1141,6 +1149,9 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* return true; } + if (!style) + return false; + bool removed = false; if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) removed = true; @@ -1214,6 +1225,7 @@ static const HTMLEquivalent HTMLEquivalents[] = { 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(style); ASSERT(!extractedStyle || mode != RemoveNone); bool removed = false; for (size_t i = 0; i < WTF_ARRAY_LENGTH(HTMLEquivalents); ++i) { @@ -1336,8 +1348,8 @@ HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(CSSMut Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node)); for (Node *n = node; n; n = n->parentNode()) { - if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, static_cast<HTMLElement*>(n))) - result = static_cast<HTMLElement*>(n); + if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n))) + result = toHTMLElement(n); // Should stop at the editable root (cannot cross editing boundary) and // also stop at the unsplittable element to be consistent with other UAs if (n == unsplittableElement) @@ -1356,7 +1368,7 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, CSSMutableStyleDe RefPtr<CSSMutableStyleDeclaration> newInlineStyle = style; if (node->isHTMLElement()) { - HTMLElement* element = static_cast<HTMLElement*>(node); + HTMLElement* element = toHTMLElement(node); CSSMutableStyleDeclaration* existingInlineStyle = element->inlineStyleDecl(); // Avoid overriding existing styles of node @@ -1392,7 +1404,7 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, CSSMutableStyleDe // 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()); + setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->cssText()); return; } @@ -1428,7 +1440,7 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration elementsToPushDown.append(styledElement); } RefPtr<CSSMutableStyleDeclaration> styleToPushDown = CSSMutableStyleDeclaration::create(); - removeInlineStyleFromElement(style, static_cast<HTMLElement*>(current), RemoveIfNeeded, styleToPushDown.get()); + removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get()); // 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. @@ -1469,8 +1481,8 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> ASSERT(start.node()->inDocument()); ASSERT(end.node()->inDocument()); ASSERT(comparePositions(start, end) <= 0); - - RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); + + RefPtr<CSSValue> textDecorationSpecialProperty = style ? style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) : 0; if (textDecorationSpecialProperty) { style = style->copy(); style->setProperty(CSSPropertyTextDecoration, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect)); @@ -1500,7 +1512,7 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> while (node) { RefPtr<Node> next = node->traverseNextNode(); if (node->isHTMLElement() && nodeFullySelected(node, start, end)) { - RefPtr<HTMLElement> elem = static_cast<HTMLElement*>(node); + RefPtr<HTMLElement> elem = toHTMLElement(node); RefPtr<Node> prev = elem->traversePreviousNodePostOrder(); RefPtr<Node> next = elem->traverseNextNode(); RefPtr<CSSMutableStyleDeclaration> styleToPushDown; @@ -1624,7 +1636,7 @@ bool ApplyStyleCommand::shouldSplitTextElement(Element* element, CSSMutableStyle if (!element || !element->isHTMLElement()) return false; - return shouldRemoveInlineStyleFromElement(style, static_cast<HTMLElement*>(element)); + return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element)); } bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position) @@ -1827,10 +1839,10 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style HTMLElement* styleContainer = 0; for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) { if (container->isHTMLElement() && container->hasTagName(fontTag)) - fontContainer = static_cast<HTMLElement*>(container); + fontContainer = toHTMLElement(container); bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag); if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount()))) - styleContainer = static_cast<HTMLElement*>(container); + styleContainer = toHTMLElement(container); if (!container->firstChild()) break; startNode = container->firstChild(); @@ -1860,7 +1872,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style if (styleChange.cssStyle().length()) { if (styleContainer) { - CSSMutableStyleDeclaration* existingStyle = static_cast<HTMLElement*>(styleContainer)->inlineStyleDecl(); + CSSMutableStyleDeclaration* existingStyle = toHTMLElement(styleContainer)->inlineStyleDecl(); if (existingStyle) setNodeAttribute(styleContainer, styleAttr, existingStyle->cssText() + styleChange.cssStyle()); else diff --git a/Source/WebCore/editing/ApplyStyleCommand.h b/Source/WebCore/editing/ApplyStyleCommand.h index 05af85c..1b2c2ef 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.h +++ b/Source/WebCore/editing/ApplyStyleCommand.h @@ -28,6 +28,7 @@ #include "CompositeEditCommand.h" #include "HTMLElement.h" +#include "WritingDirection.h" namespace WebCore { @@ -90,9 +91,9 @@ private: bool nodeFullyUnselected(Node*, const Position& start, const Position& end) const; // style-application helpers - void applyBlockStyle(CSSMutableStyleDeclaration*); + void applyBlockStyle(EditingStyle*); void applyRelativeFontStyleChange(EditingStyle*); - void applyInlineStyle(CSSMutableStyleDeclaration*); + void applyInlineStyle(EditingStyle*); void fixRangeAndApplyInlineStyle(CSSMutableStyleDeclaration*, const Position& start, const Position& end); void applyInlineStyleToNodeRange(CSSMutableStyleDeclaration*, Node* startNode, Node* pastEndNode); void addBlockStyle(const StyleChange&, HTMLElement*); @@ -111,7 +112,7 @@ private: float computedFontSize(Node*); void joinChildTextNodes(Node*, const Position& start, const Position& end); - HTMLElement* splitAncestorsWithUnicodeBidi(Node*, bool before, int allowedDirection); + HTMLElement* splitAncestorsWithUnicodeBidi(Node*, bool before, WritingDirection allowedDirection); void removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor); void updateStartEnd(const Position& newStart, const Position& newEnd); @@ -129,6 +130,8 @@ private: IsInlineElementToRemoveFunction m_isInlineElementToRemoveFunction; }; +enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch }; +int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode); bool isStyleSpan(const Node*); PassRefPtr<HTMLElement> createStyleSpanElement(Document*); RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle); diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp index 552ed79..ac3d761 100644 --- a/Source/WebCore/editing/CompositeEditCommand.cpp +++ b/Source/WebCore/editing/CompositeEditCommand.cpp @@ -28,7 +28,6 @@ #include "AppendNodeCommand.h" #include "ApplyStyleCommand.h" -#include "CharacterNames.h" #include "DeleteFromTextNodeCommand.h" #include "DeleteSelectionCommand.h" #include "Document.h" @@ -63,6 +62,7 @@ #include "htmlediting.h" #include "markup.h" #include "visible_units.h" +#include <wtf/unicode/CharacterNames.h> using namespace std; @@ -390,57 +390,84 @@ void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const Q applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value)); } -static inline bool isWhitespace(UChar c) +static inline bool containsOnlyWhitespace(const String& text) { - return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t'; + for (unsigned i = 0; i < text.length(); ++i) { + if (!isWhitespace(text.characters()[i])) + return false; + } + + return true; } -// FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc). -void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) +bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const +{ + return containsOnlyWhitespace(text); +} + +bool CompositeEditCommand::canRebalance(const Position& position) const { Node* node = position.containerNode(); if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode()) - return; - Text* textNode = static_cast<Text*>(node); + return false; + Text* textNode = static_cast<Text*>(node); if (textNode->length() == 0) - return; + return false; + RenderObject* renderer = textNode->renderer(); if (renderer && !renderer->style()->collapseWhiteSpace()) - return; + return false; - String text = textNode->data(); - ASSERT(!text.isEmpty()); + return true; +} + +// FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc). +void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) +{ + Node* node = position.containerNode(); + if (!canRebalance(position)) + return; + // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. int offset = position.deprecatedEditingOffset(); - // If neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. + String text = static_cast<Text*>(node)->data(); if (!isWhitespace(text[offset])) { offset--; if (offset < 0 || !isWhitespace(text[offset])) return; } - + + rebalanceWhitespaceOnTextSubstring(static_cast<Text*>(node), position.offsetInContainerNode(), position.offsetInContainerNode()); +} + +void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(RefPtr<Text> textNode, int startOffset, int endOffset) +{ + String text = textNode->data(); + ASSERT(!text.isEmpty()); + // Set upstream and downstream to define the extent of the whitespace surrounding text[offset]. - int upstream = offset; + int upstream = startOffset; while (upstream > 0 && isWhitespace(text[upstream - 1])) upstream--; - int downstream = offset; - while ((unsigned)downstream + 1 < text.length() && isWhitespace(text[downstream + 1])) + int downstream = endOffset; + while ((unsigned)downstream < text.length() && isWhitespace(text[downstream])) downstream++; - int length = downstream - upstream + 1; - ASSERT(length > 0); - - VisiblePosition visibleUpstreamPos(Position(position.containerNode(), upstream, Position::PositionIsOffsetInAnchor)); - VisiblePosition visibleDownstreamPos(Position(position.containerNode(), downstream + 1, Position::PositionIsOffsetInAnchor)); + int length = downstream - upstream; + if (!length) + return; + + VisiblePosition visibleUpstreamPos(Position(textNode, upstream, Position::PositionIsOffsetInAnchor)); + VisiblePosition visibleDownstreamPos(Position(textNode, downstream, Position::PositionIsOffsetInAnchor)); String string = text.substring(upstream, length); String rebalancedString = stringWithRebalancedWhitespace(string, // FIXME: Because of the problem mentioned at the top of this function, we must also use nbsps at the start/end of the string because // this function doesn't get all surrounding whitespace, just the whitespace in the current text node. isStartOfParagraph(visibleUpstreamPos) || upstream == 0, - isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length() - 1); + isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length()); if (string != rebalancedString) replaceTextInNode(textNode, upstream, length, rebalancedString); diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h index 6db4eb1..9066b65 100644 --- a/Source/WebCore/editing/CompositeEditCommand.h +++ b/Source/WebCore/editing/CompositeEditCommand.h @@ -71,7 +71,10 @@ protected: void mergeIdenticalElements(PassRefPtr<Element>, PassRefPtr<Element>); void rebalanceWhitespace(); void rebalanceWhitespaceAt(const Position&); + void rebalanceWhitespaceOnTextSubstring(RefPtr<Text>, int startOffset, int endOffset); void prepareWhitespaceAtPositionForSplit(Position&); + bool canRebalance(const Position&) const; + bool shouldRebalanceLeadingWhitespaceFor(const String&) const; void removeCSSProperty(PassRefPtr<StyledElement>, CSSPropertyID); void removeNodeAttribute(PassRefPtr<Element>, const QualifiedName& attribute); void removeChildrenInRange(PassRefPtr<Node>, unsigned from, unsigned to); diff --git a/Source/WebCore/editing/DeleteButtonController.cpp b/Source/WebCore/editing/DeleteButtonController.cpp index 61e3190..75b9a96 100644 --- a/Source/WebCore/editing/DeleteButtonController.cpp +++ b/Source/WebCore/editing/DeleteButtonController.cpp @@ -164,7 +164,7 @@ static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection) return 0; ASSERT(element->isHTMLElement()); - return static_cast<HTMLElement*>(element); + return toHTMLElement(element); } void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection) @@ -263,7 +263,7 @@ void DeleteButtonController::show(HTMLElement* element) if (!enabled() || !element || !element->inDocument() || !isDeletableElement(element)) return; - if (!m_frame->editor()->shouldShowDeleteInterface(static_cast<HTMLElement*>(element))) + if (!m_frame->editor()->shouldShowDeleteInterface(toHTMLElement(element))) return; // we rely on the renderer having current information, so we should update the layout if needed diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp index 8caf4b6..a3f66be 100644 --- a/Source/WebCore/editing/EditingStyle.cpp +++ b/Source/WebCore/editing/EditingStyle.cpp @@ -90,18 +90,18 @@ EditingStyle::EditingStyle() { } -EditingStyle::EditingStyle(Node* node) +EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude) : m_shouldUseFixedDefaultFontSize(false) , m_fontSizeDelta(NoFontDelta) { - init(node); + init(node, propertiesToInclude); } EditingStyle::EditingStyle(const Position& position) : m_shouldUseFixedDefaultFontSize(false) , m_fontSizeDelta(NoFontDelta) { - init(position.node()); + init(position.node(), OnlyInheritableProperties); } EditingStyle::EditingStyle(const CSSStyleDeclaration* style) @@ -116,10 +116,10 @@ EditingStyle::~EditingStyle() { } -void EditingStyle::init(Node* node) +void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude) { RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node); - m_mutableStyle = editingStyleFromComputedStyle(computedStyleAtPosition); + m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition); if (node && node->computedStyle()) { RenderStyle* renderStyle = node->computedStyle(); @@ -255,6 +255,20 @@ PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties() return blockProperties; } +PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection() +{ + RefPtr<EditingStyle> textDirection = EditingStyle::create(); + textDirection->m_mutableStyle = CSSMutableStyleDeclaration::create(); + textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->getPropertyPriority(CSSPropertyUnicodeBidi)); + textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection), + m_mutableStyle->getPropertyPriority(CSSPropertyDirection)); + + m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi); + m_mutableStyle->removeProperty(CSSPropertyDirection); + + return textDirection; +} + void EditingStyle::removeBlockProperties() { if (!m_mutableStyle) diff --git a/Source/WebCore/editing/EditingStyle.h b/Source/WebCore/editing/EditingStyle.h index a71b4ad..129ade5 100644 --- a/Source/WebCore/editing/EditingStyle.h +++ b/Source/WebCore/editing/EditingStyle.h @@ -47,6 +47,7 @@ class RenderStyle; class EditingStyle : public RefCounted<EditingStyle> { public: + enum PropertiesToInclude { AllProperties, OnlyInheritableProperties }; enum ShouldPreserveWritingDirection { PreserveWritingDirection, DoNotPreserveWritingDirection }; static float NoFontDelta; @@ -55,9 +56,9 @@ public: return adoptRef(new EditingStyle()); } - static PassRefPtr<EditingStyle> create(Node* node) + static PassRefPtr<EditingStyle> create(Node* node, PropertiesToInclude propertiesToInclude = OnlyInheritableProperties) { - return adoptRef(new EditingStyle(node)); + return adoptRef(new EditingStyle(node, propertiesToInclude)); } static PassRefPtr<EditingStyle> create(const Position& position) @@ -80,6 +81,7 @@ public: void clear(); PassRefPtr<EditingStyle> copy() const; PassRefPtr<EditingStyle> extractAndRemoveBlockProperties(); + PassRefPtr<EditingStyle> extractAndRemoveTextDirection(); void removeBlockProperties(); void removeStyleAddedByNode(Node*); void removeStyleConflictingWithStyleOfNode(Node*); @@ -91,10 +93,10 @@ public: private: EditingStyle(); - EditingStyle(Node*); + EditingStyle(Node*, PropertiesToInclude); EditingStyle(const Position&); EditingStyle(const CSSStyleDeclaration*); - void init(Node*); + void init(Node*, PropertiesToInclude); void removeTextFillAndStrokeColorsIfNeeded(RenderStyle*); void replaceFontSizeByKeywordIfPossible(RenderStyle*, CSSComputedStyleDeclaration*); void extractFontSizeDelta(); diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp index 23b41ce..99624b3 100644 --- a/Source/WebCore/editing/Editor.cpp +++ b/Source/WebCore/editing/Editor.cpp @@ -36,7 +36,6 @@ #include "CSSStyleSelector.h" #include "CSSValueKeywords.h" #include "CachedResourceLoader.h" -#include "CharacterNames.h" #include "ClipboardEvent.h" #include "CompositionEvent.h" #include "CreateLinkCommand.h" @@ -83,6 +82,7 @@ #include "markup.h" #include "visible_units.h" #include <wtf/UnusedParam.h> +#include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -116,6 +116,7 @@ static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() if (markerTypesForAutoCorrection.isEmpty()) { markerTypesForAutoCorrection.append(DocumentMarker::Replacement); markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator); + markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption); } return markerTypesForAutoCorrection; } @@ -123,8 +124,10 @@ static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement() { DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ()); - if (markerTypesForReplacement.isEmpty()) + if (markerTypesForReplacement.isEmpty()) { markerTypesForReplacement.append(DocumentMarker::Replacement); + markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption); + } return markerTypesForReplacement; } @@ -505,19 +508,12 @@ bool Editor::shouldShowDeleteInterface(HTMLElement* element) const void Editor::respondToChangedSelection(const VisibleSelection& oldSelection) { -#if SUPPORT_AUTOCORRECTION_PANEL - VisibleSelection currentSelection(frame()->selection()->selection()); - if (currentSelection != oldSelection) { - stopCorrectionPanelTimer(); - dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored); - } -#endif // SUPPORT_AUTOCORRECTION_PANEL - if (client()) client()->respondToChangedSelection(); m_deleteButtonController->respondToChangedSelection(oldSelection); #if SUPPORT_AUTOCORRECTION_PANEL + VisibleSelection currentSelection(frame()->selection()->selection()); // When user moves caret to the end of autocorrected word and pauses, we show the panel // containing the original pre-correction word so that user can quickly revert the // undesired autocorrection. Here, we start correction panel timer once we confirm that @@ -1051,10 +1047,10 @@ String Editor::selectionStartCSSPropertyValue(int propertyID) if (propertyID == CSSPropertyFontSize) { RefPtr<CSSValue> cssValue = selectionStyle->getPropertyCSSValue(CSSPropertyFontSize); - ASSERT(cssValue->isPrimitiveValue()); - int fontPixelSize = static_cast<CSSPrimitiveValue*>(cssValue.get())->getIntValue(CSSPrimitiveValue::CSS_PX); - int size = CSSStyleSelector::legacyFontSize(m_frame->document(), fontPixelSize, shouldUseFixedFontDefaultSize); - value = String::number(size); + if (cssValue->isPrimitiveValue()) { + value = String::number(legacyFontSizeFromCSSValue(m_frame->document(), static_cast<CSSPrimitiveValue*>(cssValue.get()), + shouldUseFixedFontDefaultSize, AlwaysUseLegacyFontSize)); + } } return value; @@ -1089,14 +1085,20 @@ void Editor::appliedEditing(PassRefPtr<EditCommand> cmd) m_frame->document()->updateLayout(); dispatchEditableContentChangedEvents(*cmd); - VisibleSelection newSelection(cmd->endingSelection()); + +#if SUPPORT_AUTOCORRECTION_PANEL + // Check to see if the command introduced paragraph separator. If it did, we remove existing autocorrection underlines. This is in consistency with the behavior in AppKit. + if (cmd->isTopLevelCommand() && !inSameParagraph(cmd->startingSelection().start(), newSelection.end())) + m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator); +#endif + // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. changeSelectionAfterCommand(newSelection, false, false); - + if (!cmd->preservesTypingStyle()) m_frame->selection()->clearTypingStyle(); - + // Command will be equal to last edit command only in the case of typing if (m_lastEditCommand.get() == cmd) ASSERT(cmd->isTypingCommand()); @@ -1173,7 +1175,12 @@ bool Editor::insertText(const String& text, Event* triggeringEvent) return m_frame->eventHandler()->handleTextInputEvent(text, triggeringEvent); } -bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, Event* triggeringEvent) +bool Editor::insertTextForConfirmedComposition(const String& text) +{ + return m_frame->eventHandler()->handleTextInputEvent(text, 0, TextEventInputComposition); +} + +bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, TextEvent* triggeringEvent) { if (text.isEmpty()) return false; @@ -1186,6 +1193,9 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn if (!shouldInsertText(text, range.get(), EditorInsertActionTyped)) return true; + if (text == " " || text == "\t") + applyAutocorrectionBeforeTypingIfAppropriate(); + // Get the selection to use for the event that triggered this insertText. // If the event handler changed the selection, we may want to use a different selection // that is contained in the event target. @@ -1195,7 +1205,8 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn RefPtr<Document> document = selectionStart->document(); // Insert the text - TypingCommand::insertText(document.get(), text, selection, selectInsertedText); + TypingCommand::insertText(document.get(), text, selection, selectInsertedText, + triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionConfirm : TypingCommand::TextCompositionNone); // Reveal the current selection if (Frame* editedFrame = document->frame()) @@ -1215,6 +1226,8 @@ bool Editor::insertLineBreak() if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; + applyAutocorrectionBeforeTypingIfAppropriate(); + TypingCommand::insertLineBreak(m_frame->document()); revealSelectionAfterEditingOperation(); return true; @@ -1231,6 +1244,8 @@ bool Editor::insertParagraphSeparator() if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; + applyAutocorrectionBeforeTypingIfAppropriate(); + TypingCommand::insertParagraphSeparator(m_frame->document()); revealSelectionAfterEditingOperation(); return true; @@ -1543,7 +1558,7 @@ void Editor::setBaseWritingDirection(WritingDirection direction) if (focusedNode && (focusedNode->hasTagName(textareaTag) || (focusedNode->hasTagName(inputTag) && static_cast<HTMLInputElement*>(focusedNode)->isTextField()))) { if (direction == NaturalWritingDirection) return; - static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); + toHTMLElement(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); frame()->document()->updateStyleIfNeeded(); return; } @@ -1618,7 +1633,7 @@ void Editor::confirmComposition(const String& text, bool preserveSelection) m_compositionNode = 0; m_customCompositionUnderlines.clear(); - insertText(text, 0); + insertTextForConfirmedComposition(text); if (preserveSelection) { m_frame->selection()->setSelection(oldSelection, false, false); @@ -1687,7 +1702,7 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin m_customCompositionUnderlines.clear(); if (!text.isEmpty()) { - TypingCommand::insertText(m_frame->document(), text, true, true); + TypingCommand::insertText(m_frame->document(), text, true, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = m_frame->selection()->base().downstream(); @@ -2210,6 +2225,9 @@ void Editor::markBadGrammar(const VisibleSelection& selection) #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCheckingOptions, Range* spellingRange, Range* grammarRange) { + // There shouldn't be pending autocorrection at this moment. + ASSERT(!m_correctionPanelInfo.rangeToBeReplaced); + bool shouldMarkSpelling = textCheckingOptions & MarkSpelling; bool shouldMarkGrammar = textCheckingOptions & MarkGrammar; bool shouldPerformReplacement = textCheckingOptions & PerformReplacement; @@ -2303,6 +2321,10 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingParagraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) { ASSERT(resultLength > 0 && resultLocation >= 0); RefPtr<Range> misspellingRange = spellingParagraph.subrange(resultLocation, resultLength); +#if SUPPORT_AUTOCORRECTION_PANEL + if (m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption)) + continue; +#endif // SUPPORT_AUTOCORRECTION_PANEL misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); } else if (shouldMarkGrammar && result->type == TextCheckingTypeGrammar && grammarParagraph.checkingRangeCovers(resultLocation, resultLength)) { ASSERT(resultLength > 0 && resultLocation >= 0); @@ -2406,6 +2428,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh RefPtr<Range> replacedRange = paragraph.subrange(resultLocation, replacementLength); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::CorrectionIndicator, replacedString); + replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::SpellCheckingExemption); } } } @@ -2488,8 +2511,10 @@ void Editor::correctionPanelTimerFired(Timer<Editor>*) String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get()); Vector<String> suggestions; client()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions); - if (suggestions.isEmpty()) + if (suggestions.isEmpty()) { + m_correctionPanelInfo.rangeToBeReplaced.clear(); break; + } String topSuggestion = suggestions.first(); suggestions.remove(0); m_correctionPanelInfo.isActive = true; @@ -2649,44 +2674,8 @@ void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemove // of marker that contains the word in question, and remove marker on that whole range. Document* document = m_frame->document(); RefPtr<Range> wordRange = Range::create(document, startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent()); - RefPtr<Range> rangeOfFirstWord = Range::create(document, startOfFirstWord.deepEquivalent(), endOfFirstWord.deepEquivalent()); - RefPtr<Range> rangeOfLastWord = Range::create(document, startOfLastWord.deepEquivalent(), endOfLastWord.deepEquivalent()); - - typedef pair<RefPtr<Range>, DocumentMarker::MarkerType> RangeMarkerPair; - // It's probably unsafe to remove marker while iterating a vector of markers. So we store the markers and ranges that we want to remove temporarily. Then remove them at the end of function. - // To avoid allocation on the heap, Give markersToRemove a small inline capacity - Vector<RangeMarkerPair, 16> markersToRemove; - for (TextIterator textIterator(wordRange.get()); !textIterator.atEnd(); textIterator.advance()) { - Node* node = textIterator.node(); - if (!node) - continue; - if (node == startOfFirstWord.deepEquivalent().containerNode() || node == endOfLastWord.deepEquivalent().containerNode()) { - // First word and last word can belong to the same node - bool processFirstWord = node == startOfFirstWord.deepEquivalent().containerNode() && document->markers()->hasMarkers(rangeOfFirstWord.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator); - bool processLastWord = node == endOfLastWord.deepEquivalent().containerNode() && document->markers()->hasMarkers(rangeOfLastWord.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator); - // Take note on the markers whose range overlaps with the range of the first word or the last word. - Vector<DocumentMarker> markers = document->markers()->markersForNode(node); - for (size_t i = 0; i < markers.size(); ++i) { - DocumentMarker marker = markers[i]; - if (processFirstWord && static_cast<int>(marker.endOffset) > startOfFirstWord.deepEquivalent().offsetInContainerNode() && (marker.type == DocumentMarker::Spelling || marker.type == DocumentMarker::CorrectionIndicator)) { - RefPtr<Range> markerRange = Range::create(document, node, marker.startOffset, node, marker.endOffset); - markersToRemove.append(std::make_pair(markerRange, marker.type)); - } - if (processLastWord && static_cast<int>(marker.startOffset) <= endOfLastWord.deepEquivalent().offsetInContainerNode() && (marker.type == DocumentMarker::Spelling || marker.type == DocumentMarker::CorrectionIndicator)) { - RefPtr<Range> markerRange = Range::create(document, node, marker.startOffset, node, marker.endOffset); - markersToRemove.append(std::make_pair(markerRange, marker.type)); - } - } - } else { - document->markers()->removeMarkers(node, DocumentMarker::Spelling); - document->markers()->removeMarkers(node, DocumentMarker::CorrectionIndicator); - } - } - // Actually remove the markers. - Vector<RangeMarkerPair>::const_iterator pairEnd = markersToRemove.end(); - for (Vector<RangeMarkerPair>::const_iterator pairIterator = markersToRemove.begin(); pairIterator != pairEnd; ++pairIterator) - document->markers()->removeMarkers(pairIterator->first.get(), pairIterator->second); + document->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator | DocumentMarker::SpellCheckingExemption, DocumentMarkerController::RemovePartiallyOverlappingMarker); } void Editor::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd) @@ -2719,7 +2708,6 @@ void Editor::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& // Take note of the location of autocorrection so that we can add marker after the replacement took place. int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get()); - Position caretPosition = m_frame->selection()->selection().end(); // Clone the range, since the caller of this method may want to keep the original range around. RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec); @@ -2727,17 +2715,34 @@ void Editor::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& if (m_frame->selection()->shouldChangeSelection(selectionToReplace)) { m_frame->selection()->setSelection(selectionToReplace); replaceSelectionWithText(m_correctionPanelInfo.replacementString, false, false); - caretPosition.moveToOffset(caretPosition.offsetInContainerNode() + m_correctionPanelInfo.replacementString.length() - m_correctionPanelInfo.replacedString.length()); - setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(caretPosition)); + setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start()); RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.replacementString.length()); DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers(); size_t size = markerTypesToAdd.size(); - for (size_t i = 0; i < size; ++i) - markers->addMarker(replacementRange.get(), markerTypesToAdd[i], m_correctionPanelInfo.replacementString); - m_frame->selection()->moveTo(caretPosition, false); + for (size_t i = 0; i < size; ++i) { + if (m_correctionPanelInfo.panelType == CorrectionPanelInfo::PanelTypeReversion) + markers->addMarker(replacementRange.get(), markerTypesToAdd[i]); + else + markers->addMarker(replacementRange.get(), markerTypesToAdd[i], m_correctionPanelInfo.replacedString); + } } } +void Editor::applyAutocorrectionBeforeTypingIfAppropriate() +{ + if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive) + return; + + if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection) + return; + + Position caretPosition = m_frame->selection()->selection().start(); + + // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction. + ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition); + dismissCorrectionPanel(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition ? ReasonForDismissingCorrectionPanelAccepted : ReasonForDismissingCorrectionPanelIgnored); +} + PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) { Document* document = m_frame->documentAtPoint(windowPoint); @@ -3004,13 +3009,6 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, b if (newSelection.start().isOrphan() || newSelection.end().isOrphan()) return; -#if SUPPORT_AUTOCORRECTION_PANEL - // Check to see if the command introduced paragraph separator. If it did, we remove existing autocorrection underlines. - // This is in consistency with the behavior in AppKit - if (!inSameParagraph(m_frame->selection()->selection().visibleStart(), newSelection.visibleEnd())) - m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator); -#endif - // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, // because there is work that it must do in this situation. // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. @@ -3018,13 +3016,13 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, b bool selectionDidNotChangeDOMPosition = newSelection == m_frame->selection()->selection(); if (selectionDidNotChangeDOMPosition || m_frame->selection()->shouldChangeSelection(newSelection)) m_frame->selection()->setSelection(newSelection, closeTyping, clearTypingStyle); - + // Some editing operations change the selection visually without affecting its position within the DOM. - // For example when you press return in the following (the caret is marked by ^): + // For example when you press return in the following (the caret is marked by ^): // <div contentEditable="true"><div>^Hello</div></div> // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't // change the caret's DOM position (["hello", 0]). In these situations the above SelectionController::setSelection call - // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and + // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and // starts a new kill ring sequence, but we want to do these things (matches AppKit). if (selectionDidNotChangeDOMPosition) client()->respondToChangedSelection(); @@ -3142,8 +3140,7 @@ PassRefPtr<CSSMutableStyleDeclaration> Editor::selectionComputedStyle(bool& shou if (!m_frame->selection()->typingStyle()) return mutableStyle; - RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle(); - typingStyle->removeNonEditingProperties(); + RefPtr<EditingStyle> typingStyle = m_frame->selection()->typingStyle()->copy(); typingStyle->prepareToApplyAt(position); mutableStyle->merge(typingStyle->style()); @@ -3421,7 +3418,11 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt if (!visibleRect.isEmpty()) { GraphicsContext context((PlatformGraphicsContext*)0); context.setPaintingDisabled(true); + + PaintBehavior oldBehavior = m_frame->view()->paintBehavior(); + m_frame->view()->setPaintBehavior(oldBehavior | PaintBehaviorFlattenCompositingLayers); m_frame->view()->paintContents(&context, visibleRect); + m_frame->view()->setPaintBehavior(oldBehavior); } } } @@ -3440,6 +3441,15 @@ void Editor::setMarkedTextMatchesAreHighlighted(bool flag) void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, bool closeTyping) { +#if SUPPORT_AUTOCORRECTION_PANEL + // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below. + VisibleSelection currentSelection(frame()->selection()->selection()); + if (currentSelection != oldSelection) { + stopCorrectionPanelTimer(); + dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored); + } +#endif // SUPPORT_AUTOCORRECTION_PANEL + bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled(); bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled(); if (isContinuousSpellCheckingEnabled) { diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h index 2e61ce6..0a06a4a 100644 --- a/Source/WebCore/editing/Editor.h +++ b/Source/WebCore/editing/Editor.h @@ -198,7 +198,8 @@ public: static bool commandIsSupportedFromMenuOrKeyBinding(const String& commandName); // Works without a frame. bool insertText(const String&, Event* triggeringEvent); - bool insertTextWithoutSendingTextEvent(const String&, bool selectInsertedText, Event* triggeringEvent); + bool insertTextForConfirmedComposition(const String& text); + bool insertTextWithoutSendingTextEvent(const String&, bool selectInsertedText, TextEvent* triggeringEvent); bool insertLineBreak(); bool insertParagraphSeparator(); @@ -423,6 +424,7 @@ private: void stopCorrectionPanelTimer(); void dismissCorrectionPanel(ReasonForDismissingCorrectionPanel); void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd); + void applyAutocorrectionBeforeTypingIfAppropriate(); }; inline void Editor::setStartNewKillRingSequence(bool flag) diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp index 9e5bf9f..451d855 100644 --- a/Source/WebCore/editing/EditorCommand.cpp +++ b/Source/WebCore/editing/EditorCommand.cpp @@ -930,6 +930,26 @@ static bool executeRemoveFormat(Frame* frame, Event*, EditorCommandSource, const return true; } +static bool executeScrollPageBackward(Frame* frame, Event*, EditorCommandSource, const String&) +{ + return frame->eventHandler()->logicalScrollRecursively(ScrollBlockDirectionBackward, ScrollByPage); +} + +static bool executeScrollPageForward(Frame* frame, Event*, EditorCommandSource, const String&) +{ + return frame->eventHandler()->logicalScrollRecursively(ScrollBlockDirectionForward, ScrollByPage); +} + +static bool executeScrollToBeginningOfDocument(Frame* frame, Event*, EditorCommandSource, const String&) +{ + return frame->eventHandler()->logicalScrollRecursively(ScrollBlockDirectionBackward, ScrollByDocument); +} + +static bool executeScrollToEndOfDocument(Frame* frame, Event*, EditorCommandSource, const String&) +{ + return frame->eventHandler()->logicalScrollRecursively(ScrollBlockDirectionForward, ScrollByDocument); +} + static bool executeSelectAll(Frame* frame, Event*, EditorCommandSource, const String&) { frame->selection()->selectAll(); @@ -1477,6 +1497,10 @@ static const CommandMap& createCommandMap() { "Print", { executePrint, supported, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Redo", { executeRedo, supported, enabledRedo, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "RemoveFormat", { executeRemoveFormat, supported, enabledRangeInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "ScrollPageBackward", { executeScrollPageBackward, supportedFromMenuOrKeyBinding, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "ScrollPageForward", { executeScrollPageForward, supportedFromMenuOrKeyBinding, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "ScrollToBeginningOfDocument", { executeScrollToBeginningOfDocument, supportedFromMenuOrKeyBinding, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "ScrollToEndOfDocument", { executeScrollToEndOfDocument, supportedFromMenuOrKeyBinding, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "SelectAll", { executeSelectAll, supported, enabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "SelectLine", { executeSelectLine, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "SelectParagraph", { executeSelectParagraph, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, diff --git a/Source/WebCore/editing/FormatBlockCommand.cpp b/Source/WebCore/editing/FormatBlockCommand.cpp index e43f330..58157af 100644 --- a/Source/WebCore/editing/FormatBlockCommand.cpp +++ b/Source/WebCore/editing/FormatBlockCommand.cpp @@ -67,6 +67,9 @@ void FormatBlockCommand::formatRange(const Position& start, const Position& end, RefPtr<Range> range = Range::create(document(), start, endOfSelection); Element* refNode = enclosingBlockFlowElement(end); Element* root = editableRootForPosition(start); + // Root is null for elements with contenteditable=false. + if (!root) + return; if (isElementForFormatBlock(refNode->tagQName()) && start == startOfBlock(start) && (end == endOfBlock(end) || isNodeVisiblyContainedWithin(refNode, range.get())) && refNode != root && !root->isDescendantOf(refNode)) { diff --git a/Source/WebCore/editing/HTMLInterchange.cpp b/Source/WebCore/editing/HTMLInterchange.cpp index 16b330d..1ce06ab 100644 --- a/Source/WebCore/editing/HTMLInterchange.cpp +++ b/Source/WebCore/editing/HTMLInterchange.cpp @@ -26,10 +26,10 @@ #include "config.h" #include "HTMLInterchange.h" -#include "CharacterNames.h" #include "Text.h" #include "TextIterator.h" #include <wtf/StdLibExtras.h> +#include <wtf/unicode/CharacterNames.h> namespace WebCore { diff --git a/Source/WebCore/editing/InsertListCommand.cpp b/Source/WebCore/editing/InsertListCommand.cpp index 9348786..c24c683 100644 --- a/Source/WebCore/editing/InsertListCommand.cpp +++ b/Source/WebCore/editing/InsertListCommand.cpp @@ -72,7 +72,7 @@ PassRefPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtr< if (!list || !list->nextElementSibling() || !list->nextElementSibling()->isHTMLElement()) return list.release(); - RefPtr<HTMLElement> nextList = static_cast<HTMLElement*>(list->nextElementSibling()); + RefPtr<HTMLElement> nextList = toHTMLElement(list->nextElementSibling()); if (canMergeLists(list.get(), nextList.get())) { mergeIdenticalElements(list, nextList); return nextList.release(); diff --git a/Source/WebCore/editing/InsertTextCommand.cpp b/Source/WebCore/editing/InsertTextCommand.cpp index fc18e91..1ec11ad 100644 --- a/Source/WebCore/editing/InsertTextCommand.cpp +++ b/Source/WebCore/editing/InsertTextCommand.cpp @@ -26,7 +26,6 @@ #include "config.h" #include "InsertTextCommand.h" -#include "CharacterNames.h" #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" @@ -41,6 +40,7 @@ #include "TextIterator.h" #include "TypingCommand.h" #include "visible_units.h" +#include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -61,13 +61,13 @@ Position InsertTextCommand::prepareForTextInsertion(const Position& p) if (!pos.node()->isTextNode()) { RefPtr<Node> textNode = document()->createEditingTextNode(""); insertNodeAt(textNode.get(), pos); - return Position(textNode.get(), 0); + return firstPositionInNode(textNode.get()); } if (isTabSpanTextNode(pos.node())) { RefPtr<Node> textNode = document()->createEditingTextNode(""); insertNodeAtTabSpanPosition(textNode.get(), pos); - return Position(textNode.get(), 0); + return firstPositionInNode(textNode.get()); } return pos; @@ -83,30 +83,32 @@ bool InsertTextCommand::performTrivialReplace(const String& text, bool selectIns if (text.contains('\t') || text.contains(' ') || text.contains('\n')) return false; - Position start = endingSelection().start(); - Position end = endingSelection().end(); - - if (start.node() != end.node() || !start.node()->isTextNode() || isTabSpanTextNode(start.node())) + Position start = endingSelection().start().parentAnchoredEquivalent(); + Position end = endingSelection().end().parentAnchoredEquivalent(); + ASSERT(start.anchorType() == Position::PositionIsOffsetInAnchor); + ASSERT(end.anchorType() == Position::PositionIsOffsetInAnchor); + + if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode() || isTabSpanTextNode(start.containerNode())) return false; - - replaceTextInNode(static_cast<Text*>(start.node()), start.deprecatedEditingOffset(), end.deprecatedEditingOffset() - start.deprecatedEditingOffset(), text); - - Position endPosition(start.node(), start.deprecatedEditingOffset() + text.length()); - + + replaceTextInNode(static_cast<Text*>(start.containerNode()), start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text); + + Position endPosition(start.containerNode(), start.offsetInContainerNode() + text.length()); + // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> VisibleSelection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(start, endPosition); setEndingSelection(forcedEndingSelection); - + if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().visibleEnd())); return true; } -void InsertTextCommand::input(const String& text, bool selectInsertedText) +void InsertTextCommand::input(const String& text, bool selectInsertedText, RebalanceType whitespaceRebalance) { ASSERT(text.find('\n') == notFound); @@ -170,13 +172,19 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText) int offset = startPosition.deprecatedEditingOffset(); insertTextIntoNode(textNode, offset, text); - endPosition = Position(textNode, offset + text.length()); - - // The insertion may require adjusting adjacent whitespace, if it is present. - rebalanceWhitespaceAt(endPosition); - // Rebalancing on both sides isn't necessary if we've inserted a space. - if (text != " ") - rebalanceWhitespaceAt(startPosition); + endPosition = Position(textNode, offset + text.length(), Position::PositionIsOffsetInAnchor); + + if (whitespaceRebalance == RebalanceLeadingAndTrailingWhitespaces) { + // The insertion may require adjusting adjacent whitespace, if it is present. + rebalanceWhitespaceAt(endPosition); + // Rebalancing on both sides isn't necessary if we've inserted only spaces. + if (!shouldRebalanceLeadingWhitespaceFor(text)) + rebalanceWhitespaceAt(startPosition); + } else { + ASSERT(whitespaceRebalance == RebalanceAllWhitespaces); + if (canRebalance(startPosition) && canRebalance(endPosition)) + rebalanceWhitespaceOnTextSubstring(textNode, startPosition.deprecatedEditingOffset(), endPosition.deprecatedEditingOffset()); + } } // We could have inserted a part of composed character sequence, @@ -207,7 +215,7 @@ Position InsertTextCommand::insertTab(const Position& pos) // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { insertTextIntoNode(static_cast<Text *>(node), offset, "\t"); - return Position(node, offset + 1); + return Position(node, offset + 1, Position::PositionIsOffsetInAnchor); } // create new tab span @@ -230,9 +238,9 @@ Position InsertTextCommand::insertTab(const Position& pos) insertNodeBefore(spanNode, textNode); } } - + // return the position following the new tab - return Position(spanNode->lastChild(), caretMaxOffset(spanNode->lastChild())); + return lastPositionInNode(spanNode.get()); } bool InsertTextCommand::isInsertTextCommand() const diff --git a/Source/WebCore/editing/InsertTextCommand.h b/Source/WebCore/editing/InsertTextCommand.h index 77ae016..672e576 100644 --- a/Source/WebCore/editing/InsertTextCommand.h +++ b/Source/WebCore/editing/InsertTextCommand.h @@ -32,14 +32,20 @@ namespace WebCore { class InsertTextCommand : public CompositeEditCommand { public: + enum RebalanceType { + RebalanceLeadingAndTrailingWhitespaces, + RebalanceAllWhitespaces + }; + static PassRefPtr<InsertTextCommand> create(Document* document) { return adoptRef(new InsertTextCommand(document)); } - void input(const String& text, bool selectInsertedText = false); + void input(const String& text, bool selectInsertedText = false, RebalanceType = RebalanceLeadingAndTrailingWhitespaces); private: + InsertTextCommand(Document*); void deleteCharacter(); diff --git a/Source/WebCore/editing/MarkupAccumulator.cpp b/Source/WebCore/editing/MarkupAccumulator.cpp index f6dbd8b..f4489c5 100644 --- a/Source/WebCore/editing/MarkupAccumulator.cpp +++ b/Source/WebCore/editing/MarkupAccumulator.cpp @@ -28,7 +28,6 @@ #include "MarkupAccumulator.h" #include "CDATASection.h" -#include "CharacterNames.h" #include "Comment.h" #include "DocumentFragment.h" #include "DocumentType.h" @@ -38,6 +37,7 @@ #include "KURL.h" #include "ProcessingInstruction.h" #include "XMLNSNames.h" +#include <wtf/unicode/CharacterNames.h> namespace WebCore { diff --git a/Source/WebCore/editing/MarkupAccumulator.h b/Source/WebCore/editing/MarkupAccumulator.h index 5a9c884..0bfc6e6 100644 --- a/Source/WebCore/editing/MarkupAccumulator.h +++ b/Source/WebCore/editing/MarkupAccumulator.h @@ -72,7 +72,7 @@ public: String serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly); protected: - void appendString(const String&); + virtual void appendString(const String&); void appendStartTag(Node*, Namespaces* = 0); void appendEndTag(Node*); static size_t totalLength(const Vector<String>&); diff --git a/Source/WebCore/editing/MoveSelectionCommand.cpp b/Source/WebCore/editing/MoveSelectionCommand.cpp index 3a1cae0..0f23b29 100644 --- a/Source/WebCore/editing/MoveSelectionCommand.cpp +++ b/Source/WebCore/editing/MoveSelectionCommand.cpp @@ -39,25 +39,21 @@ MoveSelectionCommand::MoveSelectionCommand(PassRefPtr<DocumentFragment> fragment void MoveSelectionCommand::doApply() { - VisibleSelection selection = endingSelection(); - ASSERT(selection.isNonOrphanedRange()); + ASSERT(endingSelection().isNonOrphanedRange()); Position pos = m_position; if (pos.isNull()) return; - + // Update the position otherwise it may become invalid after the selection is deleted. - Node *positionNode = m_position.node(); - int positionOffset = m_position.deprecatedEditingOffset(); - Position selectionEnd = selection.end(); - int selectionEndOffset = selectionEnd.deprecatedEditingOffset(); - if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) { - positionOffset -= selectionEndOffset; - Position selectionStart = selection.start(); - if (selectionStart.node() == positionNode) { - positionOffset += selectionStart.deprecatedEditingOffset(); - } - pos = Position(positionNode, positionOffset); + Position selectionEnd = endingSelection().end(); + if (pos.anchorType() == Position::PositionIsOffsetInAnchor && selectionEnd.anchorType() == Position::PositionIsOffsetInAnchor + && selectionEnd.containerNode() == pos.containerNode() && selectionEnd.offsetInContainerNode() < pos.offsetInContainerNode()) { + pos.moveToOffset(pos.offsetInContainerNode() - selectionEnd.offsetInContainerNode()); + + Position selectionStart = endingSelection().start(); + if (selectionStart.anchorType() == Position::PositionIsOffsetInAnchor && selectionStart.containerNode() == pos.containerNode()) + pos.moveToOffset(pos.offsetInContainerNode() + selectionStart.offsetInContainerNode()); } deleteSelection(m_smartDelete); @@ -70,11 +66,11 @@ void MoveSelectionCommand::doApply() pos = endingSelection().start(); setEndingSelection(VisibleSelection(pos, endingSelection().affinity())); - if (!positionNode->inDocument()) { + if (!pos.anchorNode()->inDocument()) { // Document was modified out from under us. return; } - applyCommandToComposite(ReplaceSelectionCommand::create(positionNode->document(), m_fragment, true, m_smartInsert)); + applyCommandToComposite(ReplaceSelectionCommand::create(document(), m_fragment, true, m_smartInsert)); } EditAction MoveSelectionCommand::editingAction() const diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp index f47cb4e..846b932 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp @@ -476,7 +476,7 @@ void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance() for (RefPtr<Node> node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) { // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance if (isStyleSpan(node.get())) { - HTMLElement* e = static_cast<HTMLElement*>(node.get()); + HTMLElement* e = toHTMLElement(node.get()); // There are other styles that style rules can give to style spans, // but these are the two important ones because they'll prevent // inserted content from appearing in the right paragraph. @@ -496,10 +496,10 @@ void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance() void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds() { document()->updateLayoutIgnorePendingStylesheets(); - if (!m_lastLeafInserted->renderer() && - m_lastLeafInserted->isTextNode() && - !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), selectTag) && - !enclosingNodeWithTag(Position(m_lastLeafInserted.get(), 0), scriptTag)) { + if (!m_lastLeafInserted->renderer() + && m_lastLeafInserted->isTextNode() + && !enclosingNodeWithTag(firstPositionInOrBeforeNode(m_lastLeafInserted.get()), selectTag) + && !enclosingNodeWithTag(firstPositionInOrBeforeNode(m_lastLeafInserted.get()), scriptTag)) { if (m_firstNodeInserted == m_lastLeafInserted) { removeNode(m_lastLeafInserted.get()); m_lastLeafInserted = 0; @@ -620,7 +620,7 @@ void ReplaceSelectionCommand::handleStyleSpans() if (!sourceDocumentStyleSpan) return; - RefPtr<EditingStyle> sourceDocumentStyle = EditingStyle::create(static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()); + RefPtr<EditingStyle> sourceDocumentStyle = EditingStyle::create(toHTMLElement(sourceDocumentStyleSpan)->getInlineStyleDecl()); ContainerNode* context = sourceDocumentStyleSpan->parentNode(); // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, @@ -657,7 +657,7 @@ void ReplaceSelectionCommand::handleStyleSpans() return; } - RefPtr<EditingStyle> copiedRangeStyle = EditingStyle::create(static_cast<HTMLElement*>(copiedRangeStyleSpan)->getInlineStyleDecl()); + RefPtr<EditingStyle> copiedRangeStyle = EditingStyle::create(toHTMLElement(copiedRangeStyleSpan)->getInlineStyleDecl()); // We're going to put sourceDocumentStyleSpan's non-redundant styles onto copiedRangeStyleSpan, // as long as they aren't overridden by ones on copiedRangeStyleSpan. @@ -696,7 +696,7 @@ void ReplaceSelectionCommand::copyStyleToChildren(Node* parentNode, const CSSMut // In this case, put a span tag around the child node. RefPtr<Node> newNode = parentNode->cloneNode(false); ASSERT(newNode->hasTagName(spanTag)); - HTMLElement* newSpan = static_cast<HTMLElement*>(newNode.get()); + HTMLElement* newSpan = toHTMLElement(newNode.get()); setNodeAttribute(newSpan, styleAttr, parentStyle->cssText()); insertNodeAfter(newSpan, childNode); ExceptionCode ec = 0; @@ -707,7 +707,7 @@ void ReplaceSelectionCommand::copyStyleToChildren(Node* parentNode, const CSSMut // Copy the style attribute and merge them into the child node. We don't want to override // existing styles, so don't clobber on merge. RefPtr<CSSMutableStyleDeclaration> newStyle = parentStyle->copy(); - HTMLElement* childElement = static_cast<HTMLElement*>(childNode); + HTMLElement* childElement = toHTMLElement(childNode); RefPtr<CSSMutableStyleDeclaration> existingStyles = childElement->getInlineStyleDecl()->copy(); existingStyles->merge(newStyle.get(), false); setNodeAttribute(childElement, styleAttr, existingStyles->cssText()); @@ -743,7 +743,7 @@ void ReplaceSelectionCommand::mergeEndIfNeeded() if (endOfParagraph(startOfParagraphToMove) == destination) { RefPtr<Node> placeholder = createBreakElement(document()); insertNodeBefore(placeholder, startOfParagraphToMove.deepEquivalent().node()); - destination = VisiblePosition(Position(placeholder.get(), 0)); + destination = VisiblePosition(positionBeforeNode(placeholder.get())); } moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination); @@ -1045,7 +1045,7 @@ void ReplaceSelectionCommand::doApply() if (isListItem(enclosingNode)) { RefPtr<Node> newListItem = createListItemElement(document()); insertNodeAfter(newListItem, enclosingNode); - setEndingSelection(VisiblePosition(Position(newListItem, 0))); + setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.get()))); } else // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph // block's style seems to annoy users. @@ -1124,7 +1124,7 @@ bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePositi if (!endBR || !endBR->inDocument()) return false; - VisiblePosition visiblePos(Position(endBR, 0)); + VisiblePosition visiblePos(positionBeforeNode(endBR)); // Don't remove the br if nothing was inserted. if (visiblePos.previous() == originalVisPosBeforeEndBR) @@ -1261,7 +1261,7 @@ bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f { if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode()) return false; - + // FIXME: Would be nice to handle smart replace in the fast path. if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd()) return false; @@ -1270,20 +1270,22 @@ bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here. String text(textNode->data()); - Position start = endingSelection().start(); - Position end = endingSelection().end(); - - if (start.anchorNode() != end.anchorNode() || !start.anchorNode()->isTextNode()) + Position start = endingSelection().start().parentAnchoredEquivalent(); + Position end = endingSelection().end().parentAnchoredEquivalent(); + ASSERT(start.anchorType() == Position::PositionIsOffsetInAnchor); + ASSERT(end.anchorType() == Position::PositionIsOffsetInAnchor); + + if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode()) return false; - - replaceTextInNode(static_cast<Text*>(start.anchorNode()), start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text); - - end = Position(start.anchorNode(), start.offsetInContainerNode() + text.length()); - + + replaceTextInNode(static_cast<Text*>(start.containerNode()), start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text); + + end = Position(start.containerNode(), start.offsetInContainerNode() + text.length(), Position::PositionIsOffsetInAnchor); + VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end); - + setEndingSelection(selectionAfterReplace); - + return true; } diff --git a/Source/WebCore/editing/SelectionController.cpp b/Source/WebCore/editing/SelectionController.cpp index fa0c32d..a574b5a 100644 --- a/Source/WebCore/editing/SelectionController.cpp +++ b/Source/WebCore/editing/SelectionController.cpp @@ -1707,7 +1707,7 @@ static HTMLFormElement* scanForForm(Node* start) for (Node* node = start; node; node = node->traverseNextNode()) { if (node->hasTagName(formTag)) return static_cast<HTMLFormElement*>(node); - if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->isFormControlElement()) + if (node->isHTMLElement() && toHTMLElement(node)->isFormControlElement()) return static_cast<HTMLFormControlElement*>(node)->form(); if (node->hasTagName(frameTag) || node->hasTagName(iframeTag)) { Node* childDocument = static_cast<HTMLFrameElementBase*>(node)->contentDocument(); @@ -1731,7 +1731,7 @@ HTMLFormElement* SelectionController::currentForm() const for (node = start; node; node = node->parentNode()) { if (node->hasTagName(formTag)) return static_cast<HTMLFormElement*>(node); - if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->isFormControlElement()) + if (node->isHTMLElement() && toHTMLElement(node)->isFormControlElement()) return static_cast<HTMLFormControlElement*>(node)->form(); } @@ -1781,7 +1781,7 @@ void SelectionController::setSelectionFromNone() while (node && !node->hasTagName(bodyTag)) node = node->traverseNextNode(); if (node) - setSelection(VisibleSelection(Position(node, 0), DOWNSTREAM)); + setSelection(VisibleSelection(firstPositionInOrBeforeNode(node), DOWNSTREAM)); } bool SelectionController::shouldChangeSelection(const VisibleSelection& newSelection) const diff --git a/Source/WebCore/editing/SpellChecker.cpp b/Source/WebCore/editing/SpellChecker.cpp index 1807474..f14a74d 100644 --- a/Source/WebCore/editing/SpellChecker.cpp +++ b/Source/WebCore/editing/SpellChecker.cpp @@ -141,7 +141,7 @@ void SpellChecker::didCheck(int sequence, const Vector<SpellCheckingResult>& res } int startOffset = 0; - PositionIterator start = Position(m_requestNode, 0); + PositionIterator start = firstPositionInOrBeforeNode(m_requestNode.get()); for (size_t i = 0; i < results.size(); ++i) { if (results[i].type() != DocumentMarker::Spelling && results[i].type() != DocumentMarker::Grammar) continue; diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index 1fc7606..b621dc2 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -27,7 +27,6 @@ #include "config.h" #include "TextIterator.h" -#include "CharacterNames.h" #include "Document.h" #include "HTMLElement.h" #include "HTMLNames.h" @@ -42,6 +41,7 @@ #include "TextBreakIterator.h" #include "VisiblePosition.h" #include "visible_units.h" +#include <wtf/unicode/CharacterNames.h> #if USE(ICU_UNICODE) && !UCONFIG_NO_COLLATION #include "TextBreakIteratorInternalICU.h" @@ -543,7 +543,7 @@ void TextIterator::handleTextBox() unsigned runStart = max(textBoxStart, start); // Check for collapsed space at the start of this run. - InlineTextBox* firstTextBox = renderer->containsReversedText() ? m_sortedTextBoxes[0] : renderer->firstTextBox(); + InlineTextBox* firstTextBox = renderer->containsReversedText() ? (m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]) : renderer->firstTextBox(); bool needSpace = m_lastTextNodeEndedWithCollapsedSpace || (m_textBox == firstTextBox && textBoxStart == runStart && runStart > 0); if (needSpace && !isCollapsibleWhitespace(m_lastCharacter) && m_lastCharacter) { @@ -551,7 +551,7 @@ void TextIterator::handleTextBox() unsigned spaceRunStart = runStart - 1; while (spaceRunStart > 0 && str[spaceRunStart - 1] == ' ') --spaceRunStart; - emitText(m_node, spaceRunStart, spaceRunStart + 1); + emitText(m_node, renderer, spaceRunStart, spaceRunStart + 1); } else emitCharacter(' ', m_node, 0, runStart, runStart); return; @@ -1922,6 +1922,11 @@ inline SearchBuffer::SearchBuffer(const String& target, FindOptions options) inline SearchBuffer::~SearchBuffer() { + // Leave the static object pointing to a valid string. + UErrorCode status = U_ZERO_ERROR; + usearch_setPattern(WebCore::searcher(), &newlineCharacter, 1, &status); + ASSERT(status == U_ZERO_ERROR); + unlockSearcher(); } diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp index d54b388..9723c2d 100644 --- a/Source/WebCore/editing/TypingCommand.cpp +++ b/Source/WebCore/editing/TypingCommand.cpp @@ -47,7 +47,8 @@ namespace WebCore { using namespace HTMLNames; -TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, bool selectInsertedText, TextGranularity granularity, bool killRing) +TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, bool selectInsertedText, TextGranularity granularity, TextCompositionType compositionType, + bool killRing) : CompositeEditCommand(document), m_commandType(commandType), m_textToInsert(textToInsert), @@ -55,6 +56,7 @@ TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, con m_selectInsertedText(selectInsertedText), m_smartDelete(false), m_granularity(granularity), + m_compositionType(compositionType), m_killRing(killRing), m_openedByBackwardDelete(false) { @@ -133,18 +135,18 @@ void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand } -void TypingCommand::insertText(Document* document, const String& text, bool selectInsertedText, bool insertedTextIsComposition) +void TypingCommand::insertText(Document* document, const String& text, bool selectInsertedText, TextCompositionType composition) { ASSERT(document); Frame* frame = document->frame(); ASSERT(frame); - insertText(document, text, frame->selection()->selection(), selectInsertedText, insertedTextIsComposition); + insertText(document, text, frame->selection()->selection(), selectInsertedText, composition); } // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to SelectionController's current selection. -void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, bool selectInsertedText, bool insertedTextIsComposition) +void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, bool selectInsertedText, TextCompositionType compositionType) { #if REMOVE_MARKERS_UPON_EDITING if (!text.isEmpty()) @@ -161,7 +163,7 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis String newText = text; Node* startNode = selectionForInsertion.start().node(); - if (startNode && startNode->rootEditableElement() && !insertedTextIsComposition) { + if (startNode && startNode->rootEditableElement() && compositionType != TextCompositionUpdate) { // Send BeforeTextInsertedEvent. The event handler will update text if necessary. ExceptionCode ec = 0; RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); @@ -182,11 +184,13 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis lastTypingCommand->setStartingSelection(selectionForInsertion); lastTypingCommand->setEndingSelection(selectionForInsertion); } + + lastTypingCommand->setCompositionType(compositionType); lastTypingCommand->insertText(newText, selectInsertedText); return; } - RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, selectInsertedText); + RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, selectInsertedText, compositionType); if (changeSelection) { cmd->setStartingSelection(selectionForInsertion); cmd->setEndingSelection(selectionForInsertion); @@ -386,7 +390,8 @@ void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool select command->setStartingSelection(endingSelection()); command->setEndingSelection(endingSelection()); } - command->input(text, selectInsertedText); + command->input(text, selectInsertedText, + m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); typingAddedToOpenCommand(InsertText); } @@ -431,7 +436,7 @@ bool TypingCommand::makeEditableRootEmpty() removeNode(child); addBlockPlaceholderIfNeeded(root); - setEndingSelection(VisibleSelection(Position(root, 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM)); return true; } @@ -490,7 +495,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) selection.modify(SelectionController::AlterationExtend, DirectionBackward, granularity); // If the caret is just after a table, select the table and don't delete anything. } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { - setEndingSelection(VisibleSelection(Position(table, 0), endingSelection().start(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(positionAfterNode(table), endingSelection().start(), DOWNSTREAM)); typingAddedToOpenCommand(DeleteKey); return; } @@ -591,7 +596,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki extraCharacters = selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset(); else extraCharacters = selectionToDelete.end().deprecatedEditingOffset(); - extent = Position(extent.node(), extent.deprecatedEditingOffset() + extraCharacters); + extent = Position(extent.node(), extent.deprecatedEditingOffset() + extraCharacters, Position::PositionIsOffsetInAnchor); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } diff --git a/Source/WebCore/editing/TypingCommand.h b/Source/WebCore/editing/TypingCommand.h index 284ebc0..b34bdc1 100644 --- a/Source/WebCore/editing/TypingCommand.h +++ b/Source/WebCore/editing/TypingCommand.h @@ -42,11 +42,17 @@ public: InsertParagraphSeparatorInQuotedContent }; + enum TextCompositionType { + TextCompositionNone, + TextCompositionUpdate, + TextCompositionConfirm + }; + static void deleteSelection(Document*, bool smartDelete = false); static void deleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false); static void forwardDeleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false); - static void insertText(Document*, const String&, bool selectInsertedText = false, bool insertedTextIsComposition = false); - static void insertText(Document*, const String&, const VisibleSelection&, bool selectInsertedText = false, bool insertedTextIsComposition = false); + static void insertText(Document*, const String&, bool selectInsertedText = false, TextCompositionType = TextCompositionNone); + static void insertText(Document*, const String&, const VisibleSelection&, bool selectInsertedText = false, TextCompositionType = TextCompositionNone); static void insertLineBreak(Document*); static void insertParagraphSeparator(Document*); static void insertParagraphSeparatorInQuotedContent(Document*); @@ -64,14 +70,20 @@ public: void deleteKeyPressed(TextGranularity, bool killRing); void forwardDeleteKeyPressed(TextGranularity, bool killRing); void deleteSelection(bool smartDelete); + void setCompositionType(TextCompositionType type) { m_compositionType = type; } private: static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text = "", bool selectInsertedText = false, TextGranularity granularity = CharacterGranularity, bool killRing = false) { - return adoptRef(new TypingCommand(document, command, text, selectInsertedText, granularity, killRing)); + return adoptRef(new TypingCommand(document, command, text, selectInsertedText, granularity, TextCompositionNone, killRing)); + } + + static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text, bool selectInsertedText, TextCompositionType compositionType) + { + return adoptRef(new TypingCommand(document, command, text, selectInsertedText, CharacterGranularity, compositionType, false)); } - TypingCommand(Document*, ETypingCommand, const String& text, bool selectInsertedText, TextGranularity, bool killRing); + TypingCommand(Document*, ETypingCommand, const String& text, bool selectInsertedText, TextGranularity, TextCompositionType, bool killRing); bool smartDelete() const { return m_smartDelete; } void setSmartDelete(bool smartDelete) { m_smartDelete = smartDelete; } @@ -94,6 +106,7 @@ private: bool m_selectInsertedText; bool m_smartDelete; TextGranularity m_granularity; + TextCompositionType m_compositionType; bool m_killRing; bool m_preservesTypingStyle; diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp index 035afb8..9096dc5 100644 --- a/Source/WebCore/editing/VisibleSelection.cpp +++ b/Source/WebCore/editing/VisibleSelection.cpp @@ -26,7 +26,6 @@ #include "config.h" #include "VisibleSelection.h" -#include "CharacterNames.h" #include "Document.h" #include "Element.h" #include "htmlediting.h" @@ -34,10 +33,10 @@ #include "VisiblePosition.h" #include "visible_units.h" #include "Range.h" - +#include <stdio.h> #include <wtf/Assertions.h> #include <wtf/text/CString.h> -#include <stdio.h> +#include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -173,6 +172,9 @@ PassRefPtr<Range> VisibleSelection::toNormalizedRange() const s = s.parentAnchoredEquivalent(); e = e.parentAnchoredEquivalent(); } + + if (s.isNull() || e.isNull()) + return 0; // VisibleSelections are supposed to always be valid. This constructor will ASSERT // if a valid range could not be created, which is fine for this callsite. @@ -432,7 +434,6 @@ void VisibleSelection::setWithoutValidation(const Position& base, const Position { ASSERT(!base.isNull()); ASSERT(!extent.isNull()); - ASSERT(base != extent); ASSERT(m_affinity == DOWNSTREAM); m_base = base; m_extent = extent; @@ -444,7 +445,7 @@ void VisibleSelection::setWithoutValidation(const Position& base, const Position m_start = extent; m_end = base; } - m_selectionType = RangeSelection; + m_selectionType = base == extent ? CaretSelection : RangeSelection; } void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() @@ -526,13 +527,13 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() Position p = nextVisuallyDistinctCandidate(m_start); Node* shadowAncestor = startRoot ? startRoot->shadowAncestorNode() : 0; if (p.isNull() && startRoot && (shadowAncestor != startRoot)) - p = Position(shadowAncestor, 0); + p = positionBeforeNode(shadowAncestor); while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; p = isAtomicNode(p.node()) ? positionInParentAfterNode(p.node()) : nextVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) - p = Position(shadowAncestor, 0); + p = positionBeforeNode(shadowAncestor); } VisiblePosition next(p); diff --git a/Source/WebCore/editing/htmlediting.cpp b/Source/WebCore/editing/htmlediting.cpp index 90db3ef..cb157d2 100644 --- a/Source/WebCore/editing/htmlediting.cpp +++ b/Source/WebCore/editing/htmlediting.cpp @@ -26,7 +26,6 @@ #include "config.h" #include "htmlediting.h" -#include "CharacterNames.h" #include "Document.h" #include "EditingText.h" #include "HTMLBRElement.h" @@ -46,6 +45,7 @@ #include "VisiblePosition.h" #include "visible_units.h" #include <wtf/StdLibExtras.h> +#include <wtf/unicode/CharacterNames.h> #if ENABLE(WML) #include "WMLNames.h" @@ -356,25 +356,27 @@ int lastOffsetForEditing(const Node* node) String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph) { - DEFINE_STATIC_LOCAL(String, twoSpaces, (" ")); - DEFINE_STATIC_LOCAL(String, nbsp, ("\xa0")); - DEFINE_STATIC_LOCAL(String, pattern, (" \xa0")); + Vector<UChar> rebalancedString; + append(rebalancedString, string); - String rebalancedString = string; + bool previousCharacterWasSpace = false; + for (size_t i = 0; i < rebalancedString.size(); i++) { + if (!isWhitespace(rebalancedString[i])) { + previousCharacterWasSpace = false; + continue; + } - rebalancedString.replace(noBreakSpace, ' '); - rebalancedString.replace('\n', ' '); - rebalancedString.replace('\t', ' '); - - rebalancedString.replace(twoSpaces, pattern); - - if (startIsStartOfParagraph && rebalancedString[0] == ' ') - rebalancedString.replace(0, 1, nbsp); - int end = rebalancedString.length() - 1; - if (endIsEndOfParagraph && rebalancedString[end] == ' ') - rebalancedString.replace(end, 1, nbsp); + if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i + 1 == rebalancedString.size() && endIsEndOfParagraph)) { + rebalancedString[i] = noBreakSpace; + previousCharacterWasSpace = false; + } else { + rebalancedString[i] = ' '; + previousCharacterWasSpace = true; + } + + } - return rebalancedString; + return String::adopt(rebalancedString); } bool isTableStructureNode(const Node *node) @@ -660,7 +662,7 @@ HTMLElement* enclosingList(Node* node) for (ContainerNode* n = node->parentNode(); n; n = n->parentNode()) { if (n->hasTagName(ulTag) || n->hasTagName(olTag)) - return static_cast<HTMLElement*>(n); + return toHTMLElement(n); if (n == root) return 0; } @@ -668,7 +670,7 @@ HTMLElement* enclosingList(Node* node) return 0; } -HTMLElement* enclosingListChild(Node *node) +Node* enclosingListChild(Node *node) { if (!node) return 0; @@ -679,7 +681,7 @@ HTMLElement* enclosingListChild(Node *node) // FIXME: This function is inappropriately named if it starts with node instead of node->parentNode() for (Node* n = node; n && n->parentNode(); n = n->parentNode()) { if (n->hasTagName(liTag) || isListElement(n->parentNode())) - return static_cast<HTMLElement*>(n); + return n; if (n == root || isTableCell(n)) return 0; } @@ -692,7 +694,7 @@ static HTMLElement* embeddedSublist(Node* listItem) // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->firstChild(); n; n = n->nextSibling()) { if (isListElement(n)) - return static_cast<HTMLElement*>(n); + return toHTMLElement(n); } return 0; @@ -703,7 +705,7 @@ static Node* appendedSublist(Node* listItem) // Check the DOM so that we'll find collapsed sublists without renderers. for (Node* n = listItem->nextSibling(); n; n = n->nextSibling()) { if (isListElement(n)) - return static_cast<HTMLElement*>(n); + return toHTMLElement(n); if (isListItem(listItem)) return 0; } diff --git a/Source/WebCore/editing/htmlediting.h b/Source/WebCore/editing/htmlediting.h index 0208dfb..b71e879 100644 --- a/Source/WebCore/editing/htmlediting.h +++ b/Source/WebCore/editing/htmlediting.h @@ -26,10 +26,11 @@ #ifndef htmlediting_h #define htmlediting_h -#include <wtf/Forward.h> -#include "HTMLNames.h" #include "ExceptionCode.h" +#include "HTMLNames.h" #include "Position.h" +#include <wtf/Forward.h> +#include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -202,7 +203,7 @@ PassRefPtr<HTMLElement> createHTMLElement(Document*, const AtomicString&); HTMLElement* enclosingList(Node*); HTMLElement* outermostEnclosingList(Node*, Node* rootList = 0); -HTMLElement* enclosingListChild(Node*); +Node* enclosingListChild(Node*); // ------------------------------------------------------------------------- // Element @@ -231,8 +232,11 @@ VisibleSelection avoidIntersectionWithNode(const VisibleSelection&, Node*); VisibleSelection selectionForParagraphIteration(const VisibleSelection&); -// Miscellaneous functions on String - +// Miscellaneous functions on Text +inline bool isWhitespace(UChar c) +{ + return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t'; +} String stringWithRebalancedWhitespace(const String&, bool, bool); const String& nonBreakingSpaceString(); diff --git a/Source/WebCore/editing/mac/SelectionControllerMac.mm b/Source/WebCore/editing/mac/SelectionControllerMac.mm index 730eb60..119d406 100644 --- a/Source/WebCore/editing/mac/SelectionControllerMac.mm +++ b/Source/WebCore/editing/mac/SelectionControllerMac.mm @@ -33,6 +33,19 @@ namespace WebCore { +static CGRect accessibilityConvertScreenRect(CGRect bounds) +{ + NSArray *screens = [NSScreen screens]; + if ([screens count]) { + CGFloat screenHeight = NSHeight([[screens objectAtIndex:0] frame]); + bounds.origin.y = (screenHeight - (bounds.origin.y + bounds.size.height)); + } else + bounds = CGRectZero; + + return bounds; +} + + void SelectionController::notifyAccessibilityForSelectionChange() { Document* document = m_frame->document(); @@ -58,8 +71,8 @@ void SelectionController::notifyAccessibilityForSelectionChange() viewRect = frameView->contentsToScreen(viewRect); CGRect cgCaretRect = CGRectMake(selectionRect.x(), selectionRect.y(), selectionRect.width(), selectionRect.height()); CGRect cgViewRect = CGRectMake(viewRect.x(), viewRect.y(), viewRect.width(), viewRect.height()); - cgCaretRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgCaretRect]; - cgViewRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgViewRect]; + cgCaretRect = accessibilityConvertScreenRect(cgCaretRect); + cgViewRect = accessibilityConvertScreenRect(cgViewRect); UAZoomChangeFocus(&cgViewRect, &cgCaretRect, kUAZoomFocusTypeInsertionPoint); } diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp index 4cbdcce..34c3ec7 100644 --- a/Source/WebCore/editing/markup.cpp +++ b/Source/WebCore/editing/markup.cpp @@ -27,7 +27,6 @@ #include "markup.h" #include "CDATASection.h" -#include "CharacterNames.h" #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPrimitiveValue.h" @@ -56,6 +55,7 @@ #include "htmlediting.h" #include "visible_units.h" #include <wtf/StdLibExtras.h> +#include <wtf/unicode/CharacterNames.h> using namespace std; @@ -124,7 +124,7 @@ public: } Node* serializeNodes(Node* startNode, Node* pastEnd); - void appendString(const String& s) { return MarkupAccumulator::appendString(s); } + virtual void appendString(const String& s) { return MarkupAccumulator::appendString(s); } void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false); String takeResults(); @@ -184,7 +184,8 @@ String StyledMarkupAccumulator::takeResults() concatenateMarkup(result); - return String::adopt(result); + // We remove '\0' characters because they are not visibly rendered to the user. + return String::adopt(result).replace(0, ""); } void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text) @@ -194,7 +195,7 @@ void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text) return; } - bool useRenderedText = !enclosingNodeWithTag(Position(text, 0), selectTag); + bool useRenderedText = !enclosingNodeWithTag(firstPositionInNode(text), selectTag); String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range); Vector<UChar> buffer; appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA); @@ -267,7 +268,7 @@ void StyledMarkupAccumulator::appendElement(Vector<UChar>& out, Element* element } if (element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) { - RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy(); + RefPtr<CSSMutableStyleDeclaration> style = toHTMLElement(element)->getInlineStyleDecl()->copy(); if (shouldAnnotate()) { RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(element)); // Styles from the inline style declaration, held in the variable "style", take precedence @@ -337,7 +338,7 @@ Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) // Don't write out empty block containers that aren't fully selected. continue; - if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) { + if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) { next = n->traverseNextSibling(); // Don't skip over pastEnd. if (pastEnd && pastEnd->isDescendantOf(n)) @@ -495,7 +496,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelected Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; if (checkAncestor->renderer()) { - Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(Position(checkAncestor, 0), &isElementPresentational); + Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(firstPositionInNode(checkAncestor), &isElementPresentational); if (newSpecialCommonAncestor) specialCommonAncestor = newSpecialCommonAncestor; } @@ -509,7 +510,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelected if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) specialCommonAncestor = commonAncestor; - if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag)) + if (Node *enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag)) specialCommonAncestor = enclosingAnchor; if (shouldAnnotate == AnnotateForInterchange && fullySelectedRoot) { @@ -579,7 +580,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc } } - Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag); + Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag); Node* fullySelectedRoot = 0; // FIXME: Do this for all fully selected blocks, not just the body. if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), range)) diff --git a/Source/WebCore/editing/visible_units.cpp b/Source/WebCore/editing/visible_units.cpp index 3582aa9..391d6e6 100644 --- a/Source/WebCore/editing/visible_units.cpp +++ b/Source/WebCore/editing/visible_units.cpp @@ -292,7 +292,7 @@ static unsigned previousWordPositionBoundary(const UChar* characters, unsigned l VisiblePosition previousWordPosition(const VisiblePosition &c) { VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary); - return c.honorEditableBoundaryAtOrAfter(prev); + return c.honorEditableBoundaryAtOrBefore(prev); } static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) @@ -308,7 +308,7 @@ static unsigned nextWordPositionBoundary(const UChar* characters, unsigned lengt VisiblePosition nextWordPosition(const VisiblePosition &c) { VisiblePosition next = nextBoundary(c, nextWordPositionBoundary); - return c.honorEditableBoundaryAtOrBefore(next); + return c.honorEditableBoundaryAtOrAfter(next); } // --------- @@ -391,7 +391,7 @@ VisiblePosition startOfLine(const VisiblePosition& c) { VisiblePosition visPos = startPositionForLine(c); - return c.honorEditableBoundaryAtOrAfter(visPos); + return c.honorEditableBoundaryAtOrBefore(visPos); } static VisiblePosition endPositionForLine(const VisiblePosition& c) @@ -458,7 +458,7 @@ VisiblePosition endOfLine(const VisiblePosition& c) visPos = endPositionForLine(visPos); } - return c.honorEditableBoundaryAtOrBefore(visPos); + return c.honorEditableBoundaryAtOrAfter(visPos); } bool inSameLine(const VisiblePosition &a, const VisiblePosition &b) @@ -536,7 +536,7 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = previousLeafWithSameEditability(n); while (n) { - if (highestEditableRoot(Position(n, 0)) != highestRoot) + if (highestEditableRoot(firstPositionInOrBeforeNode(n)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { @@ -567,7 +567,7 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int RenderObject* renderer = root->closestLeafChildForLogicalLeftPosition(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) - return Position(node->parentNode(), node->nodeIndex()); + return positionInParentBeforeNode(node); return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop())); } @@ -583,7 +583,7 @@ static Node* nextLeafWithSameEditability(Node* node, int offset) bool editable = node->isContentEditable(); ASSERT(offset >= 0); Node* child = node->childNode(offset); - Node* n = child ? child->nextLeafNode() : node->nextLeafNode(); + Node* n = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode(); while (n) { if (editable == n->isContentEditable()) return n; @@ -645,7 +645,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = nextLeafWithSameEditability(n); while (n) { - if (highestEditableRoot(Position(n, 0)) != highestRoot) + if (highestEditableRoot(firstPositionInOrBeforeNode(n)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { @@ -672,7 +672,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) RenderObject* renderer = root->closestLeafChildForLogicalLeftPosition(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) - return Position(node->parentNode(), node->nodeIndex()); + return positionInParentBeforeNode(node); return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop())); } @@ -720,7 +720,7 @@ static unsigned previousSentencePositionBoundary(const UChar* characters, unsign VisiblePosition previousSentencePosition(const VisiblePosition &c) { VisiblePosition prev = previousBoundary(c, previousSentencePositionBoundary); - return c.honorEditableBoundaryAtOrAfter(prev); + return c.honorEditableBoundaryAtOrBefore(prev); } static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) @@ -734,7 +734,7 @@ static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned l VisiblePosition nextSentencePosition(const VisiblePosition &c) { VisiblePosition next = nextBoundary(c, nextSentencePositionBoundary); - return c.honorEditableBoundaryAtOrBefore(next); + return c.honorEditableBoundaryAtOrAfter(next); } VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) @@ -913,7 +913,7 @@ VisiblePosition startOfBlock(const VisiblePosition &c) Node *startNode = p.node(); if (!startNode) return VisiblePosition(); - return VisiblePosition(Position(startNode->enclosingBlockFlowElement(), 0), DOWNSTREAM); + return VisiblePosition(firstPositionInNode(startNode->enclosingBlockFlowElement()), DOWNSTREAM); } VisiblePosition endOfBlock(const VisiblePosition &c) @@ -1133,7 +1133,7 @@ VisiblePosition logicalStartOfLine(const VisiblePosition& c) // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. VisiblePosition visPos = logicalStartPositionForLine(c); - return c.honorEditableBoundaryAtOrAfter(visPos); + return c.honorEditableBoundaryAtOrBefore(visPos); } static VisiblePosition logicalEndPositionForLine(const VisiblePosition& c) @@ -1190,7 +1190,7 @@ VisiblePosition logicalEndOfLine(const VisiblePosition& c) if (!inSameLogicalLine(c, visPos)) visPos = visPos.previous(); - return c.honorEditableBoundaryAtOrBefore(visPos); + return c.honorEditableBoundaryAtOrAfter(visPos); } VisiblePosition leftBoundaryOfLine(const VisiblePosition& c, TextDirection direction) |