diff options
Diffstat (limited to 'WebCore/editing')
24 files changed, 738 insertions, 427 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index 8862da7..4e1c733 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -36,7 +36,6 @@ #include "Document.h" #include "Editor.h" #include "Frame.h" -#include "HTMLElement.h" #include "HTMLFontElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" @@ -430,104 +429,6 @@ RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* style return result; } -// Editing style properties must be preserved during editing operation. -// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. -// FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties -static const int editingStyleProperties[] = { - // CSS inheritable properties - CSSPropertyBorderCollapse, - CSSPropertyColor, - CSSPropertyFontFamily, - CSSPropertyFontSize, - CSSPropertyFontStyle, - CSSPropertyFontVariant, - CSSPropertyFontWeight, - CSSPropertyLetterSpacing, - CSSPropertyLineHeight, - CSSPropertyOrphans, - CSSPropertyTextAlign, - CSSPropertyTextIndent, - CSSPropertyTextTransform, - CSSPropertyWhiteSpace, - CSSPropertyWidows, - CSSPropertyWordSpacing, - CSSPropertyWebkitBorderHorizontalSpacing, - CSSPropertyWebkitBorderVerticalSpacing, - CSSPropertyWebkitTextDecorationsInEffect, - CSSPropertyWebkitTextFillColor, - CSSPropertyWebkitTextSizeAdjust, - CSSPropertyWebkitTextStrokeColor, - CSSPropertyWebkitTextStrokeWidth, -}; -size_t numEditingStyleProperties = sizeof(editingStyleProperties)/sizeof(editingStyleProperties[0]); - -RefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::removeNonEditingProperties(CSSStyleDeclaration* style) -{ - return style->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties); -} - -PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle) -{ - RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = pos.computedStyle(); - RefPtr<CSSMutableStyleDeclaration> style; - if (!computedStyleAtPosition) - style = CSSMutableStyleDeclaration::create(); - else - style = removeNonEditingProperties(computedStyleAtPosition.get()); - - if (style && pos.node() && pos.node()->computedStyle()) { - RenderStyle* renderStyle = pos.node()->computedStyle(); - // If a node's text fill color is invalid, then its children use - // their font-color as their text fill color (they don't - // inherit it). Likewise for stroke color. - ExceptionCode ec = 0; - if (!renderStyle->textFillColor().isValid()) - style->removeProperty(CSSPropertyWebkitTextFillColor, ec); - if (!renderStyle->textStrokeColor().isValid()) - style->removeProperty(CSSPropertyWebkitTextStrokeColor, ec); - ASSERT(ec == 0); - if (renderStyle->fontDescription().keywordSize()) - style->setProperty(CSSPropertyFontSize, computedStyleAtPosition->getFontSizeCSSValuePreferringKeyword()->cssText()); - } - - if (shouldIncludeTypingStyle == IncludeTypingStyle) { - CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->selection()->typingStyle(); - if (typingStyle) - style->merge(typingStyle); - } - - return style.release(); -} - -void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration* editingStyle, Position pos) -{ - // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style. - // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate - // which one of editingStyleAtPosition or computedStyle is called. - RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(pos); - style->diff(editingStyle); - - // if alpha value is zero, we don't add the background color. - RefPtr<CSSValue> backgroundColor = editingStyle->getPropertyCSSValue(CSSPropertyBackgroundColor); - if (backgroundColor && backgroundColor->isPrimitiveValue()) { - CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(backgroundColor.get()); - Color color = Color(primitiveValue->getRGBA32Value()); - ExceptionCode ec; - if (color.alpha() == 0) - editingStyle->removeProperty(CSSPropertyBackgroundColor, ec); - } -} - -void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* node) -{ - ASSERT(node); - ASSERT(node->parentNode()); - RefPtr<CSSMutableStyleDeclaration> parentStyle = ApplyStyleCommand::editingStyleAtPosition(Position(node->parentNode(), 0)); - RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(Position(node, 0)); - parentStyle->diff(style.get()); - style->diff(editingStyle); -} - ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel) : CompositeEditCommand(document) , m_style(style->makeMutable()) @@ -1046,6 +947,8 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end); start = startPosition(); end = endPosition(); + if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan()) + return; if (splitStart) { if (mergeStartWithPreviousIfIdentical(start, end)) { @@ -1150,9 +1053,9 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* if (m_removeOnly) return; - for (Node* next; node && node != pastEndNode; node = next) { + for (RefPtr<Node> next; node && node != pastEndNode; node = next.get()) { next = node->traverseNextNode(); - + if (!node->renderer() || !node->isContentEditable()) continue; @@ -1183,7 +1086,8 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* } } - Node* runEnd = node; + RefPtr<Node> runStart = node; + RefPtr<Node> runEnd = node; Node* sibling = node->nextSibling(); while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode) && (!isBlock(sibling) || sibling->hasTagName(brTag)) @@ -1193,9 +1097,9 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* } next = runEnd->traverseNextSibling(); - if (!removeStyleFromRunBeforeApplyingStyle(style, node, runEnd)) + if (!removeStyleFromRunBeforeApplyingStyle(style, runStart, runEnd)) continue; - addInlineStyleIfNeeded(style, node, runEnd, AddStyledElement); + addInlineStyleIfNeeded(style, runStart.get(), runEnd.get(), AddStyledElement); } } @@ -1205,12 +1109,12 @@ bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element)); } -bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd) +bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd) { ASSERT(runStart && runEnd && runStart->parentNode() == runEnd->parentNode()); - Node* pastEndNode = runEnd->traverseNextSibling(); + RefPtr<Node> pastEndNode = runEnd->traverseNextSibling(); bool needToApplyStyle = false; - for (Node* node = runStart; node && node != pastEndNode; node = node->traverseNextNode()) { + for (Node* node = runStart.get(); node && node != pastEndNode.get(); node = node->traverseNextNode()) { if (node->childNodeCount()) continue; // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified @@ -1223,16 +1127,16 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec if (!needToApplyStyle) return false; - Node* next; - for (Node* node = runStart; node && node != pastEndNode; node = next) { + RefPtr<Node> next = runStart; + for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) { next = node->traverseNextNode(); if (!node->isHTMLElement()) continue; - - Node* previousSibling = node->previousSibling(); - Node* nextSibling = node->nextSibling(); - ContainerNode* parent = node->parentNode(); - removeInlineStyleFromElement(style, static_cast<HTMLElement*>(node), RemoveAlways); + + RefPtr<Node> previousSibling = node->previousSibling(); + RefPtr<Node> nextSibling = node->nextSibling(); + RefPtr<ContainerNode> parent = node->parentNode(); + removeInlineStyleFromElement(style, static_cast<HTMLElement*>(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) @@ -1245,7 +1149,7 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec return true; } -bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element, InlineStyleRemovalMode mode, CSSMutableStyleDeclaration* extractedStyle) +bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, CSSMutableStyleDeclaration* extractedStyle) { ASSERT(style); ASSERT(element); @@ -1253,7 +1157,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* if (!element->parentNode() || !element->parentNode()->isContentEditable()) return false; - if (isStyledInlineElementToRemove(element)) { + if (isStyledInlineElementToRemove(element.get())) { if (mode == RemoveNone) return true; ASSERT(extractedStyle); @@ -1264,7 +1168,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* } bool removed = false; - if (removeImplicitlyStyledElement(style, element, mode, extractedStyle)) + if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) removed = true; if (!element->inDocument()) @@ -1272,7 +1176,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* // If the node was converted to a span, the span may still contain relevant // styles which must be removed (e.g. <b style='font-weight: bold'>) - if (removeCSSStyle(style, element, mode, extractedStyle)) + if (removeCSSStyle(style, element.get(), mode, extractedStyle)) removed = true; return removed; @@ -1658,9 +1562,7 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> break; node = next.get(); } - - ASSERT(s.node()->inDocument()); - ASSERT(e.node()->inDocument()); + updateStartEnd(s, e); } @@ -1859,18 +1761,19 @@ bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const return false; } -void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endNode, PassRefPtr<Element> elementToInsert) +void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert) { - ASSERT(startNode); + ASSERT(passedStartNode); ASSERT(endNode); ASSERT(elementToInsert); + RefPtr<Node> startNode = passedStartNode; RefPtr<Element> element = elementToInsert; insertNodeBefore(element, startNode); - - Node* node = startNode; - while (1) { - Node* next = node->nextSibling(); + + RefPtr<Node> node = startNode; + while (node) { + RefPtr<Node> next = node->nextSibling(); removeNode(node); appendNode(node, element); if (node == endNode) @@ -1878,17 +1781,17 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endN node = next; } - Node* nextSibling = element->nextSibling(); - Node* previousSibling = element->previousSibling(); + RefPtr<Node> nextSibling = element->nextSibling(); + RefPtr<Node> previousSibling = element->previousSibling(); if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable() - && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling))) - mergeIdenticalElements(element, static_cast<Element*>(nextSibling)); + && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get()))) + mergeIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get())); if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) { Node* mergedElement = previousSibling->nextSibling(); if (mergedElement->isElementNode() && mergedElement->isContentEditable() - && areIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement))) - mergeIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement)); + && areIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement))) + mergeIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement)); } // FIXME: We should probably call updateStartEnd if the start or end was in the node @@ -1910,17 +1813,22 @@ void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen setNodeAttribute(block, styleAttr, cssText); } -void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode, EAddStyledElement addStyledElement) +void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement) { + if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument()) + return; + RefPtr<Node> startNode = passedStart; + RefPtr<Node> endNode = passedEnd; + // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run. RefPtr<HTMLElement> dummyElement; Position positionForStyleComparison; if (!startNode->isElementNode()) { dummyElement = createStyleSpanElement(document()); - insertNodeAt(dummyElement, positionBeforeNode(startNode)); + insertNodeAt(dummyElement, positionBeforeNode(startNode.get())); positionForStyleComparison = positionBeforeNode(dummyElement.get()); } else - positionForStyleComparison = firstPositionInNode(startNode); + positionForStyleComparison = firstPositionInNode(startNode.get()); StyleChange styleChange(style, positionForStyleComparison); @@ -1930,7 +1838,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style // Find appropriate font and span elements top-down. HTMLElement* fontContainer = 0; HTMLElement* styleContainer = 0; - for (Node* container = startNode; container && startNode == endNode; container = container->firstChild()) { + for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) { if (container->isHTMLElement() && container->hasTagName(fontTag)) fontContainer = static_cast<HTMLElement*>(container); bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag); diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h index 16c5b68..018148f 100644 --- a/WebCore/editing/ApplyStyleCommand.h +++ b/WebCore/editing/ApplyStyleCommand.h @@ -27,6 +27,7 @@ #define ApplyStyleCommand_h #include "CompositeEditCommand.h" +#include "HTMLElement.h" namespace WebCore { @@ -63,9 +64,6 @@ public: return adoptRef(new ApplyStyleCommand(document, style, isInlineElementToRemoveFunction, action)); } - static RefPtr<CSSMutableStyleDeclaration> removeNonEditingProperties(CSSStyleDeclaration* style); - static PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle = IgnoreTypingStyle); - private: ApplyStyleCommand(Document*, CSSStyleDeclaration*, EditAction, EPropertyLevel); ApplyStyleCommand(Document*, CSSStyleDeclaration*, const Position& start, const Position& end, EditAction, EPropertyLevel); @@ -79,8 +77,8 @@ private: // style-removal helpers bool isStyledInlineElementToRemove(Element*) const; - bool removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd); - bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded, CSSMutableStyleDeclaration* extractedStyle = 0); + bool removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd); + bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, PassRefPtr<HTMLElement>, InlineStyleRemovalMode = RemoveIfNeeded, CSSMutableStyleDeclaration* extractedStyle = 0); inline bool shouldRemoveInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element) {return removeInlineStyleFromElement(style, element, RemoveNone);} bool removeImplicitlyStyledElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode, CSSMutableStyleDeclaration* extractedStyle); void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&); @@ -99,7 +97,7 @@ private: void fixRangeAndApplyInlineStyle(CSSMutableStyleDeclaration*, const Position& start, const Position& end); void applyInlineStyleToNodeRange(CSSMutableStyleDeclaration*, Node* startNode, Node* pastEndNode); void addBlockStyle(const StyleChange&, HTMLElement*); - void addInlineStyleIfNeeded(CSSMutableStyleDeclaration*, Node* start, Node* end, EAddStyledElement addStyledElement = AddStyledElement); + void addInlineStyleIfNeeded(CSSMutableStyleDeclaration*, PassRefPtr<Node> start, PassRefPtr<Node> end, EAddStyledElement addStyledElement = AddStyledElement); void splitTextAtStart(const Position& start, const Position& end); void splitTextAtEnd(const Position& start, const Position& end); void splitTextElementAtStart(const Position& start, const Position& end); @@ -110,7 +108,7 @@ private: bool mergeEndWithNextIfIdentical(const Position& start, const Position& end); void cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor); - void surroundNodeRangeWithElement(Node* start, Node* end, PassRefPtr<Element>); + void surroundNodeRangeWithElement(PassRefPtr<Node> start, PassRefPtr<Node> end, PassRefPtr<Element>); float computedFontSize(const Node*); void joinChildTextNodes(Node*, const Position& start, const Position& end); @@ -136,9 +134,6 @@ bool isStyleSpan(const Node*); PassRefPtr<HTMLElement> createStyleSpanElement(Document*); RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle); -void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration*, Position); -void removeStylesAddedByNode(CSSMutableStyleDeclaration*, Node*); - } // namespace WebCore #endif diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 6f47fb4..5579b25 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -206,6 +206,8 @@ void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned void CompositeEditCommand::removeNode(PassRefPtr<Node> node) { + if (!node || !node->parentNode()) + return; applyCommandToComposite(RemoveNodeCommand::create(node)); } @@ -935,9 +937,9 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap // A non-empty paragraph's style is moved when we copy and move it. We don't move // anything if we're given an empty paragraph, but an empty paragraph can have style // too, <div><b><br></b></div> for example. Save it so that we can preserve it later. - RefPtr<CSSMutableStyleDeclaration> styleInEmptyParagraph; + RefPtr<EditingStyle> styleInEmptyParagraph; if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) { - styleInEmptyParagraph = ApplyStyleCommand::editingStyleAtPosition(startOfParagraphToMove.deepEquivalent(), IncludeTypingStyle); + styleInEmptyParagraph = editingStyleIncludingTypingStyle(startOfParagraphToMove.deepEquivalent()); // The moved paragraph should assume the block style of the destination. styleInEmptyParagraph->removeBlockProperties(); } @@ -981,8 +983,8 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph. bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart()); if (styleInEmptyParagraph && selectionIsEmptyParagraph) - applyStyle(styleInEmptyParagraph.get()); - + applyStyle(styleInEmptyParagraph->style()); + if (preserveSelection && startIndex != -1) { // Fragment creation (using createMarkup) incorrectly uses regular // spaces instead of nbsps for some spaces that were rendered (11475), which @@ -1003,7 +1005,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() if (!emptyListItem) return false; - RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(endingSelection().start(), IncludeTypingStyle); + RefPtr<EditingStyle> style = editingStyleIncludingTypingStyle(endingSelection().start()); ContainerNode* listNode = emptyListItem->parentNode(); // FIXME: Can't we do something better when the immediate parent wasn't a list node? @@ -1052,10 +1054,10 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() appendBlockPlaceholder(newBlock); setEndingSelection(VisibleSelection(Position(newBlock.get(), 0), DOWNSTREAM)); - prepareEditingStyleToApplyAt(style.get(), endingSelection().start()); - if (style->length()) - applyStyle(style.get()); - + style->prepareToApplyAt(endingSelection().start()); + if (!style->isEmpty()) + applyStyle(style->style()); + return true; } diff --git a/WebCore/editing/CorrectionPanelInfo.h b/WebCore/editing/CorrectionPanelInfo.h new file mode 100644 index 0000000..2caac17 --- /dev/null +++ b/WebCore/editing/CorrectionPanelInfo.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CorrectionPanelInfo_h +#define CorrectionPanelInfo_h + +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +// Some platforms provide UI for suggesting autocorrection. +#define SUPPORT_AUTOCORRECTION_PANEL 1 +// Some platforms use spelling and autocorrection markers to provide visual cue. +// On such platform, if word with marker is edited, we need to remove the marker. +#define REMOVE_MARKERS_UPON_EDITING 1 +#else +#define SUPPORT_AUTOCORRECTION_PANEL 0 +#define REMOVE_MARKERS_UPON_EDITING 0 +#endif // #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) + +#include "Range.h" + +namespace WebCore { + +struct CorrectionPanelInfo { + enum PanelType { + PanelTypeCorrection = 0, + PanelTypeReversion + }; + + RefPtr<Range> m_rangeToBeReplaced; + String m_replacedString; + String m_replacementString; + PanelType m_panelType; + bool m_isActive; +}; + +enum CorrectionWasRejectedOrNot { CorrectionWasNotRejected, CorrectionWasRejected }; + +} // namespace WebCore + +#endif // CorrectionPanelInfo_h diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp index 1f56da7..56deac3 100644 --- a/WebCore/editing/DeleteSelectionCommand.cpp +++ b/WebCore/editing/DeleteSelectionCommand.cpp @@ -26,25 +26,18 @@ #include "config.h" #include "DeleteSelectionCommand.h" -#include "CSSMutableStyleDeclaration.h" #include "Document.h" #include "DocumentFragment.h" #include "Editor.h" #include "EditorClient.h" #include "Element.h" #include "Frame.h" -#include "Logging.h" -#include "CSSComputedStyleDeclaration.h" #include "htmlediting.h" #include "HTMLInputElement.h" #include "HTMLNames.h" -#include "markup.h" #include "RenderTableCell.h" -#include "ReplaceSelectionCommand.h" #include "Text.h" -#include "TextIterator.h" #include "visible_units.h" -#include "ApplyStyleCommand.h" namespace WebCore { @@ -272,15 +265,6 @@ void DeleteSelectionCommand::initializePositionData() m_endBlock = enclosingNodeOfType(rangeCompliantEquivalent(m_upstreamEnd), &isBlock, false); } -static void removeEnclosingAnchorStyle(CSSMutableStyleDeclaration* style, const Position& position) -{ - Node* enclosingAnchor = enclosingAnchorElement(position); - if (!enclosingAnchor || !enclosingAnchor->parentNode()) - return; - - removeStylesAddedByNode(style, enclosingAnchor); -} - void DeleteSelectionCommand::saveTypingStyleState() { // A common case is deleting characters that are all from the same text node. In @@ -294,14 +278,13 @@ void DeleteSelectionCommand::saveTypingStyleState() return; // Figure out the typing style in effect before the delete is done. - m_typingStyle = ApplyStyleCommand::editingStyleAtPosition(positionBeforeTabSpan(m_selectionToDelete.start())); - - removeEnclosingAnchorStyle(m_typingStyle.get(), m_selectionToDelete.start()); + m_typingStyle = EditingStyle::create(positionBeforeTabSpan(m_selectionToDelete.start())); + m_typingStyle->removeStyleAddedByNode(enclosingAnchorElement(m_selectionToDelete.start())); // If we're deleting into a Mail blockquote, save the style at end() instead of start() // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote if (nearestMailBlockquote(m_selectionToDelete.start().node())) - m_deleteIntoBlockquoteStyle = ApplyStyleCommand::editingStyleAtPosition(m_selectionToDelete.end()); + m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end()); else m_deleteIntoBlockquoteStyle = 0; } @@ -693,8 +676,8 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() m_typingStyle = m_deleteIntoBlockquoteStyle; m_deleteIntoBlockquoteStyle = 0; - prepareEditingStyleToApplyAt(m_typingStyle.get(), m_endingPosition); - if (!m_typingStyle->length()) + m_typingStyle->prepareToApplyAt(m_endingPosition); + if (m_typingStyle->isEmpty()) m_typingStyle = 0; VisiblePosition visibleEnd(m_endingPosition); if (m_typingStyle && @@ -707,7 +690,7 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() // then move it back (which will clear typing style). setEndingSelection(visibleEnd); - applyStyle(m_typingStyle.get(), EditActionUnspecified); + applyStyle(m_typingStyle->style(), EditActionUnspecified); // applyStyle can destroy the placeholder that was at m_endingPosition if it needs to // move it, but it will set an endingSelection() at [movedPlaceholder, 0] if it does so. m_endingPosition = endingSelection().start(); diff --git a/WebCore/editing/DeleteSelectionCommand.h b/WebCore/editing/DeleteSelectionCommand.h index 20f52f4..7b46935 100644 --- a/WebCore/editing/DeleteSelectionCommand.h +++ b/WebCore/editing/DeleteSelectionCommand.h @@ -30,6 +30,8 @@ namespace WebCore { +class EditingStyle; + class DeleteSelectionCommand : public CompositeEditCommand { public: static PassRefPtr<DeleteSelectionCommand> create(Document* document, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false) @@ -86,8 +88,8 @@ private: Position m_trailingWhitespace; RefPtr<Node> m_startBlock; RefPtr<Node> m_endBlock; - RefPtr<CSSMutableStyleDeclaration> m_typingStyle; - RefPtr<CSSMutableStyleDeclaration> m_deleteIntoBlockquoteStyle; + RefPtr<EditingStyle> m_typingStyle; + RefPtr<EditingStyle> m_deleteIntoBlockquoteStyle; RefPtr<Node> m_startRoot; RefPtr<Node> m_endRoot; RefPtr<Node> m_startTableRow; diff --git a/WebCore/editing/EditingAllInOne.cpp b/WebCore/editing/EditingAllInOne.cpp index 81483f8..e93840a 100644 --- a/WebCore/editing/EditingAllInOne.cpp +++ b/WebCore/editing/EditingAllInOne.cpp @@ -36,6 +36,7 @@ #include <DeleteFromTextNodeCommand.cpp> #include <DeleteSelectionCommand.cpp> #include <EditCommand.cpp> +#include <EditingStyle.cpp> #include <Editor.cpp> #include <EditorCommand.cpp> #include <FormatBlockCommand.cpp> diff --git a/WebCore/editing/EditingBehaviorTypes.h b/WebCore/editing/EditingBehaviorTypes.h index 26ba18e..11345da 100644 --- a/WebCore/editing/EditingBehaviorTypes.h +++ b/WebCore/editing/EditingBehaviorTypes.h @@ -38,7 +38,8 @@ namespace WebCore { // if possible in the future. enum EditingBehaviorType { EditingMacBehavior, - EditingWindowsBehavior + EditingWindowsBehavior, + EditingUnixBehavior }; } // WebCore namespace diff --git a/WebCore/editing/EditingStyle.cpp b/WebCore/editing/EditingStyle.cpp new file mode 100644 index 0000000..9da337f --- /dev/null +++ b/WebCore/editing/EditingStyle.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "EditingStyle.h" + +#include "ApplyStyleCommand.h" +#include "CSSComputedStyleDeclaration.h" +#include "CSSMutableStyleDeclaration.h" +#include "Frame.h" +#include "RenderStyle.h" +#include "SelectionController.h" + +namespace WebCore { + +// Editing style properties must be preserved during editing operation. +// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. +// FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties +static const int editingStyleProperties[] = { + // CSS inheritable properties + CSSPropertyBorderCollapse, + CSSPropertyColor, + CSSPropertyFontFamily, + CSSPropertyFontSize, + CSSPropertyFontStyle, + CSSPropertyFontVariant, + CSSPropertyFontWeight, + CSSPropertyLetterSpacing, + CSSPropertyLineHeight, + CSSPropertyOrphans, + CSSPropertyTextAlign, + CSSPropertyTextIndent, + CSSPropertyTextTransform, + CSSPropertyWhiteSpace, + CSSPropertyWidows, + CSSPropertyWordSpacing, + CSSPropertyWebkitBorderHorizontalSpacing, + CSSPropertyWebkitBorderVerticalSpacing, + CSSPropertyWebkitTextDecorationsInEffect, + CSSPropertyWebkitTextFillColor, + CSSPropertyWebkitTextSizeAdjust, + CSSPropertyWebkitTextStrokeColor, + CSSPropertyWebkitTextStrokeWidth, +}; +size_t numEditingStyleProperties = sizeof(editingStyleProperties) / sizeof(editingStyleProperties[0]); + +static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style) +{ + return style->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties); +} + +static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style) +{ + if (!style) + return CSSMutableStyleDeclaration::create(); + return copyEditingProperties(style.get()); +} + +EditingStyle::EditingStyle() + : m_shouldUseFixedDefaultFontSize(false) +{ +} + +EditingStyle::EditingStyle(Node* node) + : m_shouldUseFixedDefaultFontSize(false) +{ + init(node); +} + +EditingStyle::EditingStyle(const Position& position) + : m_shouldUseFixedDefaultFontSize(false) +{ + init(position.node()); +} + +EditingStyle::EditingStyle(const CSSStyleDeclaration* style) + : m_mutableStyle(style->copy()) + , m_shouldUseFixedDefaultFontSize(false) +{ +} + +void EditingStyle::init(Node* node) +{ + RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node); + m_mutableStyle = editingStyleFromComputedStyle(computedStyleAtPosition); + + if (node && node->computedStyle()) { + RenderStyle* renderStyle = node->computedStyle(); + removeTextFillAndStrokeColorsIfNeeded(renderStyle); + replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get()); + } + + m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize(); +} + +void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle) +{ + // If a node's text fill color is invalid, then its children use + // their font-color as their text fill color (they don't + // inherit it). Likewise for stroke color. + ExceptionCode ec = 0; + if (!renderStyle->textFillColor().isValid()) + m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor, ec); + if (!renderStyle->textStrokeColor().isValid()) + m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor, ec); + ASSERT(!ec); +} + +void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle) +{ + ASSERT(renderStyle); + if (renderStyle->fontDescription().keywordSize()) + m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText()); +} + +bool EditingStyle::isEmpty() const +{ + return !m_mutableStyle || m_mutableStyle->isEmpty(); +} + +void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style) +{ + m_mutableStyle = style; + // FIXME: We should be able to figure out whether or not font is fixed width for mutable style. + // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here. + m_shouldUseFixedDefaultFontSize = false; +} + +void EditingStyle::clear() +{ + m_mutableStyle.clear(); + m_shouldUseFixedDefaultFontSize = false; +} + +void EditingStyle::removeBlockProperties() +{ + if (!m_mutableStyle) + return; + + m_mutableStyle->removeBlockProperties(); +} + +void EditingStyle::removeStyleAddedByNode(Node* node) +{ + if (!node || !node->parentNode()) + return; + RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode())); + RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node)); + parentStyle->diff(nodeStyle.get()); + nodeStyle->diff(m_mutableStyle.get()); +} + +void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node) +{ + if (!node || !node->parentNode()) + return; + RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode())); + RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node)); + parentStyle->diff(nodeStyle.get()); + + CSSMutableStyleDeclaration::const_iterator end = nodeStyle->end(); + for (CSSMutableStyleDeclaration::const_iterator it = nodeStyle->begin(); it != end; ++it) + m_mutableStyle->removeProperty(it->id()); +} + +void EditingStyle::removeNonEditingProperties() +{ + if (m_mutableStyle) + m_mutableStyle = copyEditingProperties(m_mutableStyle.get()); +} + +void EditingStyle::prepareToApplyAt(const Position& position) +{ + // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style. + // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate + // which one of editingStyleAtPosition or computedStyle is called. + RefPtr<EditingStyle> style = EditingStyle::create(position); + style->m_mutableStyle->diff(m_mutableStyle.get()); + + // if alpha value is zero, we don't add the background color. + RefPtr<CSSValue> backgroundColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor); + if (backgroundColor && backgroundColor->isPrimitiveValue() + && !alphaChannel(static_cast<CSSPrimitiveValue*>(backgroundColor.get())->getRGBA32Value())) { + ExceptionCode ec; + m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec); + } +} + +PassRefPtr<EditingStyle> editingStyleIncludingTypingStyle(const Position& position) +{ + RefPtr<EditingStyle> editingStyle = EditingStyle::create(position); + RefPtr<CSSMutableStyleDeclaration> typingStyle = position.node()->document()->frame()->selection()->typingStyle(); + if (typingStyle) + editingStyle->style()->merge(copyEditingProperties(typingStyle.get()).get()); + return editingStyle; +} + +} diff --git a/WebCore/editing/EditingStyle.h b/WebCore/editing/EditingStyle.h new file mode 100644 index 0000000..6b4c60c --- /dev/null +++ b/WebCore/editing/EditingStyle.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef EditingStyle_h +#define EditingStyle_h + +#include "CSSMutableStyleDeclaration.h" +#include "Document.h" +#include "Position.h" + +namespace WebCore { + +class CSSStyleDeclaration; +class CSSComputedStyleDeclaration; + +class EditingStyle : public RefCounted<EditingStyle> { +public: + + static PassRefPtr<EditingStyle> create() + { + return adoptRef(new EditingStyle()); + } + + static PassRefPtr<EditingStyle> create(Node* node) + { + return adoptRef(new EditingStyle(node)); + } + + static PassRefPtr<EditingStyle> create(const Position& position) + { + return adoptRef(new EditingStyle(position)); + } + + static PassRefPtr<EditingStyle> create(const CSSStyleDeclaration* style) + { + return adoptRef(new EditingStyle(style)); + } + + CSSMutableStyleDeclaration* style() { return m_mutableStyle.get(); } + bool isEmpty() const; + void setStyle(PassRefPtr<CSSMutableStyleDeclaration>); + void clear(); + void removeBlockProperties(); + void removeStyleAddedByNode(Node* node); + void removeStyleConflictingWithStyleOfNode(Node* node); + void removeNonEditingProperties(); + void prepareToApplyAt(const Position&); + +private: + EditingStyle(); + EditingStyle(Node*); + EditingStyle(const Position&); + EditingStyle(const CSSStyleDeclaration*); + void init(Node*); + void removeTextFillAndStrokeColorsIfNeeded(RenderStyle*); + void replaceFontSizeByKeywordIfPossible(RenderStyle*, CSSComputedStyleDeclaration*); + + RefPtr<CSSMutableStyleDeclaration> m_mutableStyle; + bool m_shouldUseFixedDefaultFontSize; +}; + +PassRefPtr<EditingStyle> editingStyleIncludingTypingStyle(const Position&); + +} // namespace WebCore + +#endif // EditingStyle_h diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index c74d765..327aa5f 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -457,9 +457,53 @@ 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(CorrectionWasNotRejected); + } +#endif // SUPPORT_AUTOCORRECTION_PANEL + if (client()) client()->respondToChangedSelection(); m_deleteButtonController->respondToChangedSelection(oldSelection); + +#if SUPPORT_AUTOCORRECTION_PANEL + // 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 + // the new caret position is at the end of a word. + if (!currentSelection.isCaret() || currentSelection == oldSelection) + return; + + VisiblePosition selectionPosition = currentSelection.start(); + VisiblePosition endPositionOfWord = endOfWord(selectionPosition, LeftWordIfOnBoundary); + if (selectionPosition != endPositionOfWord) + return; + + Position position = endPositionOfWord.deepEquivalent(); + if (position.anchorType() != Position::PositionIsOffsetInAnchor) + return; + + Node* node = position.containerNode(); + int endOffset = position.offsetInContainerNode(); + Vector<DocumentMarker> markers = node->document()->markers()->markersForNode(node); + size_t markerCount = markers.size(); + for (size_t i = 0; i < markerCount; ++i) { + const DocumentMarker& marker = markers[i]; + if (marker.type == DocumentMarker::CorrectionIndicator && static_cast<int>(marker.endOffset) == endOffset) { + RefPtr<Range> wordRange = Range::create(frame()->document(), node, marker.startOffset, node, marker.endOffset); + String currentWord = plainText(wordRange.get()); + if (currentWord.length() > 0 && marker.description.length() > 0) { + m_correctionPanelInfo.m_rangeToBeReplaced = wordRange; + m_correctionPanelInfo.m_replacementString = marker.description; + startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion); + } + break; + } + } +#endif // SUPPORT_AUTOCORRECTION_PANEL } void Editor::respondToChangedContents(const VisibleSelection& endingSelection) @@ -564,7 +608,8 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe } if (m_frame->selection()->isCaret()) { - if (CSSMutableStyleDeclaration* typingStyle = m_frame->selection()->typingStyle()) { + RefPtr<CSSMutableStyleDeclaration> typingStyle = m_frame->selection()->typingStyle(); + if (typingStyle) { RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); if (unicodeBidi) { ASSERT(unicodeBidi->isPrimitiveValue()); @@ -998,10 +1043,13 @@ static void dispatchEditableContentChangedEvents(const EditCommand& command) void Editor::appliedEditing(PassRefPtr<EditCommand> cmd) { + // We may start reversion panel timer in respondToChangedSelection(). + // So we stop the timer for current panel before calling changeSelectionAfterCommand() later in this method. + stopCorrectionPanelTimer(); m_frame->document()->updateLayout(); - + dispatchEditableContentChangedEvents(*cmd); - + VisibleSelection newSelection(cmd->endingSelection()); // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. changeSelectionAfterCommand(newSelection, false, false); @@ -1020,7 +1068,6 @@ void Editor::appliedEditing(PassRefPtr<EditCommand> cmd) client()->registerCommandForUndo(m_lastEditCommand); } respondToChangedContents(newSelection); - stopCorrectionPanelTimer(); } void Editor::unappliedEditing(PassRefPtr<EditCommand> cmd) @@ -1068,9 +1115,8 @@ Editor::Editor(Frame* frame) Editor::~Editor() { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - if (client()) - client()->dismissCorrectionPanel(true); +#if SUPPORT_AUTOCORRECTION_PANEL + dismissCorrectionPanel(CorrectionWasNotRejected); #endif } @@ -1671,7 +1717,6 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // repeated "check spelling" commands work. VisibleSelection selection(frame()->selection()->selection()); RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document())); - TextCheckingHelper checker(client(), spellingSearchRange); bool startedWithSelection = false; if (selection.start().node()) { @@ -1738,7 +1783,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) bool isSpelling = true; int foundOffset = 0; GrammarDetail grammarDetail; - String foundItem = checker.findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); + String foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; @@ -1748,7 +1793,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) } #else RefPtr<Range> firstMisspellingRange; - String misspelledWord = checker.findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); + String misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); String badGrammarPhrase; #ifndef BUILDING_ON_TIGER @@ -1765,7 +1810,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) } if (isGrammarCheckingEnabled()) - badGrammarPhrase = checker.findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); + badGrammarPhrase = TextCheckingHelper(client(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); #endif #endif @@ -1778,7 +1823,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) grammarSearchRange = spellingSearchRange->cloneRange(ec); - foundItem = checker.findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); + foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; @@ -1787,7 +1832,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) grammarPhraseOffset = foundOffset; } #else - misspelledWord = checker.findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); + misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); #ifndef BUILDING_ON_TIGER grammarSearchRange = spellingSearchRange->cloneRange(ec); @@ -1797,8 +1842,9 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec); } + if (isGrammarCheckingEnabled()) - badGrammarPhrase = checker.findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); + badGrammarPhrase = TextCheckingHelper(client(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); #endif #endif } @@ -1967,47 +2013,10 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) -#if !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL // Apply pending autocorrection before next round of spell checking. - bool didApplyCorrection = false; - if (m_rangeToBeReplacedByCorrection) { - ExceptionCode ec = 0; - RefPtr<Range> paragraphRangeContainingCorrection = m_rangeToBeReplacedByCorrection->cloneRange(ec); - if (!ec) { - setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_rangeToBeReplacedByCorrection->startPosition())); - setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_rangeToBeReplacedByCorrection->endPosition())); - // After we replace the word at range m_rangeToBeReplacedByCorrection, we need to add - // autocorrection underline at that range. However, once the replacement took place, the - // value of m_rangeToBeReplacedByCorrection is not valid anymore. So before we carry out - // the replacement, we need to store the start position of m_rangeToBeReplacedByCorrection - // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph - // to store this value. In order to obtain this offset, we need to first create a range - // which spans from the start of paragraph to the start position of m_rangeToBeReplacedByCorrection. - RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); - if (!ec) { - Position startPositionOfRangeToBeReplaced = m_rangeToBeReplacedByCorrection->startPosition(); - correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec); - if (!ec) { - // 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(); - RefPtr<Range> rangeToBeReplaced = m_rangeToBeReplacedByCorrection->cloneRange(ec); - VisibleSelection selectionToReplace(rangeToBeReplaced.get(), DOWNSTREAM); - if (m_frame->selection()->shouldChangeSelection(selectionToReplace)) { - m_frame->selection()->setSelection(selectionToReplace); - replaceSelectionWithText(m_correctionReplacementString, false, false); - caretPosition.moveToOffset(caretPosition.offsetInContainerNode() + m_correctionReplacementString.length() - m_stringToBeReplacedByCorrection.length()); - RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionReplacementString.length()); - replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::Replacement, m_correctionReplacementString); - replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::CorrectionIndicator); - m_frame->selection()->moveTo(caretPosition, false); - didApplyCorrection = true; - } - } - } - } - m_rangeToBeReplacedByCorrection.clear(); - } + applyCorrectionPanelInfo(true); + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); #endif TextCheckingOptions textCheckingOptions = 0; @@ -2095,7 +2104,7 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo if (!editableNode || !editableNode->isContentEditable()) return; - if (!isSpellCheckingEnabledInFocusedNode()) + if (!isSpellCheckingEnabledFor(editableNode)) return; // Get the spell checker if it is available @@ -2115,11 +2124,8 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo } } -bool Editor::isSpellCheckingEnabledInFocusedNode() const +bool Editor::isSpellCheckingEnabledFor(Node* node) const { - // Ascend the DOM tree to find a "spellcheck" attribute. - // When we find a "spellcheck" attribute, retrieve its value and return false if its value is "false". - const Node* node = frame()->document()->focusedNode(); if (!node) return false; const Element* focusedElement = node->isElementNode() ? toElement(node) : node->parentElement(); @@ -2128,6 +2134,11 @@ bool Editor::isSpellCheckingEnabledInFocusedNode() const return focusedElement->isSpellCheckingEnabled(); } +bool Editor::isSpellCheckingEnabledInFocusedNode() const +{ + return isSpellCheckingEnabledFor(m_frame->selection()->start().node()); +} + void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange) { markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange); @@ -2170,7 +2181,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (!editableNode || !editableNode->isContentEditable()) return; - if (!isSpellCheckingEnabledInFocusedNode()) + if (!isSpellCheckingEnabledFor(editableNode)) return; // Expand the range to encompass entire paragraphs, since text checking needs that much context. @@ -2187,16 +2198,15 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh bool adjustSelectionForParagraphBoundaries = false; String paragraphString; RefPtr<Range> paragraphRange; - TextCheckingHelper checker(client(), grammarRange); if (shouldMarkGrammar) { // The spelling range should be contained in the paragraph-aligned extension of the grammar range. - paragraphRange = checker.paragraphAlignedRange(grammarRangeStartOffset, paragraphString); + paragraphRange = TextCheckingHelper(client(), grammarRange).paragraphAlignedRange(grammarRangeStartOffset, paragraphString); RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition()); spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange); } else { - paragraphRange = checker.paragraphAlignedRange(spellingRangeStartOffset, paragraphString); + paragraphRange = TextCheckingHelper(client(), spellingRange).paragraphAlignedRange(spellingRangeStartOffset, paragraphString); } spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange); paragraphLength = paragraphString.length(); @@ -2242,7 +2252,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh } client()->checkTextOfParagraph(paragraphString.characters(), paragraphLength, checkingTypes, results); -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL // If this checking is only for showing correction panel, we shouldn't bother to mark misspellings. if (shouldShowCorrectionPanel) shouldMarkSpelling = false; @@ -2327,7 +2337,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh } else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) { if (result->type == TextCheckingTypeCorrection) replacedString = plainText(rangeToReplace.get()); -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL if (shouldShowCorrectionPanel && resultLocation + resultLength == spellingRangeEndOffset && result->type == TextCheckingTypeCorrection) { // We only show the correction panel on the last word. Vector<FloatQuad> textQuads; @@ -2336,10 +2346,11 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh FloatRect totalBoundingBox; for (Vector<FloatQuad>::const_iterator it = textQuads.begin(); it < end; ++it) totalBoundingBox.unite(it->boundingBox()); - m_rangeToBeReplacedByCorrection = rangeToReplace; - m_stringToBeReplacedByCorrection = replacedString; - m_correctionReplacementString = result->replacement; - client()->showCorrectionPanel(totalBoundingBox, m_stringToBeReplacedByCorrection, result->replacement, this); + m_correctionPanelInfo.m_rangeToBeReplaced = rangeToReplace; + m_correctionPanelInfo.m_replacedString = replacedString; + m_correctionPanelInfo.m_replacementString = result->replacement; + m_correctionPanelInfo.m_isActive = true; + client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, totalBoundingBox, m_correctionPanelInfo.m_replacedString, result->replacement, this); doReplacement = false; } #endif @@ -2353,7 +2364,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh // Add a marker so that corrections can easily be undone and won't be re-corrected. RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString); - replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::CorrectionIndicator); + replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::CorrectionIndicator, replacedString); } } } @@ -2416,60 +2427,99 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec void Editor::correctionPanelTimerFired(Timer<Editor>*) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) - VisibleSelection selection(frame()->selection()->selection()); - VisiblePosition start(selection.start(), selection.affinity()); - VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary); - VisibleSelection adjacentWords = VisibleSelection(p, start); - markAllMisspellingsAndBadGrammarInRanges(MarkSpelling | ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0); +#if SUPPORT_AUTOCORRECTION_PANEL + if (m_correctionPanelInfo.m_panelType == CorrectionPanelInfo::PanelTypeCorrection) { + VisibleSelection selection(frame()->selection()->selection()); + VisiblePosition start(selection.start(), selection.affinity()); + VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary); + VisibleSelection adjacentWords = VisibleSelection(p, start); + markAllMisspellingsAndBadGrammarInRanges(MarkSpelling | ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0); + } else { + String currentWord = plainText(m_correctionPanelInfo.m_rangeToBeReplaced.get()); + Vector<FloatQuad> textQuads; + m_correctionPanelInfo.m_rangeToBeReplaced->getBorderAndTextQuads(textQuads); + Vector<FloatQuad>::const_iterator end = textQuads.end(); + FloatRect totalBoundingBox; + for (Vector<FloatQuad>::const_iterator it = textQuads.begin(); it < end; ++it) + totalBoundingBox.unite(it->boundingBox()); + m_correctionPanelInfo.m_isActive = true; + m_correctionPanelInfo.m_replacedString = currentWord; + client()->showCorrectionPanel(m_correctionPanelInfo.m_panelType, totalBoundingBox, m_correctionPanelInfo.m_replacedString, m_correctionPanelInfo.m_replacementString, this); + } #endif } void Editor::handleRejectedCorrection() { - Range* replacedRange = m_rangeToBeReplacedByCorrection.get(); + Range* replacedRange = m_correctionPanelInfo.m_rangeToBeReplaced.get(); if (!replacedRange || m_frame->document() != replacedRange->ownerDocument()) return; - replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_stringToBeReplacedByCorrection); - m_rangeToBeReplacedByCorrection.clear(); + if (m_correctionPanelInfo.m_panelType == CorrectionPanelInfo::PanelTypeCorrection) + replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.m_replacedString); + else { + m_correctionPanelInfo.m_isActive = false; + applyCorrectionPanelInfo(false); + } + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); } -void Editor::startCorrectionPanelTimer() +void Editor::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - static const double correctionPanelTimerInterval = 0.3; +#if SUPPORT_AUTOCORRECTION_PANEL + const double correctionPanelTimerInterval = 0.3; if (isAutomaticSpellingCorrectionEnabled()) { - m_rangeToBeReplacedByCorrection.clear(); + if (type == CorrectionPanelInfo::PanelTypeCorrection) + // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it. + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); + m_correctionPanelInfo.m_panelType = type; m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval); } +#else + UNUSED_PARAM(type); #endif } void Editor::stopCorrectionPanelTimer() { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL m_correctionPanelTimer.stop(); #endif } void Editor::handleCancelOperation() { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL + if (!m_correctionPanelInfo.m_isActive) + return; + m_correctionPanelInfo.m_isActive = false; if (client()) - client()->dismissCorrectionPanel(false); + client()->dismissCorrectionPanel(CorrectionWasRejected); #endif } bool Editor::isShowingCorrectionPanel() { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL if (client()) return client()->isShowingCorrectionPanel(); #endif return false; } +void Editor::dismissCorrectionPanel(CorrectionWasRejectedOrNot correctionWasRejectedOrNot) +{ +#if SUPPORT_AUTOCORRECTION_PANEL + if (!m_correctionPanelInfo.m_isActive) + return; + m_correctionPanelInfo.m_isActive = false; + m_correctionPanelInfo.m_rangeToBeReplaced.clear(); + if (client()) + client()->dismissCorrectionPanel(correctionWasRejectedOrNot); +#else + UNUSED_PARAM(correctionWasRejectedOrNot); +#endif +} void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemoveIfSelectionAtWordBoundary) { // We want to remove the markers from a word if an editing command will change the word. This can happen in one of @@ -2536,6 +2586,8 @@ void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemove 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); @@ -2565,6 +2617,54 @@ void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemove document->markers()->removeMarkers(pairIterator->first.get(), pairIterator->second); } +void Editor::applyCorrectionPanelInfo(bool addCorrectionIndicatorMarker) +{ + if (!m_correctionPanelInfo.m_rangeToBeReplaced) + return; + + ExceptionCode ec = 0; + RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.m_rangeToBeReplaced->cloneRange(ec); + if (ec) + return; + + setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.m_rangeToBeReplaced->startPosition())); + setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.m_rangeToBeReplaced->endPosition())); + + // After we replace the word at range m_rangeToBeReplaced, we need to add markers to that range. + // However, once the replacement took place, the value of m_rangeToBeReplaced is not valid anymore. + // So before we carry out the replacement, we need to store the start position of m_rangeToBeReplaced + // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph + // to store this value. In order to obtain this offset, we need to first create a range + // which spans from the start of paragraph to the start position of m_rangeToBeReplaced. + RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); + if (ec) + return; + + Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.m_rangeToBeReplaced->startPosition(); + correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec); + if (ec) + return; + + // 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.m_rangeToBeReplaced->cloneRange(ec); + VisibleSelection selectionToReplace(rangeToBeReplaced.get(), DOWNSTREAM); + if (m_frame->selection()->shouldChangeSelection(selectionToReplace)) { + m_frame->selection()->setSelection(selectionToReplace); + replaceSelectionWithText(m_correctionPanelInfo.m_replacementString, false, false); + caretPosition.moveToOffset(caretPosition.offsetInContainerNode() + m_correctionPanelInfo.m_replacementString.length() - m_correctionPanelInfo.m_replacedString.length()); + setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(caretPosition)); + RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.m_replacementString.length()); + replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::Replacement, m_correctionPanelInfo.m_replacementString); + if (addCorrectionIndicatorMarker) + replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::CorrectionIndicator, m_correctionPanelInfo.m_replacedString); + m_frame->selection()->moveTo(caretPosition, false); + } +} + PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) { Document* document = m_frame->documentAtPoint(windowPoint); @@ -2831,7 +2931,7 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, b if (newSelection.start().isOrphan() || newSelection.end().isOrphan()) return; -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#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())) @@ -2954,7 +3054,7 @@ void Editor::computeAndSetTypingStyle(CSSStyleDeclaration* style, EditAction edi applyCommand(ApplyStyleCommand::create(m_frame->document(), blockStyle.get(), editingAction)); // Set the remaining style as the typing style. - m_frame->selection()->setTypingStyle(mutableStyle.release()); + m_frame->selection()->setTypingStyle(EditingStyle::create(mutableStyle.get())); } PassRefPtr<CSSMutableStyleDeclaration> Editor::selectionComputedStyle(bool& shouldUseFixedFontDefaultSize) const @@ -2985,10 +3085,10 @@ PassRefPtr<CSSMutableStyleDeclaration> Editor::selectionComputedStyle(bool& shou if (!m_frame->selection()->typingStyle()) return mutableStyle; - RefPtr<CSSMutableStyleDeclaration> typingStyle = m_frame->selection()->typingStyle()->copy(); - ApplyStyleCommand::removeNonEditingProperties(typingStyle.get()); - prepareEditingStyleToApplyAt(typingStyle.get(), position); - mutableStyle->merge(typingStyle.get()); + RefPtr<EditingStyle> typingStyle = EditingStyle::create(m_frame->selection()->typingStyle()); + typingStyle->removeNonEditingProperties(); + typingStyle->prepareToApplyAt(position); + mutableStyle->merge(typingStyle->style()); return mutableStyle; } diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h index 110e3f9..24cdaf1 100644 --- a/WebCore/editing/Editor.h +++ b/WebCore/editing/Editor.h @@ -28,23 +28,13 @@ #include "ClipboardAccessPolicy.h" #include "Color.h" +#include "CorrectionPanelInfo.h" #include "EditAction.h" #include "EditingBehavior.h" #include "EditorDeleteAction.h" #include "EditorInsertAction.h" #include "SelectionController.h" -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) -// Some platforms provide UI for suggesting autocorrection. -#define SUPPORT_AUTOCORRECTION_PANEL 1 -// Some platforms use spelling and autocorrection markers to provide visual cue. -// On such platform, if word with marker is edited, we need to remove the marker. -#define REMOVE_MARKERS_UPON_EDITING 1 -#else -#define SUPPORT_AUTOCORRECTION_PANEL 0 -#define REMOVE_MARKERS_UPON_EDITING 0 -#endif /* #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) */ - #if PLATFORM(MAC) && !defined(__OBJC__) class NSDictionary; typedef int NSWritingDirection; @@ -219,6 +209,7 @@ public: Vector<String> guessesForUngrammaticalSelection(); Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical); bool isSpellCheckingEnabledInFocusedNode() const; + bool isSpellCheckingEnabledFor(Node*) const; void markMisspellingsAfterTypingToPosition(const VisiblePosition&); void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange); void markBadGrammar(const VisibleSelection&); @@ -322,7 +313,7 @@ public: void addToKillRing(Range*, bool prepend); void handleCancelOperation(); - void startCorrectionPanelTimer(); + void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType); void handleRejectedCorrection(); bool isShowingCorrectionPanel(); @@ -388,9 +379,7 @@ private: bool m_shouldStartNewKillRingSequence; bool m_shouldStyleWithCSS; OwnPtr<KillRing> m_killRing; - RefPtr<Range> m_rangeToBeReplacedByCorrection; - String m_stringToBeReplacedByCorrection; - String m_correctionReplacementString; + CorrectionPanelInfo m_correctionPanelInfo; Timer<Editor> m_correctionPanelTimer; VisibleSelection m_mark; bool m_areMarkedTextMatchesHighlighted; @@ -417,6 +406,8 @@ private: void correctionPanelTimerFired(Timer<Editor>*); Node* findEventTargetFromSelection() const; void stopCorrectionPanelTimer(); + void dismissCorrectionPanel(CorrectionWasRejectedOrNot); + void applyCorrectionPanelInfo(bool addCorrectionIndicatorMarker); }; inline void Editor::setStartNewKillRingSequence(bool flag) diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index 85becd5..e3ea37e 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -618,8 +618,7 @@ static bool executeMoveBackwardAndModifySelection(Frame* frame, Event*, EditorCo static bool executeMoveDown(Frame* frame, Event*, EditorCommandSource, const String&) { - frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionForward, LineGranularity, true); - return true; + return frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionForward, LineGranularity, true); } static bool executeMoveDownAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) @@ -642,8 +641,7 @@ static bool executeMoveForwardAndModifySelection(Frame* frame, Event*, EditorCom static bool executeMoveLeft(Frame* frame, Event*, EditorCommandSource, const String&) { - frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionLeft, CharacterGranularity, true); - return true; + return frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionLeft, CharacterGranularity, true); } static bool executeMoveLeftAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) @@ -686,8 +684,7 @@ static bool executeMovePageUpAndModifySelection(Frame* frame, Event*, EditorComm static bool executeMoveRight(Frame* frame, Event*, EditorCommandSource, const String&) { - frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionRight, CharacterGranularity, true); - return true; + return frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionRight, CharacterGranularity, true); } static bool executeMoveRightAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) @@ -806,8 +803,7 @@ static bool executeMoveParagraphForwardAndModifySelection(Frame* frame, Event*, static bool executeMoveUp(Frame* frame, Event*, EditorCommandSource, const String&) { - frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionBackward, LineGranularity, true); - return true; + return frame->selection()->modify(SelectionController::AlterationMove, SelectionController::DirectionBackward, LineGranularity, true); } static bool executeMoveUpAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) @@ -1074,7 +1070,7 @@ static bool executeYankAndSelect(Frame* frame, Event*, EditorCommandSource, cons return true; } -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL static bool executeCancelOperation(Frame* frame, Event*, EditorCommandSource, const String&) { frame->editor()->handleCancelOperation(); @@ -1124,7 +1120,7 @@ static bool supportedPaste(Frame* frame, EditorCommandSource source) return false; } -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL static bool supportedDismissCorrectionPanel(Frame* frame, EditorCommandSource source) { return supportedFromMenuOrKeyBinding(frame, source) && frame->editor()->isShowingCorrectionPanel(); @@ -1506,7 +1502,7 @@ static const CommandMap& createCommandMap() { "Unselect", { executeUnselect, supported, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Yank", { executeYank, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "YankAndSelect", { executeYankAndSelect, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL { "CancelOperation", { executeCancelOperation, supportedDismissCorrectionPanel, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, #endif }; diff --git a/WebCore/editing/InsertLineBreakCommand.cpp b/WebCore/editing/InsertLineBreakCommand.cpp index 8ac1167..5588326 100644 --- a/WebCore/editing/InsertLineBreakCommand.cpp +++ b/WebCore/editing/InsertLineBreakCommand.cpp @@ -165,14 +165,14 @@ void InsertLineBreakCommand::doApply() // Handle the case where there is a typing style. - CSSMutableStyleDeclaration* typingStyle = document()->frame()->selection()->typingStyle(); + RefPtr<CSSMutableStyleDeclaration> typingStyle = document()->frame()->selection()->typingStyle(); if (typingStyle && typingStyle->length() > 0) { // Apply the typing style to the inserted line break, so that if the selection // leaves and then comes back, new input will have the right style. // FIXME: We shouldn't always apply the typing style to the line break here, // see <rdar://problem/5794462>. - applyStyle(typingStyle, firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); + applyStyle(typingStyle.get(), firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); // Even though this applyStyle operates on a Range, it still sets an endingSelection(). // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection // will either (a) select the line break we inserted, or it will (b) be a caret just diff --git a/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/WebCore/editing/InsertParagraphSeparatorCommand.cpp index 1804bf4..abd744c 100644 --- a/WebCore/editing/InsertParagraphSeparatorCommand.cpp +++ b/WebCore/editing/InsertParagraphSeparatorCommand.cpp @@ -26,19 +26,16 @@ #include "config.h" #include "InsertParagraphSeparatorCommand.h" -#include "CSSComputedStyleDeclaration.h" -#include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" #include "Document.h" +#include "EditingStyle.h" #include "HTMLElement.h" #include "HTMLNames.h" #include "InsertLineBreakCommand.h" -#include "Logging.h" #include "RenderObject.h" #include "Text.h" #include "htmlediting.h" #include "visible_units.h" -#include "ApplyStyleCommand.h" namespace WebCore { @@ -82,7 +79,7 @@ void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Positi if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos)) return; - m_style = ApplyStyleCommand::editingStyleAtPosition(pos, IncludeTypingStyle); + m_style = editingStyleIncludingTypingStyle(pos); } void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnclosingBlock) @@ -95,14 +92,13 @@ void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnc originalEnclosingBlock->hasTagName(h4Tag) || originalEnclosingBlock->hasTagName(h5Tag)) return; - + if (!m_style) return; - - prepareEditingStyleToApplyAt(m_style.get(), endingSelection().start()); - if (m_style->length() > 0) - applyStyle(m_style.get()); + m_style->prepareToApplyAt(endingSelection().start()); + if (!m_style->isEmpty()) + applyStyle(m_style->style()); } bool InsertParagraphSeparatorCommand::shouldUseDefaultParagraphElement(Node* enclosingBlock) const diff --git a/WebCore/editing/InsertParagraphSeparatorCommand.h b/WebCore/editing/InsertParagraphSeparatorCommand.h index 23ee51c..2eae77d 100644 --- a/WebCore/editing/InsertParagraphSeparatorCommand.h +++ b/WebCore/editing/InsertParagraphSeparatorCommand.h @@ -30,6 +30,8 @@ namespace WebCore { +class EditingStyle; + class InsertParagraphSeparatorCommand : public CompositeEditCommand { public: static PassRefPtr<InsertParagraphSeparatorCommand> create(Document* document, bool useDefaultParagraphElement = false) @@ -46,13 +48,13 @@ private: void applyStyleAfterInsertion(Node* originalEnclosingBlock); void getAncestorsInsideBlock(const Node* insertionNode, Element* outerBlock, Vector<Element*>& ancestors); PassRefPtr<Element> cloneHierarchyUnderNewBlock(const Vector<Element*>& ancestors, PassRefPtr<Element> blockToInsert); - + bool shouldUseDefaultParagraphElement(Node*) const; virtual bool preservesTypingStyle() const; - RefPtr<CSSMutableStyleDeclaration> m_style; - + RefPtr<EditingStyle> m_style; + bool m_mustUseDefaultParagraphElement; }; diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp index b6c8236..87a695d 100644 --- a/WebCore/editing/InsertTextCommand.cpp +++ b/WebCore/editing/InsertTextCommand.cpp @@ -190,7 +190,7 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText) setEndingSelection(forcedEndingSelection); // Handle the case where there is a typing style. - CSSMutableStyleDeclaration* typingStyle = document()->frame()->selection()->typingStyle(); + RefPtr<CSSMutableStyleDeclaration> typingStyle = document()->frame()->selection()->typingStyle(); RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle(); RefPtr<CSSValue> unicodeBidi; RefPtr<CSSValue> direction; @@ -198,7 +198,7 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText) unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection); } - endingStyle->diff(typingStyle); + endingStyle->diff(typingStyle.get()); if (typingStyle && unicodeBidi) { ASSERT(unicodeBidi->isPrimitiveValue()); typingStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); @@ -209,7 +209,7 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText) } if (typingStyle && typingStyle->length()) - applyStyle(typingStyle); + applyStyle(typingStyle.get()); if (!selectInsertedText) setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity())); diff --git a/WebCore/editing/RemoveFormatCommand.cpp b/WebCore/editing/RemoveFormatCommand.cpp index f8807e2..42833cb 100644 --- a/WebCore/editing/RemoveFormatCommand.cpp +++ b/WebCore/editing/RemoveFormatCommand.cpp @@ -28,18 +28,11 @@ #include "RemoveFormatCommand.h" #include "ApplyStyleCommand.h" -#include "CSSComputedStyleDeclaration.h" -#include "CSSMutableStyleDeclaration.h" -#include "CSSValueKeywords.h" -#include "Editor.h" +#include "EditingStyle.h" +#include "Element.h" #include "Frame.h" -#include "HTMLElement.h" #include "HTMLNames.h" -#include "VisibleSelection.h" #include "SelectionController.h" -#include "TextIterator.h" -#include "TypingCommand.h" -#include "htmlediting.h" namespace WebCore { @@ -92,9 +85,9 @@ void RemoveFormatCommand::doApply() // Get the default style for this editable root, it's the style that we'll give the // content that we're operating on. Node* root = frame->selection()->rootEditableElement(); - RefPtr<CSSMutableStyleDeclaration> defaultStyle = ApplyStyleCommand::editingStyleAtPosition(Position(root, 0)); + RefPtr<EditingStyle> defaultStyle = EditingStyle::create(root); - applyCommandToComposite(ApplyStyleCommand::create(document(), defaultStyle.get(), isElementForRemoveFormatCommand, editingAction())); + applyCommandToComposite(ApplyStyleCommand::create(document(), defaultStyle->style(), isElementForRemoveFormatCommand, editingAction())); } } diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp index 056ab70..fb52e56 100644 --- a/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/WebCore/editing/ReplaceSelectionCommand.cpp @@ -31,7 +31,6 @@ #include "BreakBlockquoteCommand.h" #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" -#include "CSSProperty.h" #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" #include "Document.h" @@ -484,17 +483,6 @@ void ReplaceSelectionCommand::negateStyleRulesThatAffectAppearance() e->getInlineStyleDecl()->setProperty(CSSPropertyDisplay, CSSValueInline); if (e->renderer() && e->renderer()->style()->floating() != FNONE) e->getInlineStyleDecl()->setProperty(CSSPropertyFloat, CSSValueNone); - - // Undo the effects of page zoom if we have an absolute font size. When we copy, we - // compute the new font size as an absolute size so pasting will cause the zoom to be - // applied twice. - if (e->renderer() && e->renderer()->style() && e->renderer()->style()->effectiveZoom() != 1.0 - && e->renderer()->style()->fontDescription().isAbsoluteSize()) { - float newSize = e->renderer()->style()->fontDescription().specifiedSize() / e->renderer()->style()->effectiveZoom(); - ExceptionCode ec = 0; - e->style()->setProperty(CSSPropertyFontSize, String::number(newSize), false, ec); - ASSERT(!ec); - } } if (node == m_lastLeafInserted) break; @@ -563,35 +551,36 @@ VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos) { Node* topNode = fragment.firstChild(); - + // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans) // and doesn't receive the optimization. if (isMailPasteAsQuotationNode(topNode) || nearestMailBlockquote(topNode)) return false; - + // Either there are no style spans in the fragment or a WebKit client has added content to the fragment // before inserting it. Look for and handle style spans after insertion. if (!isStyleSpan(topNode)) return false; - + Node* sourceDocumentStyleSpan = topNode; RefPtr<Node> copiedRangeStyleSpan = sourceDocumentStyleSpan->firstChild(); - RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = ApplyStyleCommand::editingStyleAtPosition(rangeCompliantEquivalent(insertionPos)); + RefPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create(rangeCompliantEquivalent(insertionPos)); + String styleText = styleAtInsertionPos->style()->cssText(); - String styleText = styleAtInsertionPos->cssText(); - + // FIXME: This string comparison is a naive way of comparing two styles. + // We should be taking the diff and check that the diff is empty. if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) { fragment.removeNodePreservingChildren(sourceDocumentStyleSpan); if (!isStyleSpan(copiedRangeStyleSpan.get())) return true; } - + if (isStyleSpan(copiedRangeStyleSpan.get()) && styleText == static_cast<Element*>(copiedRangeStyleSpan.get())->getAttribute(styleAttr)) { fragment.removeNodePreservingChildren(copiedRangeStyleSpan.get()); return true; } - + return false; } @@ -626,29 +615,20 @@ void ReplaceSelectionCommand::handleStyleSpans() // we are here because of a document.execCommand("InsertHTML", ...) call. if (!sourceDocumentStyleSpan) return; - - RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy(); + + RefPtr<EditingStyle> sourceDocumentStyle = EditingStyle::create(static_cast<HTMLElement*>(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, // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : nearestMailBlockquote(context); if (blockquoteNode) { - RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = ApplyStyleCommand::editingStyleAtPosition(Position(blockquoteNode, 0)); - RefPtr<CSSMutableStyleDeclaration> parentStyle = ApplyStyleCommand::editingStyleAtPosition(Position(blockquoteNode->parentNode(), 0)); - parentStyle->diff(blockquoteStyle.get()); - - CSSMutableStyleDeclaration::const_iterator end = blockquoteStyle->end(); - for (CSSMutableStyleDeclaration::const_iterator it = blockquoteStyle->begin(); it != end; ++it) { - const CSSProperty& property = *it; - sourceDocumentStyle->removeProperty(property.id()); - } - + sourceDocumentStyle->removeStyleConflictingWithStyleOfNode(blockquoteNode); context = blockquoteNode->parentNode(); } // This operation requires that only editing styles to be removed from sourceDocumentStyle. - prepareEditingStyleToApplyAt(sourceDocumentStyle.get(), Position(context, 0)); + sourceDocumentStyle->prepareToApplyAt(firstPositionInNode(context)); // Remove block properties in the span's style. This prevents properties that probably have no effect // currently from affecting blocks later if the style is cloned for a new block element during a future @@ -656,49 +636,44 @@ void ReplaceSelectionCommand::handleStyleSpans() // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might. sourceDocumentStyle->removeBlockProperties(); - + // The styles on sourceDocumentStyleSpan are all redundant, and there is no copiedRangeStyleSpan // to consider. We're finished. - if (sourceDocumentStyle->length() == 0 && !copiedRangeStyleSpan) { + if (sourceDocumentStyle->isEmpty() && !copiedRangeStyleSpan) { removeNodePreservingChildren(sourceDocumentStyleSpan); return; } - + // There are non-redundant styles on sourceDocumentStyleSpan, but there is no // copiedRangeStyleSpan. Remove the span, because it could be surrounding block elements, // and apply the styles to its children. - if (sourceDocumentStyle->length() > 0 && !copiedRangeStyleSpan) { - copyStyleToChildren(sourceDocumentStyleSpan, sourceDocumentStyle.get()); + if (!sourceDocumentStyle->isEmpty() && !copiedRangeStyleSpan) { + copyStyleToChildren(sourceDocumentStyleSpan, sourceDocumentStyle->style()); removeNodePreservingChildren(sourceDocumentStyleSpan); return; } - RefPtr<CSSMutableStyleDeclaration> copiedRangeStyle = static_cast<HTMLElement*>(copiedRangeStyleSpan)->getInlineStyleDecl()->copy(); - + RefPtr<EditingStyle> copiedRangeStyle = EditingStyle::create(static_cast<HTMLElement*>(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. - sourceDocumentStyle->merge(copiedRangeStyle.get(), true); - copiedRangeStyle = sourceDocumentStyle; - + copiedRangeStyle->style()->merge(sourceDocumentStyle->style(), false); + removeNodePreservingChildren(sourceDocumentStyleSpan); - + // Remove redundant styles. context = copiedRangeStyleSpan->parentNode(); - prepareEditingStyleToApplyAt(copiedRangeStyle.get(), Position(context, 0)); - - // See the comments above about removing block properties. + copiedRangeStyle->prepareToApplyAt(firstPositionInNode(context)); copiedRangeStyle->removeBlockProperties(); - - // All the styles on copiedRangeStyleSpan are redundant, remove it. - if (copiedRangeStyle->length() == 0) { + if (copiedRangeStyle->isEmpty()) { removeNodePreservingChildren(copiedRangeStyleSpan); return; } - + // Clear the redundant styles from the span's style attribute. // FIXME: If font-family:-webkit-monospace is non-redundant, then the font-size should stay, even if it // appears redundant. - setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->cssText()); + setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->style()->cssText()); } // Take the style attribute of a span and apply it to it's children instead. This allows us to @@ -810,9 +785,14 @@ void ReplaceSelectionCommand::doApply() if (performTrivialReplace(fragment)) return; - if (m_matchStyle) - m_insertionStyle = ApplyStyleCommand::editingStyleAtPosition(selection.start(), IncludeTypingStyle); + // We can skip matching the style if the selection is plain text. + if ((selection.start().node()->renderer() && selection.start().node()->renderer()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY) && + (selection.end().node()->renderer() && selection.end().node()->renderer()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY)) + m_matchStyle = false; + if (m_matchStyle) + m_insertionStyle = editingStyleIncludingTypingStyle(selection.start()); + VisiblePosition visibleStart = selection.visibleStart(); VisiblePosition visibleEnd = selection.visibleEnd(); @@ -1163,7 +1143,7 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi if (m_matchStyle) { ASSERT(m_insertionStyle); - applyStyle(m_insertionStyle.get(), start, end); + applyStyle(m_insertionStyle->style(), start, end); } if (lastPositionToSelect.isNotNull()) diff --git a/WebCore/editing/ReplaceSelectionCommand.h b/WebCore/editing/ReplaceSelectionCommand.h index e995e79..9fc4a49 100644 --- a/WebCore/editing/ReplaceSelectionCommand.h +++ b/WebCore/editing/ReplaceSelectionCommand.h @@ -31,6 +31,7 @@ namespace WebCore { class DocumentFragment; +class EditingStyle; class ReplacementFragment; class ReplaceSelectionCommand : public CompositeEditCommand { @@ -82,7 +83,7 @@ private: RefPtr<Node> m_firstNodeInserted; RefPtr<Node> m_lastLeafInserted; - RefPtr<CSSMutableStyleDeclaration> m_insertionStyle; + RefPtr<EditingStyle> m_insertionStyle; bool m_selectReplacement; bool m_smartReplace; bool m_matchStyle; diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index 6f25c86..bb42662 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -642,6 +642,8 @@ bool SelectionController::modify(EAlteration alter, EDirection direction, TextGr willBeModified(alter, direction); + bool wasRange = m_selection.isRange(); + Position originalStartPosition = m_selection.start(); VisiblePosition position; switch (direction) { case DirectionRight: @@ -673,6 +675,10 @@ bool SelectionController::modify(EAlteration alter, EDirection direction, TextGr if (position.isNull()) return false; + if (isSpatialNavigationEnabled(m_frame)) + if (!wasRange && alter == AlterationMove && position == originalStartPosition) + return false; + // Some of the above operations set an xPosForVerticalArrowNavigation. // Setting a selection will clear it, so save it to possibly restore later. // Note: the START position type is arbitrary because it is unused, it would be diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h index e5fe71b..2edad0a 100644 --- a/WebCore/editing/SelectionController.h +++ b/WebCore/editing/SelectionController.h @@ -26,7 +26,7 @@ #ifndef SelectionController_h #define SelectionController_h -#include "CSSMutableStyleDeclaration.h" +#include "EditingStyle.h" #include "IntRect.h" #include "Range.h" #include "ScrollBehavior.h" @@ -37,6 +37,7 @@ namespace WebCore { class Frame; +class CSSMutableStyleDeclaration; class GraphicsContext; class HTMLFormElement; class RenderObject; @@ -160,7 +161,7 @@ public: void paintDragCaret(GraphicsContext*, int tx, int ty, const IntRect& clipRect) const; CSSMutableStyleDeclaration* typingStyle() const; - void setTypingStyle(PassRefPtr<CSSMutableStyleDeclaration>); + void setTypingStyle(PassRefPtr<EditingStyle>); void clearTypingStyle(); FloatRect bounds(bool clipToVisibleContent = true) const; @@ -214,7 +215,7 @@ private: VisibleSelection m_selection; TextGranularity m_granularity; - RefPtr<CSSMutableStyleDeclaration> m_typingStyle; + RefPtr<EditingStyle> m_typingStyle; Timer<SelectionController> m_caretBlinkTimer; @@ -234,7 +235,7 @@ private: inline CSSMutableStyleDeclaration* SelectionController::typingStyle() const { - return m_typingStyle.get(); + return m_typingStyle ? m_typingStyle->style() : 0; } inline void SelectionController::clearTypingStyle() @@ -242,7 +243,7 @@ inline void SelectionController::clearTypingStyle() m_typingStyle.clear(); } -inline void SelectionController::setTypingStyle(PassRefPtr<CSSMutableStyleDeclaration> style) +inline void SelectionController::setTypingStyle(PassRefPtr<EditingStyle> style) { m_typingStyle = style; } diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp index d78708b..9165a3e 100644 --- a/WebCore/editing/TypingCommand.cpp +++ b/WebCore/editing/TypingCommand.cpp @@ -311,9 +311,9 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); if (p1 != p2) document()->frame()->editor()->markMisspellingsAfterTypingToPosition(p1); -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) +#if SUPPORT_AUTOCORRECTION_PANEL else if (commandType == TypingCommand::InsertText) - document()->frame()->editor()->startCorrectionPanelTimer(); + document()->frame()->editor()->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection); #else UNUSED_PARAM(commandType); #endif diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp index 75d567e..417de71 100644 --- a/WebCore/editing/markup.cpp +++ b/WebCore/editing/markup.cpp @@ -26,10 +26,8 @@ #include "config.h" #include "markup.h" -#include "ApplyStyleCommand.h" #include "CDATASection.h" #include "CharacterNames.h" -#include "Comment.h" #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPrimitiveValue.h" @@ -49,11 +47,8 @@ #include "HTMLBodyElement.h" #include "HTMLElement.h" #include "HTMLNames.h" -#include "InlineTextBox.h" #include "KURL.h" -#include "Logging.h" #include "MarkupAccumulator.h" -#include "ProcessingInstruction.h" #include "Range.h" #include "TextIterator.h" #include "VisibleSelection.h" @@ -525,23 +520,6 @@ static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelected return specialCommonAncestor; } -static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node) -{ - Node* blockquote = nearestMailBlockquote(node); - if (!blockquote || !blockquote->parentNode()) - return; - - removeStylesAddedByNode(style, blockquote); -} - -static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document) -{ - if (!document || !document->documentElement()) - return; - - prepareEditingStyleToApplyAt(style, Position(document->documentElement(), 0)); -} - // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString() String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) @@ -650,34 +628,34 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc // Add a wrapper span with the styles that all of the nodes in the markup inherit. ContainerNode* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0; if (parentOfLastClosed && parentOfLastClosed->renderer()) { - RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(Position(parentOfLastClosed, 0)); + RefPtr<EditingStyle> style = EditingStyle::create(parentOfLastClosed); // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help // us differentiate those styles from ones that the user has applied. This helps us // get the color of content pasted into blockquotes right. - removeEnclosingMailBlockquoteStyle(style.get(), parentOfLastClosed); - + style->removeStyleAddedByNode(nearestMailBlockquote(parentOfLastClosed)); + // Document default styles will be added on another wrapper span. - removeDefaultStyles(style.get(), document); - + if (document && document->documentElement()) + style->prepareToApplyAt(firstPositionInNode(document->documentElement())); + // Since we are converting blocks to inlines, remove any inherited block properties that are in the style. // This cuts out meaningless properties and prevents properties from magically affecting blocks later // if the style is cloned for a new block element during a future editing operation. if (convertBlocksToInlines) style->removeBlockProperties(); - if (style->length() > 0) - accumulator.wrapWithStyleNode(style.get(), document); + if (!style->isEmpty()) + accumulator.wrapWithStyleNode(style->style(), document); } if (lastClosed && lastClosed != document->documentElement()) { // Add a style span with the document's default styles. We add these in a separate // span so that at paste time we can differentiate between document defaults and user // applied styles. - RefPtr<CSSMutableStyleDeclaration> defaultStyle = ApplyStyleCommand::editingStyleAtPosition(Position(document->documentElement(), 0)); - - if (defaultStyle->length() > 0) - accumulator.wrapWithStyleNode(defaultStyle.get(), document); + RefPtr<EditingStyle> defaultStyle = EditingStyle::create(document->documentElement()); + if (!defaultStyle->isEmpty()) + accumulator.wrapWithStyleNode(defaultStyle->style(), document); } // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. |