summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/editing
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2011-06-02 12:07:03 +0100
committerBen Murdoch <benm@google.com>2011-06-10 10:47:21 +0100
commit2daae5fd11344eaa88a0d92b0f6d65f8d2255c00 (patch)
treee4964fbd1cb70599f7718ff03e50ea1dab33890b /Source/WebCore/editing
parent87bdf0060a247bfbe668342b87e0874182e0ffa9 (diff)
downloadexternal_webkit-2daae5fd11344eaa88a0d92b0f6d65f8d2255c00.zip
external_webkit-2daae5fd11344eaa88a0d92b0f6d65f8d2255c00.tar.gz
external_webkit-2daae5fd11344eaa88a0d92b0f6d65f8d2255c00.tar.bz2
Merge WebKit at r84325: Initial merge by git.
Change-Id: Ic1a909300ecc0a13ddc6b4e784371d2ac6e3d59b
Diffstat (limited to 'Source/WebCore/editing')
-rw-r--r--Source/WebCore/editing/ApplyStyleCommand.cpp325
-rw-r--r--Source/WebCore/editing/ApplyStyleCommand.h4
-rw-r--r--Source/WebCore/editing/CompositeEditCommand.cpp22
-rw-r--r--Source/WebCore/editing/CorrectionPanelInfo.h65
-rw-r--r--Source/WebCore/editing/DeleteButtonController.cpp10
-rw-r--r--Source/WebCore/editing/DeleteSelectionCommand.cpp20
-rw-r--r--Source/WebCore/editing/EditingAllInOne.cpp1
-rw-r--r--Source/WebCore/editing/EditingBehavior.h4
-rw-r--r--Source/WebCore/editing/EditingStyle.cpp275
-rw-r--r--Source/WebCore/editing/EditingStyle.h58
-rw-r--r--Source/WebCore/editing/Editor.cpp591
-rw-r--r--Source/WebCore/editing/Editor.h38
-rw-r--r--Source/WebCore/editing/EditorCommand.cpp18
-rw-r--r--Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp54
-rw-r--r--Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp4
-rw-r--r--Source/WebCore/editing/ReplaceSelectionCommand.cpp25
-rw-r--r--Source/WebCore/editing/SelectionController.cpp24
-rw-r--r--Source/WebCore/editing/SpellChecker.cpp29
-rw-r--r--Source/WebCore/editing/SpellChecker.h24
-rw-r--r--Source/WebCore/editing/SpellingCorrectionCommand.cpp4
-rw-r--r--Source/WebCore/editing/SpellingCorrectionController.cpp479
-rw-r--r--Source/WebCore/editing/SpellingCorrectionController.h146
-rw-r--r--Source/WebCore/editing/TextCheckingHelper.cpp4
-rw-r--r--Source/WebCore/editing/TextGranularity.h5
-rw-r--r--Source/WebCore/editing/TextIterator.cpp40
-rw-r--r--Source/WebCore/editing/TextIterator.h6
-rw-r--r--Source/WebCore/editing/TypingCommand.cpp94
-rw-r--r--Source/WebCore/editing/TypingCommand.h32
-rw-r--r--Source/WebCore/editing/VisibleSelection.cpp2
-rw-r--r--Source/WebCore/editing/htmlediting.cpp14
-rw-r--r--Source/WebCore/editing/htmlediting.h11
-rw-r--r--Source/WebCore/editing/markup.cpp7
-rw-r--r--Source/WebCore/editing/qt/EditorQt.cpp2
-rw-r--r--Source/WebCore/editing/qt/SmartReplaceQt.cpp18
-rw-r--r--Source/WebCore/editing/visible_units.cpp393
-rw-r--r--Source/WebCore/editing/visible_units.h2
36 files changed, 1794 insertions, 1056 deletions
diff --git a/Source/WebCore/editing/ApplyStyleCommand.cpp b/Source/WebCore/editing/ApplyStyleCommand.cpp
index 59540ec..c9649d0 100644
--- a/Source/WebCore/editing/ApplyStyleCommand.cpp
+++ b/Source/WebCore/editing/ApplyStyleCommand.cpp
@@ -54,242 +54,6 @@ namespace WebCore {
using namespace HTMLNames;
-static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
-{
- RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor);
- if (!colorValue || !colorValue->isPrimitiveValue())
- return Color::transparent;
-
- CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get());
- RGBA32 rgba = 0;
- if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) {
- CSSParser::parseColor(rgba, colorValue->cssText());
- // Need to take care of named color such as green and black
- // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed.
- } else
- rgba = primitiveColor->getRGBA32Value();
-
- return rgba;
-}
-
-class StyleChange {
-public:
- StyleChange(EditingStyle*, const Position&);
-
- String cssStyle() const { return m_cssStyle; }
- bool applyBold() const { return m_applyBold; }
- bool applyItalic() const { return m_applyItalic; }
- bool applyUnderline() const { return m_applyUnderline; }
- bool applyLineThrough() const { return m_applyLineThrough; }
- bool applySubscript() const { return m_applySubscript; }
- bool applySuperscript() const { return m_applySuperscript; }
- bool applyFontColor() const { return m_applyFontColor.length() > 0; }
- bool applyFontFace() const { return m_applyFontFace.length() > 0; }
- bool applyFontSize() const { return m_applyFontSize.length() > 0; }
-
- String fontColor() { return m_applyFontColor; }
- String fontFace() { return m_applyFontFace; }
- String fontSize() { return m_applyFontSize; }
-
- bool operator==(const StyleChange& other)
- {
- return m_cssStyle == other.m_cssStyle
- && m_applyBold == other.m_applyBold
- && m_applyItalic == other.m_applyItalic
- && m_applyUnderline == other.m_applyUnderline
- && m_applyLineThrough == other.m_applyLineThrough
- && m_applySubscript == other.m_applySubscript
- && m_applySuperscript == other.m_applySuperscript
- && m_applyFontColor == other.m_applyFontColor
- && m_applyFontFace == other.m_applyFontFace
- && m_applyFontSize == other.m_applyFontSize;
- }
- bool operator!=(const StyleChange& other)
- {
- return !(*this == other);
- }
-private:
- void init(EditingStyle*, const Position&);
- void reconcileTextDecorationProperties(CSSMutableStyleDeclaration*);
- void extractTextStyles(Document*, CSSMutableStyleDeclaration*, bool shouldUseFixedFontDefaultSize);
-
- String m_cssStyle;
- bool m_applyBold;
- bool m_applyItalic;
- bool m_applyUnderline;
- bool m_applyLineThrough;
- bool m_applySubscript;
- bool m_applySuperscript;
- String m_applyFontColor;
- String m_applyFontFace;
- String m_applyFontSize;
-};
-
-
-StyleChange::StyleChange(EditingStyle* style, const Position& position)
- : m_applyBold(false)
- , m_applyItalic(false)
- , m_applyUnderline(false)
- , m_applyLineThrough(false)
- , m_applySubscript(false)
- , m_applySuperscript(false)
-{
- init(style, position);
-}
-
-void StyleChange::init(EditingStyle* style, const Position& position)
-{
- Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
- if (!style || !style->style() || !document || !document->frame())
- return;
-
- RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
- RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
-
- reconcileTextDecorationProperties(mutableStyle.get());
- if (!document->frame()->editor()->shouldStyleWithCSS())
- extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
-
- // Changing the whitespace style in a tab span would collapse the tab into a space.
- if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
- mutableStyle->removeProperty(CSSPropertyWhiteSpace);
-
- // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
- // FIXME: Shouldn't this be done in getPropertiesNotIn?
- if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
- mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
-
- // Save the result for later
- m_cssStyle = mutableStyle->cssText().stripWhiteSpace();
-}
-
-void StyleChange::reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style)
-{
- RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
- RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
- // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
- ASSERT(!textDecorationsInEffect || !textDecoration);
- if (textDecorationsInEffect) {
- style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
- style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
- textDecoration = textDecorationsInEffect;
- }
-
- // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
- if (textDecoration && !textDecoration->isValueList())
- style->removeProperty(CSSPropertyTextDecoration);
-}
-
-static int getIdentifierValue(CSSStyleDeclaration* style, int propertyID)
-{
- if (!style)
- return 0;
-
- RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
- if (!value || !value->isPrimitiveValue())
- return 0;
-
- return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
-}
-
-static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID)
-{
- if (newTextDecoration->length())
- style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID));
- else {
- // text-decoration: none is redundant since it does not remove any text decorations.
- ASSERT(!style->getPropertyPriority(propertyID));
- style->removeProperty(propertyID);
- }
-}
-
-static bool isCSSValueLength(CSSPrimitiveValue* value)
-{
- return value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC;
-}
-
-int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
-{
- if (isCSSValueLength(value)) {
- int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
- int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
- // Use legacy font size only if pixel value matches exactly to that of legacy font size.
- int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
- if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
- return legacyFontSize;
-
- return 0;
- }
-
- if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
- return value->getIdent() - CSSValueXSmall + 1;
-
- return 0;
-}
-
-void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefaultSize)
-{
- ASSERT(style);
-
- if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
- style->removeProperty(CSSPropertyFontWeight);
- m_applyBold = true;
- }
-
- int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
- if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
- style->removeProperty(CSSPropertyFontStyle);
- m_applyItalic = true;
- }
-
- // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
- // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
- RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
- if (textDecoration && textDecoration->isValueList()) {
- DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
- DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
-
- RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
- if (newTextDecoration->removeAll(underline.get()))
- m_applyUnderline = true;
- if (newTextDecoration->removeAll(lineThrough.get()))
- m_applyLineThrough = true;
-
- // If trimTextDecorations, delete underline and line-through
- setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
- }
-
- int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
- switch (verticalAlign) {
- case CSSValueSub:
- style->removeProperty(CSSPropertyVerticalAlign);
- m_applySubscript = true;
- break;
- case CSSValueSuper:
- style->removeProperty(CSSPropertyVerticalAlign);
- m_applySuperscript = true;
- break;
- }
-
- if (style->getPropertyCSSValue(CSSPropertyColor)) {
- m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
- style->removeProperty(CSSPropertyColor);
- }
-
- m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
- style->removeProperty(CSSPropertyFontFamily);
-
- if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
- if (!fontSize->isPrimitiveValue())
- style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
- else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
- shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
- m_applyFontSize = String::number(legacyFontSize);
- style->removeProperty(CSSPropertyFontSize);
- }
- }
-}
-
static String& styleSpanClassString()
{
DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
@@ -354,95 +118,6 @@ PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
return styleElement.release();
}
-static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration)
-{
- RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
- if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
- return;
-
- RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
- CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
-
- for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
- newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
-
- setTextDecorationProperty(style, newTextDecoration.get(), propertID);
-}
-
-static bool fontWeightIsBold(CSSStyleDeclaration* style)
-{
- ASSERT(style);
- RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
-
- if (!fontWeight)
- return false;
- if (!fontWeight->isPrimitiveValue())
- return false;
-
- // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
- // Collapse all other values to either one of these two states for editing purposes.
- switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) {
- case CSSValue100:
- case CSSValue200:
- case CSSValue300:
- case CSSValue400:
- case CSSValue500:
- case CSSValueNormal:
- return false;
- case CSSValueBold:
- case CSSValue600:
- case CSSValue700:
- case CSSValue800:
- case CSSValue900:
- return true;
- }
-
- ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
- return false; // Make compiler happy
-}
-
-static int getTextAlignment(CSSStyleDeclaration* style)
-{
- int textAlign = getIdentifierValue(style, CSSPropertyTextAlign);
- switch (textAlign) {
- case CSSValueCenter:
- case CSSValueWebkitCenter:
- return CSSValueCenter;
- case CSSValueJustify:
- return CSSValueJustify;
- case CSSValueLeft:
- case CSSValueWebkitLeft:
- return CSSValueLeft;
- case CSSValueRight:
- case CSSValueWebkitRight:
- return CSSValueRight;
- }
- return CSSValueInvalid;
-}
-
-RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
-{
- ASSERT(styleWithRedundantProperties);
- ASSERT(baseStyle);
- RefPtr<CSSMutableStyleDeclaration> result = styleWithRedundantProperties->copy();
- baseStyle->diff(result.get());
-
- RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
- diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
- diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
-
- if (fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
- result->removeProperty(CSSPropertyFontWeight);
-
- if (getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
- result->removeProperty(CSSPropertyColor);
-
- if (getTextAlignment(result.get()) == getTextAlignment(baseStyle))
- result->removeProperty(CSSPropertyTextAlign);
-
- return result;
-}
-
ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
: CompositeEditCommand(document)
, m_style(style->copy())
diff --git a/Source/WebCore/editing/ApplyStyleCommand.h b/Source/WebCore/editing/ApplyStyleCommand.h
index dc2217e..6953456 100644
--- a/Source/WebCore/editing/ApplyStyleCommand.h
+++ b/Source/WebCore/editing/ApplyStyleCommand.h
@@ -34,7 +34,6 @@ namespace WebCore {
class CSSPrimitiveValue;
class EditingStyle;
-class HTMLElement;
class StyleChange;
enum ShouldIncludeTypingStyle {
@@ -130,11 +129,8 @@ private:
IsInlineElementToRemoveFunction m_isInlineElementToRemoveFunction;
};
-enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
-int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
bool isStyleSpan(const Node*);
PassRefPtr<HTMLElement> createStyleSpanElement(Document*);
-RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
} // namespace WebCore
diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp
index 2a69fb7..cf2959a 100644
--- a/Source/WebCore/editing/CompositeEditCommand.cpp
+++ b/Source/WebCore/editing/CompositeEditCommand.cpp
@@ -1217,23 +1217,27 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi
// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
// to determine if the split is necessary. Returns the last split node.
-PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor)
+PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor)
{
+ ASSERT(start);
+ ASSERT(end);
ASSERT(start != end);
RefPtr<Node> node;
- for (node = start; node && node->parentNode() != end; node = node->parentNode()) {
+ if (shouldSplitAncestor && end->parentNode())
+ end = end->parentNode();
+
+ RefPtr<Node> endNode = end;
+ for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) {
if (!node->parentNode()->isElementNode())
break;
- VisiblePosition positionInParent(firstPositionInNode(node->parentNode()), DOWNSTREAM);
- VisiblePosition positionInNode(firstPositionInOrBeforeNode(node.get()), DOWNSTREAM);
+ // Do not split a node when doing so introduces an empty node.
+ VisiblePosition positionInParent = firstPositionInNode(node->parentNode());
+ VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get());
if (positionInParent != positionInNode)
- applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parentNode()), node));
- }
- if (splitAncestor) {
- splitElement(static_cast<Element*>(end), node);
- return node->parentNode();
+ splitElement(static_cast<Element*>(node->parentNode()), node);
}
+
return node.release();
}
diff --git a/Source/WebCore/editing/CorrectionPanelInfo.h b/Source/WebCore/editing/CorrectionPanelInfo.h
deleted file mode 100644
index 76099e1..0000000
--- a/Source/WebCore/editing/CorrectionPanelInfo.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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,
- PanelTypeSpellingSuggestions
- };
-
- RefPtr<Range> rangeToBeReplaced;
- String replacedString;
- String replacementString;
- PanelType panelType;
- bool isActive;
-};
-
-enum ReasonForDismissingCorrectionPanel {
- ReasonForDismissingCorrectionPanelCancelled = 0,
- ReasonForDismissingCorrectionPanelIgnored,
- ReasonForDismissingCorrectionPanelAccepted
-};
-} // namespace WebCore
-
-#endif // CorrectionPanelInfo_h
diff --git a/Source/WebCore/editing/DeleteButtonController.cpp b/Source/WebCore/editing/DeleteButtonController.cpp
index 332e68f..5e76c08 100644
--- a/Source/WebCore/editing/DeleteButtonController.cpp
+++ b/Source/WebCore/editing/DeleteButtonController.cpp
@@ -112,8 +112,12 @@ static bool isDeletableElement(const Node* node)
return false;
// Allow blocks that have background images
- if (style->hasBackgroundImage() && style->backgroundImage()->canRender(1.0f))
- return true;
+ if (style->hasBackgroundImage()) {
+ for (const FillLayer* background = style->backgroundLayers(); background; background = background->next()) {
+ if (background->image() && background->image()->canRender(1))
+ return true;
+ }
+ }
// Allow blocks with a minimum number of non-transparent borders
unsigned visibleBorders = style->borderTop().isVisible() + style->borderBottom().isVisible() + style->borderLeft().isVisible() + style->borderRight().isVisible();
@@ -188,7 +192,7 @@ void DeleteButtonController::createDeletionUI()
CSSMutableStyleDeclaration* style = container->getInlineStyleDecl();
style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone);
style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone);
- style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone);
+ style->setProperty(CSSPropertyWebkitUserModify, CSSValueReadOnly);
style->setProperty(CSSPropertyVisibility, CSSValueHidden);
style->setProperty(CSSPropertyPosition, CSSValueAbsolute);
style->setProperty(CSSPropertyCursor, CSSValueDefault);
diff --git a/Source/WebCore/editing/DeleteSelectionCommand.cpp b/Source/WebCore/editing/DeleteSelectionCommand.cpp
index cbebe54..529c71d 100644
--- a/Source/WebCore/editing/DeleteSelectionCommand.cpp
+++ b/Source/WebCore/editing/DeleteSelectionCommand.cpp
@@ -281,7 +281,7 @@ void DeleteSelectionCommand::saveTypingStyleState()
return;
// Figure out the typing style in effect before the delete is done.
- m_typingStyle = EditingStyle::create(positionBeforeTabSpan(m_selectionToDelete.start()));
+ m_typingStyle = EditingStyle::create(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()
@@ -335,6 +335,15 @@ static void updatePositionForNodeRemoval(Node* node, Position& position)
}
}
+static Position firstEditablePositionInNode(Node* node)
+{
+ ASSERT(node);
+ Node* next = node;
+ while (next && !next->rendererIsEditable())
+ next = next->traverseNextNode(node);
+ return next ? firstPositionInOrBeforeNode(next) : Position();
+}
+
void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
{
if (!node)
@@ -372,11 +381,14 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node)
removeNode(remove);
}
- // make sure empty cell has some height
+ // Make sure empty cell has some height, if a placeholder can be inserted.
updateLayout();
RenderObject *r = node->renderer();
- if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0)
- insertBlockPlaceholder(firstPositionInNode(node.get()));
+ if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0) {
+ Position firstEditablePosition = firstEditablePositionInNode(node.get());
+ if (firstEditablePosition.isNotNull())
+ insertBlockPlaceholder(firstEditablePosition);
+ }
return;
}
diff --git a/Source/WebCore/editing/EditingAllInOne.cpp b/Source/WebCore/editing/EditingAllInOne.cpp
index e4e0bbb..f38cbe6 100644
--- a/Source/WebCore/editing/EditingAllInOne.cpp
+++ b/Source/WebCore/editing/EditingAllInOne.cpp
@@ -63,6 +63,7 @@
#include <SetNodeAttributeCommand.cpp>
#include <SmartReplace.cpp>
#include <SmartReplaceCF.cpp>
+#include <SpellingCorrectionController.cpp>
#include <SpellChecker.cpp>
#include <SplitElementCommand.cpp>
#include <SplitTextNodeCommand.cpp>
diff --git a/Source/WebCore/editing/EditingBehavior.h b/Source/WebCore/editing/EditingBehavior.h
index a367c52..4ee85f3 100644
--- a/Source/WebCore/editing/EditingBehavior.h
+++ b/Source/WebCore/editing/EditingBehavior.h
@@ -59,6 +59,10 @@ public:
// On Mac, when processing a contextual click, the object being clicked upon should be selected.
bool shouldSelectOnContextualMenuClick() const { return m_type == EditingMacBehavior; }
+
+ // On Mac, when the web view loses focus, any active selection clears. On Windows, the selection
+ // should remain highlighted, just in an inactive state.
+ bool shouldClearSelectionWhenLosingWebPageFocus() const { return m_type == EditingMacBehavior; }
private:
EditingBehaviorType m_type;
diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp
index 668c943..9509ead 100644
--- a/Source/WebCore/editing/EditingStyle.cpp
+++ b/Source/WebCore/editing/EditingStyle.cpp
@@ -30,6 +30,8 @@
#include "ApplyStyleCommand.h"
#include "CSSComputedStyleDeclaration.h"
#include "CSSMutableStyleDeclaration.h"
+#include "CSSParser.h"
+#include "CSSStyleSelector.h"
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "Frame.h"
@@ -87,6 +89,8 @@ static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(Pass
return copyEditingProperties(style.get());
}
+static RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
+
class HTMLElementEquivalent {
public:
static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, int primitiveValue, const QualifiedName& tagName)
@@ -300,6 +304,11 @@ EditingStyle::~EditingStyle()
void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
{
+ if (isTabSpanTextNode(node))
+ node = tabSpanNode(node)->parentNode();
+ else if (isTabSpanNode(node))
+ node = node->parentNode();
+
RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node);
m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
@@ -768,4 +777,270 @@ void EditingStyle::mergeStyle(CSSMutableStyleDeclaration* style)
}
}
+static void reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style)
+{
+ RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
+ RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
+ // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
+ ASSERT(!textDecorationsInEffect || !textDecoration);
+ if (textDecorationsInEffect) {
+ style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
+ style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
+ textDecoration = textDecorationsInEffect;
+ }
+
+ // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
+ if (textDecoration && !textDecoration->isValueList())
+ style->removeProperty(CSSPropertyTextDecoration);
+}
+
+StyleChange::StyleChange(EditingStyle* style, const Position& position)
+ : m_applyBold(false)
+ , m_applyItalic(false)
+ , m_applyUnderline(false)
+ , m_applyLineThrough(false)
+ , m_applySubscript(false)
+ , m_applySuperscript(false)
+{
+ Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
+ if (!style || !style->style() || !document || !document->frame())
+ return;
+
+ RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
+ RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
+
+ reconcileTextDecorationProperties(mutableStyle.get());
+ if (!document->frame()->editor()->shouldStyleWithCSS())
+ extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
+
+ // Changing the whitespace style in a tab span would collapse the tab into a space.
+ if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
+ mutableStyle->removeProperty(CSSPropertyWhiteSpace);
+
+ // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
+ // FIXME: Shouldn't this be done in getPropertiesNotIn?
+ if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
+ mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
+
+ // Save the result for later
+ m_cssStyle = mutableStyle->cssText().stripWhiteSpace();
+}
+
+static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID)
+{
+ if (newTextDecoration->length())
+ style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID));
+ else {
+ // text-decoration: none is redundant since it does not remove any text decorations.
+ ASSERT(!style->getPropertyPriority(propertyID));
+ style->removeProperty(propertyID);
+ }
+}
+
+static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
+{
+ RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor);
+ if (!colorValue || !colorValue->isPrimitiveValue())
+ return Color::transparent;
+
+ CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get());
+ if (primitiveColor->primitiveType() == CSSPrimitiveValue::CSS_RGBCOLOR)
+ return primitiveColor->getRGBA32Value();
+
+ // Need to take care of named color such as green and black
+ // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed.
+ RGBA32 rgba = 0;
+ CSSParser::parseColor(rgba, colorValue->cssText());
+ return rgba;
+}
+
+void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefaultSize)
+{
+ ASSERT(style);
+
+ if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
+ style->removeProperty(CSSPropertyFontWeight);
+ m_applyBold = true;
+ }
+
+ int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
+ if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
+ style->removeProperty(CSSPropertyFontStyle);
+ m_applyItalic = true;
+ }
+
+ // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
+ // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
+ RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
+ if (textDecoration && textDecoration->isValueList()) {
+ DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
+ DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
+
+ RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
+ if (newTextDecoration->removeAll(underline.get()))
+ m_applyUnderline = true;
+ if (newTextDecoration->removeAll(lineThrough.get()))
+ m_applyLineThrough = true;
+
+ // If trimTextDecorations, delete underline and line-through
+ setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
+ }
+
+ int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
+ switch (verticalAlign) {
+ case CSSValueSub:
+ style->removeProperty(CSSPropertyVerticalAlign);
+ m_applySubscript = true;
+ break;
+ case CSSValueSuper:
+ style->removeProperty(CSSPropertyVerticalAlign);
+ m_applySuperscript = true;
+ break;
+ }
+
+ if (style->getPropertyCSSValue(CSSPropertyColor)) {
+ m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
+ style->removeProperty(CSSPropertyColor);
+ }
+
+ m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
+ style->removeProperty(CSSPropertyFontFamily);
+
+ if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
+ if (!fontSize->isPrimitiveValue())
+ style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
+ else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
+ shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
+ m_applyFontSize = String::number(legacyFontSize);
+ style->removeProperty(CSSPropertyFontSize);
+ }
+ }
+}
+
+static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration)
+{
+ RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
+ if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
+ return;
+
+ RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
+ CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
+
+ for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
+ newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
+
+ setTextDecorationProperty(style, newTextDecoration.get(), propertID);
+}
+
+static bool fontWeightIsBold(CSSStyleDeclaration* style)
+{
+ ASSERT(style);
+ RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
+
+ if (!fontWeight)
+ return false;
+ if (!fontWeight->isPrimitiveValue())
+ return false;
+
+ // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
+ // Collapse all other values to either one of these two states for editing purposes.
+ switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) {
+ case CSSValue100:
+ case CSSValue200:
+ case CSSValue300:
+ case CSSValue400:
+ case CSSValue500:
+ case CSSValueNormal:
+ return false;
+ case CSSValueBold:
+ case CSSValue600:
+ case CSSValue700:
+ case CSSValue800:
+ case CSSValue900:
+ return true;
+ }
+
+ ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
+ return false; // Make compiler happy
+}
+
+static int getTextAlignment(CSSStyleDeclaration* style)
+{
+ int textAlign = getIdentifierValue(style, CSSPropertyTextAlign);
+ switch (textAlign) {
+ case CSSValueCenter:
+ case CSSValueWebkitCenter:
+ return CSSValueCenter;
+ case CSSValueJustify:
+ return CSSValueJustify;
+ case CSSValueLeft:
+ case CSSValueWebkitLeft:
+ return CSSValueLeft;
+ case CSSValueRight:
+ case CSSValueWebkitRight:
+ return CSSValueRight;
+ }
+ return CSSValueInvalid;
+}
+
+RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
+{
+ ASSERT(styleWithRedundantProperties);
+ ASSERT(baseStyle);
+ RefPtr<CSSMutableStyleDeclaration> result = styleWithRedundantProperties->copy();
+ baseStyle->diff(result.get());
+
+ RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
+ diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
+ diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
+
+ if (fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
+ result->removeProperty(CSSPropertyFontWeight);
+
+ if (getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
+ result->removeProperty(CSSPropertyColor);
+
+ if (getTextAlignment(result.get()) == getTextAlignment(baseStyle))
+ result->removeProperty(CSSPropertyTextAlign);
+
+ return result;
+}
+
+
+int getIdentifierValue(CSSStyleDeclaration* style, int propertyID)
+{
+ if (!style)
+ return 0;
+
+ RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
+ if (!value || !value->isPrimitiveValue())
+ return 0;
+
+ return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
+}
+
+static bool isCSSValueLength(CSSPrimitiveValue* value)
+{
+ return value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC;
+}
+
+int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
+{
+ if (isCSSValueLength(value)) {
+ int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
+ int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
+ // Use legacy font size only if pixel value matches exactly to that of legacy font size.
+ int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
+ if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
+ return legacyFontSize;
+
+ return 0;
+ }
+
+ if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
+ return value->getIdent() - CSSValueXSmall + 1;
+
+ return 0;
+}
+
}
diff --git a/Source/WebCore/editing/EditingStyle.h b/Source/WebCore/editing/EditingStyle.h
index 37bfea7..257a2f9 100644
--- a/Source/WebCore/editing/EditingStyle.h
+++ b/Source/WebCore/editing/EditingStyle.h
@@ -32,6 +32,7 @@
#define EditingStyle_h
#include "CSSPropertyNames.h"
+#include "PlatformString.h"
#include "WritingDirection.h"
#include <wtf/Forward.h>
#include <wtf/RefCounted.h>
@@ -43,6 +44,7 @@ namespace WebCore {
class CSSStyleDeclaration;
class CSSComputedStyleDeclaration;
class CSSMutableStyleDeclaration;
+class CSSPrimitiveValue;
class Document;
class HTMLElement;
class Node;
@@ -144,6 +146,62 @@ private:
friend class HTMLAttributeEquivalent;
};
+class StyleChange {
+public:
+ StyleChange(EditingStyle*, const Position&);
+
+ String cssStyle() const { return m_cssStyle; }
+ bool applyBold() const { return m_applyBold; }
+ bool applyItalic() const { return m_applyItalic; }
+ bool applyUnderline() const { return m_applyUnderline; }
+ bool applyLineThrough() const { return m_applyLineThrough; }
+ bool applySubscript() const { return m_applySubscript; }
+ bool applySuperscript() const { return m_applySuperscript; }
+ bool applyFontColor() const { return m_applyFontColor.length() > 0; }
+ bool applyFontFace() const { return m_applyFontFace.length() > 0; }
+ bool applyFontSize() const { return m_applyFontSize.length() > 0; }
+
+ String fontColor() { return m_applyFontColor; }
+ String fontFace() { return m_applyFontFace; }
+ String fontSize() { return m_applyFontSize; }
+
+ bool operator==(const StyleChange& other)
+ {
+ return m_cssStyle == other.m_cssStyle
+ && m_applyBold == other.m_applyBold
+ && m_applyItalic == other.m_applyItalic
+ && m_applyUnderline == other.m_applyUnderline
+ && m_applyLineThrough == other.m_applyLineThrough
+ && m_applySubscript == other.m_applySubscript
+ && m_applySuperscript == other.m_applySuperscript
+ && m_applyFontColor == other.m_applyFontColor
+ && m_applyFontFace == other.m_applyFontFace
+ && m_applyFontSize == other.m_applyFontSize;
+ }
+ bool operator!=(const StyleChange& other)
+ {
+ return !(*this == other);
+ }
+private:
+ void extractTextStyles(Document*, CSSMutableStyleDeclaration*, bool shouldUseFixedFontDefaultSize);
+
+ String m_cssStyle;
+ bool m_applyBold;
+ bool m_applyItalic;
+ bool m_applyUnderline;
+ bool m_applyLineThrough;
+ bool m_applySubscript;
+ bool m_applySuperscript;
+ String m_applyFontColor;
+ String m_applyFontFace;
+ String m_applyFontSize;
+};
+
+// FIXME: Remove these functions or make them non-global to discourage using CSSStyleDeclaration directly.
+int getIdentifierValue(CSSStyleDeclaration*, int propertyID);
+enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
+int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
+
} // namespace WebCore
#endif // EditingStyle_h
diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp
index a793e0b..e7c5c1d 100644
--- a/Source/WebCore/editing/Editor.cpp
+++ b/Source/WebCore/editing/Editor.cpp
@@ -38,6 +38,7 @@
#include "CachedResourceLoader.h"
#include "ClipboardEvent.h"
#include "CompositionEvent.h"
+#include "SpellingCorrectionController.h"
#include "CreateLinkCommand.h"
#include "DeleteButtonController.h"
#include "DeleteSelectionCommand.h"
@@ -64,7 +65,6 @@
#include "NodeList.h"
#include "Page.h"
#include "Pasteboard.h"
-#include "TextCheckerClient.h"
#include "TextCheckingHelper.h"
#include "RemoveFormatCommand.h"
#include "RenderBlock.h"
@@ -94,35 +94,6 @@ using namespace HTMLNames;
using namespace WTF;
using namespace Unicode;
-static inline bool isAmbiguousBoundaryCharacter(UChar character)
-{
- // These are characters that can behave as word boundaries, but can appear within words.
- // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed.
- // FIXME: this is required until 6853027 is fixed and text checking can do this for us.
- return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim;
-}
-
-static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
-{
- DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
- if (markerTypesForAutoCorrection.isEmpty()) {
- markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
- markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
- markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
- }
- return markerTypesForAutoCorrection;
-}
-
-static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
-{
- DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
- if (markerTypesForReplacement.isEmpty()) {
- markerTypesForReplacement.append(DocumentMarker::Replacement);
- markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
- }
- return markerTypesForReplacement;
-}
-
// When an event handler has moved the selection outside of a text control
// we should use the target control's selection for this editing operation.
VisibleSelection Editor::selectionForCommand(Event* event)
@@ -210,7 +181,7 @@ bool Editor::handleTextEvent(TextEvent* event)
bool Editor::canEdit() const
{
- return m_frame->selection()->isContentEditable();
+ return m_frame->selection()->rootEditableElement();
}
bool Editor::canEditRichly() const
@@ -278,7 +249,7 @@ bool Editor::canPaste() const
bool Editor::canDelete() const
{
SelectionController* selection = m_frame->selection();
- return selection->isRange() && selection->isContentEditable();
+ return selection->isRange() && selection->rootEditableElement();
}
bool Editor::canDeleteRange(Range* range) const
@@ -324,7 +295,7 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g
if (m_frame->selection()->isRange()) {
if (isTypingAction) {
- TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity);
+ TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete() ? TypingCommand::SmartDelete : 0, granularity);
revealSelectionAfterEditingOperation();
} else {
if (killRing)
@@ -333,14 +304,19 @@ bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity g
// Implicitly calls revealSelectionAfterEditingOperation().
}
} else {
+ TypingCommand::Options options = 0;
+ if (canSmartCopyOrDelete())
+ options |= TypingCommand::SmartDelete;
+ if (killRing)
+ options |= TypingCommand::KillRing;
switch (direction) {
case DirectionForward:
case DirectionRight:
- TypingCommand::forwardDeleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
+ TypingCommand::forwardDeleteKeyPressed(m_frame->document(), options, granularity);
break;
case DirectionBackward:
case DirectionLeft:
- TypingCommand::deleteKeyPressed(m_frame->document(), canSmartCopyOrDelete(), granularity, killRing);
+ TypingCommand::deleteKeyPressed(m_frame->document(), options, granularity);
break;
}
revealSelectionAfterEditingOperation();
@@ -440,7 +416,7 @@ void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment,
Node* nodeToCheck = m_frame->selection()->rootEditableElement();
if (m_spellChecker->canCheckAsynchronously(nodeToCheck))
- m_spellChecker->requestCheckingFor(nodeToCheck);
+ m_spellChecker->requestCheckingFor(textCheckingTypeMaskFor(MarkSpelling | MarkGrammar), nodeToCheck);
}
void Editor::replaceSelectionWithText(const String& text, bool selectReplacement, bool smartReplace)
@@ -518,48 +494,7 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection)
if (client())
client()->respondToChangedSelection();
m_deleteButtonController->respondToChangedSelection(oldSelection);
-
-#if SUPPORT_AUTOCORRECTION_PANEL
- VisibleSelection currentSelection(frame()->selection()->selection());
- // When user moves caret to the end of autocorrected word and pauses, we show the panel
- // containing the original pre-correction word so that user can quickly revert the
- // undesired autocorrection. Here, we start correction panel timer once we confirm that
- // 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::Replacement && !marker.description.isNull()) || marker.type == DocumentMarker::Spelling) && 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()) {
- m_correctionPanelInfo.rangeToBeReplaced = wordRange;
- m_correctionPanelInfo.replacedString = currentWord;
- if (marker.type == DocumentMarker::Spelling)
- startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions);
- else {
- m_correctionPanelInfo.replacementString = marker.description;
- startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion);
- }
- }
- break;
- }
- }
-#endif // SUPPORT_AUTOCORRECTION_PANEL
+ m_spellingCorrector->respondToChangedSelection(oldSelection);
}
void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
@@ -570,9 +505,7 @@ void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
m_frame->document()->axObjectCache()->postNotification(node->renderer(), AXObjectCache::AXValueChanged, false);
}
-#if REMOVE_MARKERS_UPON_EDITING
- removeSpellAndCorrectionMarkersFromWordsToBeEdited(true);
-#endif
+ updateMarkersForWordsAffectedByEditing(true);
if (client())
client()->respondToChangedContents();
@@ -983,7 +916,7 @@ TriState Editor::selectionHasStyle(int propertyID, const String& value) const
return state;
}
-static bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
+bool Editor::hasTransparentBackgroundColor(CSSStyleDeclaration* style)
{
RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
if (!cssValue)
@@ -1059,10 +992,7 @@ void Editor::appliedEditing(PassRefPtr<EditCommand> cmd)
dispatchEditableContentChangedEvents(*cmd);
VisibleSelection newSelection(cmd->endingSelection());
-#if SUPPORT_AUTOCORRECTION_PANEL
- if (cmd->isTopLevelCommand() && !cmd->shouldRetainAutocorrectionIndicator())
- m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
-#endif
+ m_spellingCorrector->respondToAppliedEditing(cmd.get());
// Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
changeSelectionAfterCommand(newSelection, false, false);
@@ -1121,17 +1051,14 @@ Editor::Editor(Frame* frame)
// This is off by default, since most editors want this behavior (this matches IE but not FF).
, m_shouldStyleWithCSS(false)
, m_killRing(adoptPtr(new KillRing))
- , m_spellChecker(new SpellChecker(frame, frame->page() ? frame->page()->editorClient()->textChecker() : 0))
- , m_correctionPanelTimer(this, &Editor::correctionPanelTimerFired)
+ , m_spellChecker(adoptPtr(new SpellChecker(frame, frame->page() ? frame->page()->editorClient()->textChecker() : 0)))
+ , m_spellingCorrector(adoptPtr(new SpellingCorrectionController(frame)))
, m_areMarkedTextMatchesHighlighted(false)
{
}
Editor::~Editor()
{
-#if SUPPORT_AUTOCORRECTION_PANEL
- dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
-#endif
}
void Editor::clear()
@@ -1164,10 +1091,8 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
if (!shouldInsertText(text, range.get(), EditorInsertActionTyped))
return true;
-#if REMOVE_MARKERS_UPON_EDITING
if (!text.isEmpty())
- removeSpellAndCorrectionMarkersFromWordsToBeEdited(isSpaceOrNewline(text[0]));
-#endif
+ updateMarkersForWordsAffectedByEditing(text[0]);
bool shouldConsiderApplyingAutocorrection = false;
if (text == " " || text == "\t")
@@ -1176,7 +1101,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
if (text.length() == 1 && isPunct(text[0]) && !isAmbiguousBoundaryCharacter(text[0]))
shouldConsiderApplyingAutocorrection = true;
- bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && applyAutocorrectionBeforeTypingIfAppropriate();
+ bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate();
// Get the selection to use for the event that triggered this insertText.
// If the event handler changed the selection, we may want to use a different selection
@@ -1187,7 +1112,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
RefPtr<Document> document = selectionStart->document();
// Insert the text
- TypingCommand::TypingCommandOptions options = 0;
+ TypingCommand::Options options = 0;
if (selectInsertedText)
options |= TypingCommand::SelectInsertedText;
if (autocorrectionWasApplied)
@@ -1212,7 +1137,7 @@ bool Editor::insertLineBreak()
if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped))
return true;
- bool autocorrectionIsApplied = applyAutocorrectionBeforeTypingIfAppropriate();
+ bool autocorrectionIsApplied = m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate();
TypingCommand::insertLineBreak(m_frame->document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0);
revealSelectionAfterEditingOperation();
@@ -1230,7 +1155,7 @@ bool Editor::insertParagraphSeparator()
if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped))
return true;
- bool autocorrectionIsApplied = applyAutocorrectionBeforeTypingIfAppropriate();
+ bool autocorrectionIsApplied = m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate();
TypingCommand::insertParagraphSeparator(m_frame->document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0);
revealSelectionAfterEditingOperation();
@@ -1247,9 +1172,7 @@ void Editor::cut()
}
RefPtr<Range> selection = selectedRange();
if (shouldDeleteRange(selection.get())) {
-#if REMOVE_MARKERS_UPON_EDITING
- removeSpellAndCorrectionMarkersFromWordsToBeEdited(true);
-#endif
+ updateMarkersForWordsAffectedByEditing(true);
if (isNodeInTextFormControl(m_frame->selection()->start().deprecatedNode()))
Pasteboard::generalPasteboard()->writePlainText(selectedText());
else
@@ -1288,9 +1211,7 @@ void Editor::paste()
return; // DHTML did the whole operation
if (!canPaste())
return;
-#if REMOVE_MARKERS_UPON_EDITING
- removeSpellAndCorrectionMarkersFromWordsToBeEdited(false);
-#endif
+ updateMarkersForWordsAffectedByEditing(false);
CachedResourceLoader* loader = m_frame->document()->cachedResourceLoader();
loader->setAllowStaleResources(true);
if (m_frame->selection()->isContentRichlyEditable())
@@ -1306,9 +1227,7 @@ void Editor::pasteAsPlainText()
return;
if (!canPaste())
return;
-#if REMOVE_MARKERS_UPON_EDITING
- removeSpellAndCorrectionMarkersFromWordsToBeEdited(false);
-#endif
+ updateMarkersForWordsAffectedByEditing(false);
pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard());
}
@@ -1461,7 +1380,7 @@ void Editor::toggleAutomaticTextReplacement()
bool Editor::isAutomaticSpellingCorrectionEnabled()
{
- return client() && client()->isAutomaticSpellingCorrectionEnabled();
+ return m_spellingCorrector->isAutomaticSpellingCorrectionEnabled();
}
void Editor::toggleAutomaticSpellingCorrection()
@@ -1614,7 +1533,7 @@ void Editor::confirmComposition(const String& text, bool preserveSelection)
// If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
// will delete the old composition with an optimized replace operation.
if (text.isEmpty())
- TypingCommand::deleteSelection(m_frame->document(), false);
+ TypingCommand::deleteSelection(m_frame->document(), 0);
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
@@ -1682,13 +1601,13 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin
// If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
// will delete the old composition with an optimized replace operation.
if (text.isEmpty())
- TypingCommand::deleteSelection(m_frame->document(), false);
+ TypingCommand::deleteSelection(m_frame->document(), TypingCommand::PreventSpellChecking);
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
if (!text.isEmpty()) {
- TypingCommand::insertText(m_frame->document(), text, true, TypingCommand::TextCompositionUpdate);
+ TypingCommand::insertText(m_frame->document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
// Find out what node has the composition now.
Position base = m_frame->selection()->base().downstream();
@@ -2050,39 +1969,24 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti
markBadGrammar(movingSelection);
}
-void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping)
+void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement)
{
#if USE(UNIFIED_TEXT_CHECKING)
-#if SUPPORT_AUTOCORRECTION_PANEL
- // Apply pending autocorrection before next round of spell checking.
- bool doApplyCorrection = true;
- VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
- VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
- if (currentWord.visibleEnd() == startOfSelection) {
- String wordText = plainText(currentWord.toNormalizedRange().get());
- if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
- doApplyCorrection = false;
- }
- if (doApplyCorrection)
- handleCorrectionPanelResult(dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanelAccepted));
- else
- m_correctionPanelInfo.rangeToBeReplaced.clear();
-#else
- UNUSED_PARAM(selectionAfterTyping);
-#endif
+ m_spellingCorrector->applyPendingCorrection(selectionAfterTyping);
+
TextCheckingOptions textCheckingOptions = 0;
if (isContinuousSpellCheckingEnabled())
textCheckingOptions |= MarkSpelling;
#if USE(AUTOMATIC_TEXT_REPLACEMENT)
- if (isAutomaticQuoteSubstitutionEnabled()
- || isAutomaticLinkDetectionEnabled()
- || isAutomaticDashSubstitutionEnabled()
- || isAutomaticTextReplacementEnabled()
- || ((textCheckingOptions & MarkSpelling) && isAutomaticSpellingCorrectionEnabled()))
+ if (doReplacement
+ && (isAutomaticQuoteSubstitutionEnabled()
+ || isAutomaticLinkDetectionEnabled()
+ || isAutomaticDashSubstitutionEnabled()
+ || isAutomaticTextReplacementEnabled()
+ || ((textCheckingOptions & MarkSpelling) && isAutomaticSpellingCorrectionEnabled())))
textCheckingOptions |= PerformReplacement;
#endif
-
if (!textCheckingOptions & (MarkSpelling | PerformReplacement))
return;
@@ -2099,6 +2003,8 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart,
#else
UNUSED_PARAM(selectionAfterTyping);
+ UNUSED_PARAM(doReplacement);
+
if (!isContinuousSpellCheckingEnabled())
return;
@@ -2207,12 +2113,13 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
{
#if USE(UNIFIED_TEXT_CHECKING)
// There shouldn't be pending autocorrection at this moment.
- ASSERT(!m_correctionPanelInfo.rangeToBeReplaced);
+ ASSERT(!m_spellingCorrector->hasPendingCorrection());
bool shouldMarkSpelling = textCheckingOptions & MarkSpelling;
bool shouldMarkGrammar = textCheckingOptions & MarkGrammar;
bool shouldPerformReplacement = textCheckingOptions & PerformReplacement;
bool shouldShowCorrectionPanel = textCheckingOptions & ShowCorrectionPanel;
+ bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & CheckForCorrection);
// This function is called with selections already expanded to word boundaries.
ExceptionCode ec = 0;
@@ -2240,7 +2147,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
if (shouldMarkGrammar ? (spellingParagraph.isRangeEmpty() && grammarParagraph.isEmpty()) : spellingParagraph.isEmpty())
return;
- if (shouldPerformReplacement || shouldMarkSpelling) {
+ if (shouldPerformReplacement || shouldMarkSpelling || shouldCheckForCorrection) {
if (m_frame->selection()->selectionType() == VisibleSelection::CaretSelection) {
// Attempt to save the caret position so we can restore it later if needed
Position caretPosition = m_frame->selection()->end();
@@ -2257,38 +2164,17 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
}
Vector<TextCheckingResult> results;
- uint64_t checkingTypes = 0;
- if (shouldMarkSpelling)
- checkingTypes |= TextCheckingTypeSpelling;
- if (shouldMarkGrammar)
- checkingTypes |= TextCheckingTypeGrammar;
- if (shouldShowCorrectionPanel)
- checkingTypes |= TextCheckingTypeCorrection;
- if (shouldPerformReplacement) {
-#if USE(AUTOMATIC_TEXT_REPLACEMENT)
- if (isAutomaticLinkDetectionEnabled())
- checkingTypes |= TextCheckingTypeLink;
- if (isAutomaticQuoteSubstitutionEnabled())
- checkingTypes |= TextCheckingTypeQuote;
- if (isAutomaticDashSubstitutionEnabled())
- checkingTypes |= TextCheckingTypeDash;
- if (isAutomaticTextReplacementEnabled())
- checkingTypes |= TextCheckingTypeReplacement;
- if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
- checkingTypes |= TextCheckingTypeCorrection;
-#endif
- }
if (shouldMarkGrammar)
- textChecker()->checkTextOfParagraph(grammarParagraph.textCharacters(), grammarParagraph.textLength(), checkingTypes, results);
+ textChecker()->checkTextOfParagraph(grammarParagraph.textCharacters(), grammarParagraph.textLength(),
+ textCheckingTypeMaskFor(textCheckingOptions), results);
else
- textChecker()->checkTextOfParagraph(spellingParagraph.textCharacters(), spellingParagraph.textLength(), checkingTypes, results);
+ textChecker()->checkTextOfParagraph(spellingParagraph.textCharacters(), spellingParagraph.textLength(),
+ textCheckingTypeMaskFor(textCheckingOptions), results);
-#if SUPPORT_AUTOCORRECTION_PANEL
// If this checking is only for showing correction panel, we shouldn't bother to mark misspellings.
if (shouldShowCorrectionPanel)
shouldMarkSpelling = false;
-#endif
int offsetDueToReplacement = 0;
@@ -2307,10 +2193,8 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= spellingParagraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
ASSERT(resultLength > 0 && resultLocation >= 0);
RefPtr<Range> misspellingRange = spellingParagraph.subrange(resultLocation, resultLength);
-#if SUPPORT_AUTOCORRECTION_PANEL
- if (m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption))
+ if (!m_spellingCorrector->isSpellingMarkerAllowed(misspellingRange))
continue;
-#endif // SUPPORT_AUTOCORRECTION_PANEL
misspellingRange->startContainer(ec)->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
} else if (shouldMarkGrammar && result->type == TextCheckingTypeGrammar && grammarParagraph.checkingRangeCovers(resultLocation, resultLength)) {
ASSERT(resultLength > 0 && resultLocation >= 0);
@@ -2322,7 +2206,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
grammarRange->startContainer(ec)->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
}
}
- } else if ((shouldPerformReplacement || shouldShowCorrectionPanel) && resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingParagraph.checkingStart()
+ } else if (resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= spellingParagraph.checkingStart()
&& (result->type == TextCheckingTypeLink
|| result->type == TextCheckingTypeQuote
|| result->type == TextCheckingTypeDash
@@ -2348,45 +2232,33 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
if (result->type == TextCheckingTypeLink && selectionOffset > resultLocation + resultLength + 1)
continue;
+ String replacedString;
+
// Don't correct spelling in an already-corrected word.
if (result->type == TextCheckingTypeCorrection) {
- Node* node = rangeToReplace->startContainer();
- int startOffset = rangeToReplace->startOffset();
- int endOffset = startOffset + replacementLength;
- 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::Replacement || marker.type == DocumentMarker::RejectedCorrection) && static_cast<int>(marker.startOffset) < endOffset && static_cast<int>(marker.endOffset) > startOffset) {
- doReplacement = false;
- break;
- }
- if (static_cast<int>(marker.startOffset) >= endOffset)
- break;
- }
+ replacedString = plainText(rangeToReplace.get());
+ DocumentMarkerController* markers = m_frame->document()->markers();
+ if (markers->hasMarkers(rangeToReplace.get(), DocumentMarker::Replacement)) {
+ doReplacement = false;
+ m_spellingCorrector->recordSpellcheckerResponseForModifiedCorrection(rangeToReplace.get(), replacedString, result->replacement);
+ } else if (markers->hasMarkers(rangeToReplace.get(), DocumentMarker::RejectedCorrection))
+ doReplacement = false;
}
- if (!doReplacement)
+ if (!(shouldPerformReplacement || shouldShowCorrectionPanel) || !doReplacement)
continue;
-#if SUPPORT_AUTOCORRECTION_PANEL
if (shouldShowCorrectionPanel) {
+ ASSERT(SUPPORT_AUTOCORRECTION_PANEL);
+ // shouldShowCorrectionPanel can be true only when the panel is available.
if (resultLocation + resultLength == spellingRangeEndOffset) {
// We only show the correction panel on the last word.
- FloatRect boundingBox = windowRectForRange(rangeToReplace.get());
- if (boundingBox.isEmpty())
- break;
- m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace;
- m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get());
- m_correctionPanelInfo.replacementString = result->replacement;
- m_correctionPanelInfo.isActive = true;
- client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, result->replacement, Vector<String>());
+ m_spellingCorrector->show(rangeToReplace, result->replacement);
break;
}
// If this function is called for showing correction panel, we ignore other correction or replacement.
continue;
}
-#endif
if (selectionToReplace != m_frame->selection()->selection()) {
if (!m_frame->selection()->shouldChangeSelection(selectionToReplace))
@@ -2400,22 +2272,18 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
if (canEditRichly())
applyCommand(CreateLinkCommand::create(m_frame->document(), result->replacement));
} else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
- String replacedString;
if (result->type == TextCheckingTypeCorrection)
- replacedString = plainText(rangeToReplace.get());
-
- bool useSpellingCorrectionCommand = false;
-#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
- if (result->type == TextCheckingTypeCorrection)
- useSpellingCorrectionCommand = true;
-#endif
- if (useSpellingCorrectionCommand)
applyCommand(SpellingCorrectionCommand::create(rangeToReplace, result->replacement));
else {
m_frame->selection()->setSelection(selectionToReplace);
replaceSelectionWithText(result->replacement, false, false);
}
+ if (AXObjectCache::accessibilityEnabled()) {
+ if (Element* root = m_frame->selection()->selection().rootEditableElement())
+ m_frame->document()->axObjectCache()->postNotification(root->renderer(), AXObjectCache::AXAutocorrectionOccured, true);
+ }
+
selectionChanged = true;
offsetDueToReplacement += replacementLength - resultLength;
if (resultLocation < selectionOffset) {
@@ -2424,13 +2292,9 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
ambiguousBoundaryOffset = selectionOffset - 1;
}
- if (result->type == TextCheckingTypeCorrection) {
- // Add a marker so that corrections can easily be undone and won't be re-corrected.
- RefPtr<Range> replacedRange = spellingParagraph.subrange(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::SpellCheckingExemption);
- }
+ // Add a marker so that corrections can easily be undone and won't be re-corrected.
+ if (result->type == TextCheckingTypeCorrection)
+ m_spellingCorrector->markCorrection(spellingParagraph.subrange(resultLocation, replacementLength), replacedString);
}
}
}
@@ -2467,17 +2331,12 @@ void Editor::changeBackToReplacedString(const String& replacedString)
if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted))
return;
-#if SUPPORT_AUTOCORRECTION_PANEL
- String replacement = plainText(selection.get());
- client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacement);
-#endif
+ m_spellingCorrector->recordAutocorrectionResponseReversed(replacedString, selection);
TextCheckingParagraph paragraph(selection);
replaceSelectionWithText(replacedString, false, false);
RefPtr<Range> changedRange = paragraph.subrange(paragraph.checkingStart(), replacedString.length());
changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::Replacement, String());
-#if SUPPORT_AUTOCORRECTION_PANEL
- changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
-#endif
+ m_spellingCorrector->markReversed(changedRange.get());
#else
ASSERT_NOT_REACHED();
UNUSED_PARAM(replacedString);
@@ -2490,7 +2349,7 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec
#if USE(UNIFIED_TEXT_CHECKING)
if (!isContinuousSpellCheckingEnabled())
return;
- TextCheckingOptions textCheckingOptions = MarkSpelling;
+ TextCheckingOptions textCheckingOptions = MarkSpelling | CheckForCorrection;
if (markGrammar && isGrammarCheckingEnabled())
textCheckingOptions |= MarkGrammar;
markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellingSelection.toNormalizedRange().get(), grammarSelection.toNormalizedRange().get());
@@ -2502,140 +2361,16 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec
#endif
}
-void Editor::correctionPanelTimerFired(Timer<Editor>*)
-{
-#if SUPPORT_AUTOCORRECTION_PANEL
- m_correctionPanelIsDismissedByEditor = false;
- switch (m_correctionPanelInfo.panelType) {
- case 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);
- }
- break;
- case CorrectionPanelInfo::PanelTypeReversion: {
- m_correctionPanelInfo.isActive = true;
- m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
- FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
- if (!boundingBox.isEmpty())
- client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>());
- }
- break;
- case CorrectionPanelInfo::PanelTypeSpellingSuggestions: {
- if (plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString)
- break;
- String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get());
- Vector<String> suggestions;
- textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions);
- if (suggestions.isEmpty()) {
- m_correctionPanelInfo.rangeToBeReplaced.clear();
- break;
- }
- String topSuggestion = suggestions.first();
- suggestions.remove(0);
- m_correctionPanelInfo.isActive = true;
- FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
- if (!boundingBox.isEmpty())
- client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions);
- }
- break;
- }
-#endif
-}
-
-void Editor::handleCorrectionPanelResult(const String& correction)
-{
- Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get();
- if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
- return;
-
- String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
- // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
- if (currentWord != m_correctionPanelInfo.replacedString)
- return;
-
- m_correctionPanelInfo.isActive = false;
-
- switch (m_correctionPanelInfo.panelType) {
- case CorrectionPanelInfo::PanelTypeCorrection:
- if (correction.length()) {
- m_correctionPanelInfo.replacementString = correction;
- applyCorrectionPanelInfo(markerTypesForAutocorrection());
- } else {
- if (!m_correctionPanelIsDismissedByEditor)
- replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString);
- }
- break;
- case CorrectionPanelInfo::PanelTypeReversion:
- case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
- if (correction.length()) {
- m_correctionPanelInfo.replacementString = correction;
- applyCorrectionPanelInfo(markerTypesForReplacement());
- }
- break;
- }
-
- m_correctionPanelInfo.rangeToBeReplaced.clear();
-}
-
-void Editor::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)
-{
-#if SUPPORT_AUTOCORRECTION_PANEL
- const double correctionPanelTimerInterval = 0.3;
- if (isAutomaticSpellingCorrectionEnabled()) {
- if (type == CorrectionPanelInfo::PanelTypeCorrection)
- // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
- m_correctionPanelInfo.rangeToBeReplaced.clear();
- m_correctionPanelInfo.panelType = type;
- m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
- }
-#else
- UNUSED_PARAM(type);
-#endif
-}
-
-void Editor::stopCorrectionPanelTimer()
+void Editor::unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
{
-#if SUPPORT_AUTOCORRECTION_PANEL
- m_correctionPanelTimer.stop();
- m_correctionPanelInfo.rangeToBeReplaced.clear();
-#endif
+ m_spellingCorrector->respondToUnappliedSpellCorrection(selectionOfCorrected, corrected, correction);
}
-void Editor::dismissCorrectionPanel(ReasonForDismissingCorrectionPanel reasonForDismissing)
+void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)
{
-#if SUPPORT_AUTOCORRECTION_PANEL
- if (!m_correctionPanelInfo.isActive)
+ if (!m_spellingCorrector->shouldRemoveMarkersUponEditing())
return;
- m_correctionPanelInfo.isActive = false;
- m_correctionPanelIsDismissedByEditor = true;
- if (client())
- client()->dismissCorrectionPanel(reasonForDismissing);
-#else
- UNUSED_PARAM(reasonForDismissing);
-#endif
-}
-String Editor::dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
-{
-#if SUPPORT_AUTOCORRECTION_PANEL
- if (!m_correctionPanelInfo.isActive)
- return String();
- m_correctionPanelInfo.isActive = false;
- m_correctionPanelIsDismissedByEditor = true;
- if (!client())
- return String();
- return client()->dismissCorrectionPanelSoon(reasonForDismissing);
-#else
- UNUSED_PARAM(reasonForDismissing);
- return String();
-#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
// several scenarios:
// 1. Insert in the middle of a word.
@@ -2701,102 +2436,6 @@ void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemove
document->markers()->clearDescriptionOnMarkersIntersectingRange(wordRange.get(), DocumentMarker::Replacement);
}
-void Editor::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
-{
-#if SUPPORT_AUTOCORRECTION_PANEL
- if (!m_correctionPanelInfo.rangeToBeReplaced)
- return;
-
- ExceptionCode ec = 0;
- RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
- if (ec)
- return;
-
- setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition()));
- setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition()));
-
- // After we replace the word at range rangeToBeReplaced, we need to add markers to that range.
- // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore.
- // So before we carry out the replacement, we need to store the start position of 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 rangeToBeReplaced.
- RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
- if (ec)
- return;
-
- Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.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());
-
- // Clone the range, since the caller of this method may want to keep the original range around.
- RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
- applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString));
- setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
- RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.replacementString.length());
- String newText = plainText(replacementRange.get());
-
- // Check to see if replacement succeeded.
- if (newText != m_correctionPanelInfo.replacementString)
- return;
-
- DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
- size_t size = markerTypesToAdd.size();
- for (size_t i = 0; i < size; ++i) {
- if (m_correctionPanelInfo.panelType == CorrectionPanelInfo::PanelTypeReversion)
- markers->addMarker(replacementRange.get(), markerTypesToAdd[i]);
- else
- markers->addMarker(replacementRange.get(), markerTypesToAdd[i], m_correctionPanelInfo.replacedString);
- }
-#else // SUPPORT_AUTOCORRECTION_PANEL
- UNUSED_PARAM(markerTypesToAdd);
-#endif // SUPPORT_AUTOCORRECTION_PANEL
-}
-
-bool Editor::applyAutocorrectionBeforeTypingIfAppropriate()
-{
- if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive)
- return false;
-
- if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection)
- return false;
-
- Position caretPosition = m_frame->selection()->selection().start();
-
- if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) {
- handleCorrectionPanelResult(dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanelAccepted));
- return true;
- }
-
- // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction.
- ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition);
- dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
- return false;
-}
-
-void Editor::unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
-{
-#if SUPPORT_AUTOCORRECTION_PANEL
- client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
- m_frame->document()->updateLayout();
- m_frame->selection()->setSelection(selectionOfCorrected, SelectionController::CloseTyping | SelectionController::ClearTypingStyle | SelectionController::SpellCorrectionTriggered);
- RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
-
- DocumentMarkerController* markers = m_frame->document()->markers();
- markers->removeMarkers(range.get(), DocumentMarker::Spelling);
- markers->addMarker(range.get(), DocumentMarker::Replacement);
- markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
-#else // SUPPORT_AUTOCORRECTION_PANEL
- UNUSED_PARAM(selectionOfCorrected);
- UNUSED_PARAM(corrected);
- UNUSED_PARAM(correction);
-#endif // SUPPORT_AUTOCORRECTION_PANEL
-}
-
PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
{
Document* document = m_frame->documentAtPoint(windowPoint);
@@ -2919,6 +2558,22 @@ void Editor::addToKillRing(Range* range, bool prepend)
m_shouldStartNewKillRingSequence = false;
}
+void Editor::startCorrectionPanelTimer()
+{
+ m_spellingCorrector->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection);
+}
+
+void Editor::handleCorrectionPanelResult(const String& correction)
+{
+ m_spellingCorrector->handleCorrectionPanelResult(correction);
+}
+
+
+void Editor::dismissCorrectionPanelAsIgnored()
+{
+ m_spellingCorrector->dismiss(ReasonForDismissingCorrectionPanelIgnored);
+}
+
bool Editor::insideVisibleArea(const IntPoint& point) const
{
if (m_frame->excludeFromTextSearch())
@@ -3491,14 +3146,7 @@ void Editor::setMarkedTextMatchesAreHighlighted(bool flag)
void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, SelectionController::SetSelectionOptions options)
{
-#if SUPPORT_AUTOCORRECTION_PANEL
- // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
- VisibleSelection currentSelection(frame()->selection()->selection());
- if (currentSelection != oldSelection) {
- stopCorrectionPanelTimer();
- dismissCorrectionPanel(ReasonForDismissingCorrectionPanelIgnored);
- }
-#endif // SUPPORT_AUTOCORRECTION_PANEL
+ m_spellingCorrector->stopPendingCorrection(oldSelection);
bool closeTyping = options & SelectionController::CloseTyping;
bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
@@ -3514,11 +3162,8 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Sel
newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
}
- bool shouldCheckSpellingAndGrammar = true;
-#if SUPPORT_AUTOCORRECTION_PANEL
// Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
- shouldCheckSpellingAndGrammar = !(options & SelectionController::SpellCorrectionTriggered);
-#endif
+ bool shouldCheckSpellingAndGrammar = !(options & SelectionController::SpellCorrectionTriggered);
// When typing we check spelling elsewhere, so don't redo it here.
// If this is a change in selection resulting from a delete operation,
@@ -3593,7 +3238,49 @@ bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, i
FloatRect Editor::windowRectForRange(const Range* range) const
{
FrameView* view = frame()->view();
- return view ? view->contentsToWindow(IntRect(range->boundingRect())) : FloatRect();
+ if (!view)
+ return FloatRect();
+ Vector<FloatQuad> textQuads;
+ range->textQuads(textQuads);
+ FloatRect boundingRect;
+ size_t size = textQuads.size();
+ for (size_t i = 0; i < size; ++i)
+ boundingRect.unite(textQuads[i].boundingBox());
+ return view->contentsToWindow(IntRect(boundingRect));
}
+TextCheckingTypeMask Editor::textCheckingTypeMaskFor(TextCheckingOptions textCheckingOptions)
+{
+ bool shouldMarkSpelling = textCheckingOptions & MarkSpelling;
+ bool shouldMarkGrammar = textCheckingOptions & MarkGrammar;
+ bool shouldShowCorrectionPanel = textCheckingOptions & ShowCorrectionPanel;
+ bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & CheckForCorrection);
+
+ TextCheckingTypeMask checkingTypes = 0;
+ if (shouldMarkSpelling)
+ checkingTypes |= TextCheckingTypeSpelling;
+ if (shouldMarkGrammar)
+ checkingTypes |= TextCheckingTypeGrammar;
+ if (shouldCheckForCorrection)
+ checkingTypes |= TextCheckingTypeCorrection;
+
+#if USE(AUTOMATIC_TEXT_REPLACEMENT)
+ bool shouldPerformReplacement = textCheckingOptions & PerformReplacement;
+ if (shouldPerformReplacement) {
+ if (isAutomaticLinkDetectionEnabled())
+ checkingTypes |= TextCheckingTypeLink;
+ if (isAutomaticQuoteSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeQuote;
+ if (isAutomaticDashSubstitutionEnabled())
+ checkingTypes |= TextCheckingTypeDash;
+ if (isAutomaticTextReplacementEnabled())
+ checkingTypes |= TextCheckingTypeReplacement;
+ if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
+ checkingTypes |= TextCheckingTypeCorrection;
+ }
+#endif
+
+ return checkingTypes;
+}
+
} // namespace WebCore
diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h
index c723ddf..24d5c87 100644
--- a/Source/WebCore/editing/Editor.h
+++ b/Source/WebCore/editing/Editor.h
@@ -28,7 +28,6 @@
#include "ClipboardAccessPolicy.h"
#include "Color.h"
-#include "CorrectionPanelInfo.h"
#include "DocumentMarker.h"
#include "EditAction.h"
#include "EditingBehavior.h"
@@ -51,6 +50,7 @@ namespace WebCore {
class CSSMutableStyleDeclaration;
class CSSStyleDeclaration;
class Clipboard;
+class SpellingCorrectionController;
class DeleteButtonController;
class EditCommand;
class EditorClient;
@@ -198,6 +198,7 @@ public:
Command command(const String& commandName); // Command source is CommandFromMenuOrKeyBinding.
Command command(const String& commandName, EditorCommandSource);
static bool commandIsSupportedFromMenuOrKeyBinding(const String& commandName); // Works without a frame.
+ static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
bool insertText(const String&, Event* triggeringEvent);
bool insertTextForConfirmedComposition(const String& text);
@@ -219,10 +220,20 @@ public:
Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical);
bool isSpellCheckingEnabledInFocusedNode() const;
bool isSpellCheckingEnabledFor(Node*) const;
- void markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping);
+ void markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement);
void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange);
void markBadGrammar(const VisibleSelection&);
void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection);
+
+ enum TextCheckingOptionFlags {
+ MarkSpelling = 1 << 0,
+ MarkGrammar = 1 << 1,
+ PerformReplacement = 1 << 2,
+ ShowCorrectionPanel = 1 << 3,
+ CheckForCorrection = 1 << 4,
+ };
+ typedef unsigned TextCheckingOptions;
+
#if USE(AUTOMATIC_TEXT_REPLACEMENT)
void uppercaseWord();
void lowercaseWord();
@@ -242,14 +253,6 @@ public:
void toggleAutomaticSpellingCorrection();
#endif
- enum TextCheckingOptionFlags {
- MarkSpelling = 1 << 0,
- MarkGrammar = 1 << 1,
- PerformReplacement = 1 << 2,
- ShowCorrectionPanel = 1 << 3,
- };
- typedef unsigned TextCheckingOptions;
-
void markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions, Range* spellingRange, Range* grammarRange);
void changeBackToReplacedString(const String& replacedString);
@@ -323,9 +326,10 @@ public:
void addToKillRing(Range*, bool prepend);
- void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType);
+ void startCorrectionPanelTimer();
// If user confirmed a correction in the correction panel, correction has non-zero length, otherwise it means that user has dismissed the panel.
void handleCorrectionPanelResult(const String& correction);
+ void dismissCorrectionPanelAsIgnored();
void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle);
void pasteAsPlainText(const String&, bool smartReplace);
@@ -381,7 +385,7 @@ public:
#endif
bool selectionStartHasMarkerFor(DocumentMarker::MarkerType, int from, int length) const;
- void removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemoveIfSelectionAtWordBoundary);
+ void updateMarkersForWordsAffectedByEditing(bool onlyHandleWordsContainingSelection);
private:
Frame* m_frame;
@@ -396,10 +400,8 @@ private:
bool m_shouldStartNewKillRingSequence;
bool m_shouldStyleWithCSS;
OwnPtr<KillRing> m_killRing;
- CorrectionPanelInfo m_correctionPanelInfo;
OwnPtr<SpellChecker> m_spellChecker;
- Timer<Editor> m_correctionPanelTimer;
- bool m_correctionPanelIsDismissedByEditor;
+ OwnPtr<SpellingCorrectionController> m_spellingCorrector;
VisibleSelection m_mark;
bool m_areMarkedTextMatchesHighlighted;
@@ -413,6 +415,7 @@ private:
void writeSelectionToPasteboard(Pasteboard*);
void revealSelectionAfterEditingOperation();
void markMisspellingsOrBadGrammar(const VisibleSelection&, bool checkSpelling, RefPtr<Range>& firstMisspellingRange);
+ TextCheckingTypeMask textCheckingTypeMaskFor(TextCheckingOptions);
void selectComposition();
void confirmComposition(const String&, bool preserveSelection);
@@ -423,11 +426,10 @@ private:
PassRefPtr<Range> nextVisibleRange(Range*, const String&, FindOptions);
void changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle);
- void correctionPanelTimerFired(Timer<Editor>*);
+
Node* findEventTargetFromSelection() const;
void stopCorrectionPanelTimer();
- void dismissCorrectionPanel(ReasonForDismissingCorrectionPanel);
- String dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanel);
+
void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd);
// Return true if correction was applied, false otherwise.
bool applyAutocorrectionBeforeTypingIfAppropriate();
diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp
index 8ea37bb..290ee47 100644
--- a/Source/WebCore/editing/EditorCommand.cpp
+++ b/Source/WebCore/editing/EditorCommand.cpp
@@ -318,7 +318,7 @@ static bool executeDelete(Frame* frame, Event*, EditorCommandSource source, cons
case CommandFromDOMWithUserInterface:
// If the current selection is a caret, delete the preceding character. IE performs forwardDelete, but we currently side with Firefox.
// Doesn't scroll to make the selection visible, or modify the kill ring (this time, siding with IE, not Firefox).
- TypingCommand::deleteKeyPressed(frame->document(), frame->selection()->granularity() == WordGranularity);
+ TypingCommand::deleteKeyPressed(frame->document(), frame->selection()->granularity() == WordGranularity ? TypingCommand::SmartDelete : 0);
return true;
}
ASSERT_NOT_REACHED();
@@ -1196,27 +1196,27 @@ static bool enabledCut(Frame* frame, Event*, EditorCommandSource)
return frame->editor()->canDHTMLCut() || frame->editor()->canCut();
}
+static bool enabledInEditableText(Frame* frame, Event* event, EditorCommandSource)
+{
+ return frame->editor()->selectionForCommand(event).rootEditableElement();
+}
+
static bool enabledDelete(Frame* frame, Event* event, EditorCommandSource source)
{
switch (source) {
case CommandFromMenuOrKeyBinding:
// "Delete" from menu only affects selected range, just like Cut but without affecting pasteboard
- return frame->editor()->canDHTMLCut() || frame->editor()->canCut();
+ return enabledCut(frame, event, source);
case CommandFromDOM:
case CommandFromDOMWithUserInterface:
// "Delete" from DOM is like delete/backspace keypress, affects selected range if non-empty,
// otherwise removes a character
- return frame->editor()->selectionForCommand(event).isContentEditable();
+ return enabledInEditableText(frame, event, source);
}
ASSERT_NOT_REACHED();
return false;
}
-static bool enabledInEditableText(Frame* frame, Event* event, EditorCommandSource)
-{
- return frame->editor()->selectionForCommand(event).isContentEditable();
-}
-
static bool enabledInEditableTextOrCaretBrowsing(Frame* frame, Event* event, EditorCommandSource)
{
// The EditorCommandSource parameter is unused in enabledInEditableText, so just pass a dummy variable
@@ -1225,7 +1225,7 @@ static bool enabledInEditableTextOrCaretBrowsing(Frame* frame, Event* event, Edi
static bool enabledInRichlyEditableText(Frame* frame, Event*, EditorCommandSource)
{
- return frame->selection()->isCaretOrRange() && frame->selection()->isContentRichlyEditable();
+ return frame->selection()->isCaretOrRange() && frame->selection()->isContentRichlyEditable() && frame->selection()->rootEditableElement();
}
static bool enabledPaste(Frame* frame, Event*, EditorCommandSource)
diff --git a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp
index 771c56a..2b7ee8c 100644
--- a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp
+++ b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp
@@ -239,7 +239,7 @@ void InsertParagraphSeparatorCommand::doApply()
// Recreate the same structure in the new paragraph.
Vector<Element*> ancestors;
- getAncestorsInsideBlock(insertionPosition.deprecatedNode(), startBlock, ancestors);
+ getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock, ancestors);
RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
appendBlockPlaceholder(parent);
@@ -254,6 +254,9 @@ void InsertParagraphSeparatorCommand::doApply()
// similar case where previous position is in another, presumeably nested, block.
if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
Node *refNode;
+
+ insertionPosition = positionOutsideTabSpan(insertionPosition);
+
if (isFirstInBlock && !nestNewBlock)
refNode = startBlock;
else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) {
@@ -270,7 +273,7 @@ void InsertParagraphSeparatorCommand::doApply()
// Recreate the same structure in the new paragraph.
Vector<Element*> ancestors;
- getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(insertionPosition).deprecatedNode(), startBlock, ancestors);
+ getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock, ancestors);
appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert));
@@ -299,11 +302,7 @@ void InsertParagraphSeparatorCommand::doApply()
// At this point, the insertionPosition's node could be a container, and we want to make sure we include
// all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position
// before we walk the DOM tree.
- insertionPosition = VisiblePosition(insertionPosition).deepEquivalent();
-
- // Build up list of ancestors in between the start node and the start block.
- Vector<Element*> ancestors;
- getAncestorsInsideBlock(insertionPosition.deprecatedNode(), startBlock, ancestors);
+ insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent());
// Make sure we do not cause a rendered space to become unrendered.
// FIXME: We need the affinity for pos, but pos.downstream() does not give it
@@ -335,48 +334,35 @@ void InsertParagraphSeparatorCommand::doApply()
insertNodeAfter(blockToInsert.get(), startBlock);
updateLayout();
-
- // Make clones of ancestors in between the start node and the outer block.
- RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
// If the paragraph separator was inserted at the end of a paragraph, an empty line must be
// created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph
// element. If the first node to be inserted won't be one that will hold an empty line open, add a br.
if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
appendNode(createBreakElement(document()).get(), blockToInsert.get());
-
+
// Move the start node and the siblings of the start node.
- if (insertionPosition.deprecatedNode() != startBlock) {
- Node* n = insertionPosition.deprecatedNode();
- if (insertionPosition.deprecatedEditingOffset() >= caretMaxOffset(n))
- n = n->nextSibling();
+ if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) {
+ Node* n;
+ if (insertionPosition.containerNode() == startBlock)
+ n = insertionPosition.computeNodeAfterPosition();
+ else {
+ splitTreeToNode(insertionPosition.containerNode(), startBlock);
+
+ for (n = startBlock->firstChild(); n; n = n->nextSibling()) {
+ if (comparePositions(VisiblePosition(insertionPosition), positionBeforeNode(n)) <= 0)
+ break;
+ }
+ }
while (n && n != blockToInsert) {
Node *next = n->nextSibling();
removeNode(n);
- appendNode(n, parent.get());
+ appendNode(n, blockToInsert);
n = next;
}
}
- // Move everything after the start node.
- if (!ancestors.isEmpty()) {
- Element* leftParent = ancestors.first();
- while (leftParent && leftParent != startBlock) {
- parent = parent->parentElement();
- if (!parent)
- break;
- Node* n = leftParent->nextSibling();
- while (n && n != blockToInsert) {
- Node* next = n->nextSibling();
- removeNode(n);
- appendNode(n, parent.get());
- n = next;
- }
- leftParent = leftParent->parentElement();
- }
- }
-
// Handle whitespace that occurs after the split
if (splitText) {
updateLayout();
diff --git a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp
index 7ab3aba..662415b 100644
--- a/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp
+++ b/Source/WebCore/editing/ReplaceNodeWithSpanCommand.cpp
@@ -56,8 +56,8 @@ static void swapInNodePreservingAttributesAndChildren(HTMLElement* newNode, HTML
parentNode->insertBefore(newNode, nodeToReplace, ec);
ASSERT(!ec);
- Node* nextChild;
- for (Node* child = nodeToReplace->firstChild(); child; child = nextChild) {
+ RefPtr<Node> nextChild;
+ for (Node* child = nodeToReplace->firstChild(); child; child = nextChild.get()) {
nextChild = child->nextSibling();
newNode->appendChild(child, ec);
ASSERT(!ec);
diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp
index 94531a6..c3b1501 100644
--- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -952,20 +952,17 @@ void ReplaceSelectionCommand::doApply()
// We can skip this optimization for fragments not wrapped in one of
// our style spans and for positions inside list items
// since insertAsListItems already does the right thing.
- if (!m_matchStyle && !enclosingList(insertionPos.anchorNode()) && isStyleSpan(fragment.firstChild())) {
- Node* parentNode = insertionPos.anchorNode()->parentNode();
- while (parentNode && parentNode->renderer() && isInlineNodeWithStyle(parentNode)) {
- // If we are in the middle of a text node, we need to split it before we can
- // move the insertion position.
- if (insertionPos.anchorNode()->isTextNode() && insertionPos.anchorType() == Position::PositionIsOffsetInAnchor && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode())
- splitTextNodeContainingElement(static_cast<Text*>(insertionPos.anchorNode()), insertionPos.offsetInContainerNode());
-
- // If the style element has more than one child, we need to split it.
- if (parentNode->firstChild()->nextSibling())
- splitElement(static_cast<Element*>(parentNode), insertionPos.computeNodeAfterPosition());
-
- insertionPos = positionInParentBeforeNode(parentNode);
- parentNode = parentNode->parentNode();
+ if (!m_matchStyle && !enclosingList(insertionPos.containerNode()) && isStyleSpan(fragment.firstChild())) {
+ if (insertionPos.containerNode()->isTextNode() && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) {
+ splitTextNodeContainingElement(static_cast<Text*>(insertionPos.containerNode()), insertionPos.offsetInContainerNode());
+ insertionPos = firstPositionInNode(insertionPos.containerNode());
+ }
+
+ // FIXME: isInlineNodeWithStyle does not check editability.
+ if (RefPtr<Node> nodeToSplitTo = highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle)) {
+ if (insertionPos.containerNode() != nodeToSplitTo)
+ nodeToSplitTo = splitTreeToNode(insertionPos.anchorNode(), nodeToSplitTo.get(), true).get();
+ insertionPos = positionInParentBeforeNode(nodeToSplitTo.get());
}
}
diff --git a/Source/WebCore/editing/SelectionController.cpp b/Source/WebCore/editing/SelectionController.cpp
index 698ba2c..c5a33d3 100644
--- a/Source/WebCore/editing/SelectionController.cpp
+++ b/Source/WebCore/editing/SelectionController.cpp
@@ -254,9 +254,9 @@ void SelectionController::respondToNodeModification(Node* node, bool baseRemoved
m_selection.setWithoutValidation(m_selection.start(), m_selection.end());
else
m_selection.setWithoutValidation(m_selection.end(), m_selection.start());
- } else if (m_selection.firstRange()) {
+ } else if (RefPtr<Range> range = m_selection.firstRange()) {
ExceptionCode ec = 0;
- Range::CompareResults compareResult = m_selection.firstRange()->compareNode(node, ec);
+ Range::CompareResults compareResult = range->compareNode(node, ec);
if (!ec && (compareResult == Range::NODE_BEFORE_AND_AFTER || compareResult == Range::NODE_INSIDE)) {
// If we did nothing here, when this node's renderer was destroyed, the rect that it
// occupied would be invalidated, but, selection gaps that change as a result of
@@ -450,6 +450,9 @@ VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granul
case DocumentBoundary:
// FIXME: implement all of the above?
pos = modifyExtendingForward(granularity);
+ break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
}
@@ -489,6 +492,8 @@ VisiblePosition SelectionController::modifyExtendingForward(TextGranularity gran
else
pos = endOfDocument(pos);
break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
@@ -520,6 +525,9 @@ VisiblePosition SelectionController::modifyMovingRight(TextGranularity granulari
case LineBoundary:
pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
break;
+ case WebKitVisualWordGranularity:
+ pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()));
+ break;
}
return pos;
}
@@ -568,6 +576,8 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula
else
pos = endOfDocument(pos);
break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
}
@@ -607,6 +617,9 @@ VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granula
case ParagraphBoundary:
case DocumentBoundary:
pos = modifyExtendingBackward(granularity);
+ break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
}
@@ -651,6 +664,8 @@ VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity gra
else
pos = startOfDocument(pos);
break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
}
@@ -681,6 +696,9 @@ VisiblePosition SelectionController::modifyMovingLeft(TextGranularity granularit
case LineBoundary:
pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
break;
+ case WebKitVisualWordGranularity:
+ pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()));
+ break;
}
return pos;
}
@@ -723,6 +741,8 @@ VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granul
else
pos = startOfDocument(pos);
break;
+ case WebKitVisualWordGranularity:
+ break;
}
return pos;
}
diff --git a/Source/WebCore/editing/SpellChecker.cpp b/Source/WebCore/editing/SpellChecker.cpp
index 7988e41..fedcc07 100644
--- a/Source/WebCore/editing/SpellChecker.cpp
+++ b/Source/WebCore/editing/SpellChecker.cpp
@@ -100,13 +100,13 @@ bool SpellChecker::isCheckable(Node* node) const
return node && node->renderer();
}
-void SpellChecker::requestCheckingFor(Node* node)
+void SpellChecker::requestCheckingFor(TextCheckingTypeMask mask, Node* node)
{
ASSERT(canCheckAsynchronously(node));
if (!initRequest(node))
return;
- m_client->requestCheckingOfString(this, m_requestSequence, m_requestText);
+ m_client->requestCheckingOfString(this, m_requestSequence, mask, m_requestText);
}
static bool forwardIterator(PositionIterator& iterator, int distance)
@@ -131,7 +131,16 @@ static bool forwardIterator(PositionIterator& iterator, int distance)
return false;
}
-void SpellChecker::didCheck(int sequence, const Vector<SpellCheckingResult>& results)
+static DocumentMarker::MarkerType toMarkerType(TextCheckingType type)
+{
+ if (type == TextCheckingTypeSpelling)
+ return DocumentMarker::Spelling;
+ ASSERT(type == TextCheckingTypeGrammar);
+ return DocumentMarker::Grammar;
+}
+
+// Currenntly ignoring TextCheckingResult::details but should be handled. See Bug 56368.
+void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& results)
{
if (!isValid(sequence))
return;
@@ -144,16 +153,16 @@ void SpellChecker::didCheck(int sequence, const Vector<SpellCheckingResult>& res
int startOffset = 0;
PositionIterator start = firstPositionInOrBeforeNode(m_requestNode.get());
for (size_t i = 0; i < results.size(); ++i) {
- if (results[i].type() != DocumentMarker::Spelling && results[i].type() != DocumentMarker::Grammar)
+ if (results[i].type != TextCheckingTypeSpelling && results[i].type != TextCheckingTypeGrammar)
continue;
// To avoid moving the position backward, we assume the given results are sorted with
// startOffset as the ones returned by [NSSpellChecker requestCheckingOfString:].
- ASSERT(startOffset <= results[i].location());
- if (!forwardIterator(start, results[i].location() - startOffset))
+ ASSERT(startOffset <= results[i].location);
+ if (!forwardIterator(start, results[i].location - startOffset))
break;
PositionIterator end = start;
- if (!forwardIterator(end, results[i].length()))
+ if (!forwardIterator(end, results[i].length))
break;
// Users or JavaScript applications may change text while a spell-checker checks its
@@ -163,11 +172,11 @@ void SpellChecker::didCheck(int sequence, const Vector<SpellCheckingResult>& res
RefPtr<Range> range = Range::create(m_requestNode->document(), start, end);
// FIXME: Use textContent() compatible string conversion.
String destination = range->text();
- String source = m_requestText.substring(results[i].location(), results[i].length());
+ String source = m_requestText.substring(results[i].location, results[i].length);
if (destination == source)
- m_requestNode->document()->markers()->addMarker(range.get(), results[i].type());
+ m_requestNode->document()->markers()->addMarker(range.get(), toMarkerType(results[i].type));
- startOffset = results[i].location();
+ startOffset = results[i].location;
}
clearRequest();
diff --git a/Source/WebCore/editing/SpellChecker.h b/Source/WebCore/editing/SpellChecker.h
index d3940e5..4bcb89e 100644
--- a/Source/WebCore/editing/SpellChecker.h
+++ b/Source/WebCore/editing/SpellChecker.h
@@ -27,6 +27,7 @@
#define SpellChecker_h
#include "DocumentMarker.h"
+#include "TextCheckerClient.h"
#include <wtf/Noncopyable.h>
namespace WebCore {
@@ -35,25 +36,6 @@ class TextCheckerClient;
class Frame;
class Node;
-class SpellCheckingResult {
-public:
- explicit SpellCheckingResult(DocumentMarker::MarkerType type = DocumentMarker::Spelling, int location = 0, int length = 0)
- : m_type(type)
- , m_location(location)
- , m_length(length)
- {
- }
-
- DocumentMarker::MarkerType type() const { return m_type; }
- int location() const { return m_location; }
- int length() const { return m_length; }
-
-private:
- DocumentMarker::MarkerType m_type;
- int m_location;
- int m_length;
-};
-
class SpellChecker {
WTF_MAKE_NONCOPYABLE(SpellChecker);
public:
@@ -65,8 +47,8 @@ public:
bool isBusy() const;
bool isValid(int sequence) const;
bool isCheckable(Node*) const;
- void requestCheckingFor(Node*);
- void didCheck(int sequence, const Vector<SpellCheckingResult>&);
+ void requestCheckingFor(TextCheckingTypeMask, Node*);
+ void didCheck(int sequence, const Vector<TextCheckingResult>&);
private:
bool initRequest(Node*);
diff --git a/Source/WebCore/editing/SpellingCorrectionCommand.cpp b/Source/WebCore/editing/SpellingCorrectionCommand.cpp
index bad4a99..f0297c1 100644
--- a/Source/WebCore/editing/SpellingCorrectionCommand.cpp
+++ b/Source/WebCore/editing/SpellingCorrectionCommand.cpp
@@ -26,7 +26,7 @@
#include "config.h"
#include "SpellingCorrectionCommand.h"
-#include "CorrectionPanelInfo.h"
+#include "SpellingCorrectionController.h"
#include "DocumentFragment.h"
#include "Frame.h"
#include "ReplaceSelectionCommand.h"
@@ -94,7 +94,7 @@ void SpellingCorrectionCommand::doApply()
if (!fragment)
return;
- applyCommandToComposite(SetSelectionCommand::create(m_selectionToBeCorrected, SelectionController::CloseTyping | SelectionController::ClearTypingStyle));
+ applyCommandToComposite(SetSelectionCommand::create(m_selectionToBeCorrected, SelectionController::SpellCorrectionTriggered | SelectionController::CloseTyping | SelectionController::ClearTypingStyle));
#if SUPPORT_AUTOCORRECTION_PANEL
applyCommandToComposite(SpellingCorrectionRecordUndoCommand::create(document(), m_corrected, m_correction));
#endif
diff --git a/Source/WebCore/editing/SpellingCorrectionController.cpp b/Source/WebCore/editing/SpellingCorrectionController.cpp
new file mode 100644
index 0000000..c377fe8
--- /dev/null
+++ b/Source/WebCore/editing/SpellingCorrectionController.cpp
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
+ *
+ * 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.
+ */
+
+#include "config.h"
+#include "SpellingCorrectionController.h"
+
+#include "DocumentMarkerController.h"
+#include "EditCommand.h"
+#include "EditorClient.h"
+#include "Frame.h"
+#include "FrameView.h"
+#include "SpellingCorrectionCommand.h"
+#include "TextCheckerClient.h"
+#include "TextCheckingHelper.h"
+#include "TextIterator.h"
+#include "htmlediting.h"
+#include "markup.h"
+#include "visible_units.h"
+
+
+namespace WebCore {
+
+using namespace std;
+using namespace WTF;
+
+#if SUPPORT_AUTOCORRECTION_PANEL
+
+static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection()
+{
+ DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForAutoCorrection, ());
+ if (markerTypesForAutoCorrection.isEmpty()) {
+ markerTypesForAutoCorrection.append(DocumentMarker::Replacement);
+ markerTypesForAutoCorrection.append(DocumentMarker::CorrectionIndicator);
+ markerTypesForAutoCorrection.append(DocumentMarker::SpellCheckingExemption);
+ markerTypesForAutoCorrection.append(DocumentMarker::Autocorrected);
+ }
+ return markerTypesForAutoCorrection;
+}
+
+static const Vector<DocumentMarker::MarkerType>& markerTypesForReplacement()
+{
+ DEFINE_STATIC_LOCAL(Vector<DocumentMarker::MarkerType>, markerTypesForReplacement, ());
+ if (markerTypesForReplacement.isEmpty()) {
+ markerTypesForReplacement.append(DocumentMarker::Replacement);
+ markerTypesForReplacement.append(DocumentMarker::SpellCheckingExemption);
+ }
+ return markerTypesForReplacement;
+}
+
+static bool markersHaveIdenticalDescription(const Vector<DocumentMarker>& markers)
+{
+ if (markers.isEmpty())
+ return true;
+
+ const String& description = markers[0].description;
+ for (size_t i = 1; i < markers.size(); ++i) {
+ if (description != markers[i].description)
+ return false;
+ }
+ return true;
+}
+
+SpellingCorrectionController::SpellingCorrectionController(Frame* frame)
+ : m_frame(frame)
+ , m_correctionPanelTimer(this, &SpellingCorrectionController::correctionPanelTimerFired)
+{
+}
+
+SpellingCorrectionController::~SpellingCorrectionController()
+{
+ dismiss(ReasonForDismissingCorrectionPanelIgnored);
+}
+
+void SpellingCorrectionController::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type)
+{
+ const double correctionPanelTimerInterval = 0.3;
+ if (!isAutomaticSpellingCorrectionEnabled())
+ return;
+
+ // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it.
+ if (type == CorrectionPanelInfo::PanelTypeCorrection)
+ m_correctionPanelInfo.rangeToBeReplaced.clear();
+ m_correctionPanelInfo.panelType = type;
+ m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval);
+}
+
+void SpellingCorrectionController::stopCorrectionPanelTimer()
+{
+ m_correctionPanelTimer.stop();
+ m_correctionPanelInfo.rangeToBeReplaced.clear();
+}
+
+void SpellingCorrectionController::stopPendingCorrection(const VisibleSelection& oldSelection)
+{
+ // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below.
+ VisibleSelection currentSelection(m_frame->selection()->selection());
+ if (currentSelection == oldSelection)
+ return;
+
+ stopCorrectionPanelTimer();
+ dismiss(ReasonForDismissingCorrectionPanelIgnored);
+}
+
+void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping)
+{
+ // Apply pending autocorrection before next round of spell checking.
+ bool doApplyCorrection = true;
+ VisiblePosition startOfSelection = selectionAfterTyping.visibleStart();
+ VisibleSelection currentWord = VisibleSelection(startOfWord(startOfSelection, LeftWordIfOnBoundary), endOfWord(startOfSelection, RightWordIfOnBoundary));
+ if (currentWord.visibleEnd() == startOfSelection) {
+ String wordText = plainText(currentWord.toNormalizedRange().get());
+ if (wordText.length() > 0 && isAmbiguousBoundaryCharacter(wordText[wordText.length() - 1]))
+ doApplyCorrection = false;
+ }
+ if (doApplyCorrection)
+ handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
+ else
+ m_correctionPanelInfo.rangeToBeReplaced.clear();
+}
+
+bool SpellingCorrectionController::hasPendingCorrection() const
+{
+ return m_correctionPanelInfo.rangeToBeReplaced;
+}
+
+bool SpellingCorrectionController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const
+{
+ return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption);
+}
+
+void SpellingCorrectionController::show(PassRefPtr<Range> rangeToReplace, const String& replacement)
+{
+ FloatRect boundingBox = windowRectForRange(rangeToReplace.get());
+ if (boundingBox.isEmpty())
+ return;
+ m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get());
+ m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace;
+ m_correctionPanelInfo.replacementString = replacement;
+ m_correctionPanelInfo.isActive = true;
+ client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, replacement, Vector<String>());
+}
+
+void SpellingCorrectionController::handleCancelOperation()
+{
+ if (!m_correctionPanelInfo.isActive)
+ return;
+ m_correctionPanelInfo.isActive = false;
+ dismiss(ReasonForDismissingCorrectionPanelCancelled);
+}
+
+void SpellingCorrectionController::dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing)
+{
+ if (!m_correctionPanelInfo.isActive)
+ return;
+ m_correctionPanelInfo.isActive = false;
+ m_correctionPanelIsDismissedByEditor = true;
+ if (client())
+ client()->dismissCorrectionPanel(reasonForDismissing);
+}
+
+String SpellingCorrectionController::dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing)
+{
+ if (!m_correctionPanelInfo.isActive)
+ return String();
+ m_correctionPanelInfo.isActive = false;
+ m_correctionPanelIsDismissedByEditor = true;
+ if (!client())
+ return String();
+ return client()->dismissCorrectionPanelSoon(reasonForDismissing);
+}
+
+void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd)
+{
+ if (!m_correctionPanelInfo.rangeToBeReplaced)
+ return;
+
+ ExceptionCode ec = 0;
+ RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
+ if (ec)
+ return;
+
+ setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition()));
+ setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition()));
+
+ // After we replace the word at range rangeToBeReplaced, we need to add markers to that range.
+ // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore.
+ // So before we carry out the replacement, we need to store the start position of 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 rangeToBeReplaced.
+ RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition());
+ if (ec)
+ return;
+
+ Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.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());
+
+ // Clone the range, since the caller of this method may want to keep the original range around.
+ RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec);
+ applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString));
+ setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start());
+ RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.replacementString.length());
+ String newText = plainText(replacementRange.get());
+
+ // Check to see if replacement succeeded.
+ if (newText != m_correctionPanelInfo.replacementString)
+ return;
+
+ DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers();
+ size_t size = markerTypesToAdd.size();
+ for (size_t i = 0; i < size; ++i) {
+ DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
+ String description;
+ if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected))
+ description = m_correctionPanelInfo.replacedString;
+ markers->addMarker(replacementRange.get(), markerType, description);
+ }
+}
+
+bool SpellingCorrectionController::applyAutocorrectionBeforeTypingIfAppropriate()
+{
+ if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive)
+ return false;
+
+ if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection)
+ return false;
+
+ Position caretPosition = m_frame->selection()->selection().start();
+
+ if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) {
+ handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted));
+ return true;
+ }
+
+ // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction.
+ ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition);
+ dismiss(ReasonForDismissingCorrectionPanelIgnored);
+ return false;
+}
+
+void SpellingCorrectionController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
+{
+ client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
+ m_frame->document()->updateLayout();
+ m_frame->selection()->setSelection(selectionOfCorrected, SelectionController::CloseTyping | SelectionController::ClearTypingStyle | SelectionController::SpellCorrectionTriggered);
+ RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end());
+
+ DocumentMarkerController* markers = m_frame->document()->markers();
+ markers->removeMarkers(range.get(), DocumentMarker::Spelling | DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
+ markers->addMarker(range.get(), DocumentMarker::Replacement);
+ markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption);
+}
+
+void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorrectionController>*)
+{
+ m_correctionPanelIsDismissedByEditor = false;
+ switch (m_correctionPanelInfo.panelType) {
+ case CorrectionPanelInfo::PanelTypeCorrection: {
+ VisibleSelection selection(m_frame->selection()->selection());
+ VisiblePosition start(selection.start(), selection.affinity());
+ VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary);
+ VisibleSelection adjacentWords = VisibleSelection(p, start);
+ m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(Editor::MarkSpelling | Editor::ShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0);
+ }
+ break;
+ case CorrectionPanelInfo::PanelTypeReversion: {
+ m_correctionPanelInfo.isActive = true;
+ m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
+ FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
+ if (!boundingBox.isEmpty())
+ client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>());
+ }
+ break;
+ case CorrectionPanelInfo::PanelTypeSpellingSuggestions: {
+ if (plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString)
+ break;
+ String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get());
+ Vector<String> suggestions;
+ textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions);
+ if (suggestions.isEmpty()) {
+ m_correctionPanelInfo.rangeToBeReplaced.clear();
+ break;
+ }
+ String topSuggestion = suggestions.first();
+ suggestions.remove(0);
+ m_correctionPanelInfo.isActive = true;
+ FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get());
+ if (!boundingBox.isEmpty())
+ client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions);
+ }
+ break;
+ }
+}
+
+void SpellingCorrectionController::handleCorrectionPanelResult(const String& correction)
+{
+ Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get();
+ if (!replacedRange || m_frame->document() != replacedRange->ownerDocument())
+ return;
+
+ String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get());
+ // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered.
+ if (currentWord != m_correctionPanelInfo.replacedString)
+ return;
+
+ m_correctionPanelInfo.isActive = false;
+
+ switch (m_correctionPanelInfo.panelType) {
+ case CorrectionPanelInfo::PanelTypeCorrection:
+ if (correction.length()) {
+ m_correctionPanelInfo.replacementString = correction;
+ applyCorrectionPanelInfo(markerTypesForAutocorrection());
+ } else if (!m_correctionPanelIsDismissedByEditor)
+ replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString);
+ break;
+ case CorrectionPanelInfo::PanelTypeReversion:
+ case CorrectionPanelInfo::PanelTypeSpellingSuggestions:
+ if (correction.length()) {
+ m_correctionPanelInfo.replacementString = correction;
+ applyCorrectionPanelInfo(markerTypesForReplacement());
+ }
+ break;
+ }
+
+ m_correctionPanelInfo.rangeToBeReplaced.clear();
+}
+
+bool SpellingCorrectionController::isAutomaticSpellingCorrectionEnabled()
+{
+ return client() && client()->isAutomaticSpellingCorrectionEnabled();
+}
+
+FloatRect SpellingCorrectionController::windowRectForRange(const Range* range) const
+{
+ FrameView* view = m_frame->view();
+ return view ? view->contentsToWindow(IntRect(range->boundingRect())) : FloatRect();
+}
+
+void SpellingCorrectionController::respondToChangedSelection(const VisibleSelection& oldSelection)
+{
+ VisibleSelection currentSelection(m_frame->selection()->selection());
+ // When user moves caret to the end of autocorrected word and pauses, we show the panel
+ // containing the original pre-correction word so that user can quickly revert the
+ // undesired autocorrection. Here, we start correction panel timer once we confirm that
+ // 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 (!shouldStartTimeFor(marker, endOffset))
+ continue;
+ RefPtr<Range> wordRange = Range::create(m_frame->document(), node, marker.startOffset, node, marker.endOffset);
+ String currentWord = plainText(wordRange.get());
+ if (!currentWord.length())
+ continue;
+
+ m_correctionPanelInfo.rangeToBeReplaced = wordRange;
+ m_correctionPanelInfo.replacedString = currentWord;
+ if (marker.type == DocumentMarker::Spelling)
+ startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions);
+ else {
+ m_correctionPanelInfo.replacementString = marker.description;
+ startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion);
+ }
+
+ break;
+ }
+}
+
+void SpellingCorrectionController::respondToAppliedEditing(PassRefPtr<EditCommand> command)
+{
+ if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator())
+ m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator);
+}
+
+EditorClient* SpellingCorrectionController::client()
+{
+ return m_frame->page() ? m_frame->page()->editorClient() : 0;
+}
+
+TextCheckerClient* SpellingCorrectionController::textChecker()
+{
+ if (EditorClient* owner = client())
+ return owner->textChecker();
+ return 0;
+}
+
+void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString)
+{
+ client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacementString);
+}
+
+void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange)
+{
+ recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get()));
+}
+
+void SpellingCorrectionController::markReversed(PassRefPtr<Range> changedRange)
+{
+ changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
+ changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption);
+}
+
+void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString)
+{
+ Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection();
+ DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers();
+ for (size_t i = 0; i < markerTypesToAdd.size(); ++i) {
+ DocumentMarker::MarkerType markerType = markerTypesToAdd[i];
+ if (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)
+ markers->addMarker(replacedRange.get(), markerType, replacedString);
+ else
+ markers->addMarker(replacedRange.get(), markerType);
+ }
+}
+
+void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction)
+{
+ if (!rangeOfCorrection)
+ return;
+ DocumentMarkerController* markers = rangeOfCorrection->startContainer()->document()->markers();
+ Vector<DocumentMarker> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected);
+ if (correctedOnceMarkers.isEmpty())
+ return;
+
+ // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or
+ // edited it to something else, and notify spellchecker accordingly.
+ if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0].description == corrected)
+ client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction);
+ else
+ client()->recordAutocorrectionResponse(EditorClient::AutocorrectionEdited, corrected, correction);
+ markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker);
+}
+
+#endif
+
+} // namespace WebCore
diff --git a/Source/WebCore/editing/SpellingCorrectionController.h b/Source/WebCore/editing/SpellingCorrectionController.h
new file mode 100644
index 0000000..691510a
--- /dev/null
+++ b/Source/WebCore/editing/SpellingCorrectionController.h
@@ -0,0 +1,146 @@
+/*
+ * 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 SpellingCorrectionController_h
+#define SpellingCorrectionController_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 "DocumentMarker.h"
+#include "EditCommand.h"
+#include "FloatRect.h"
+#include "Range.h"
+#include "Timer.h"
+#include <wtf/Noncopyable.h>
+#include <wtf/UnusedParam.h>
+
+namespace WebCore {
+
+class EditorClient;
+class Range;
+class TextCheckerClient;
+class VisibleSelection;
+
+struct CorrectionPanelInfo {
+ enum PanelType {
+ PanelTypeCorrection = 0,
+ PanelTypeReversion,
+ PanelTypeSpellingSuggestions
+ };
+
+ RefPtr<Range> rangeToBeReplaced;
+ String replacedString;
+ String replacementString;
+ PanelType panelType;
+ bool isActive;
+};
+
+enum ReasonForDismissingCorrectionPanel {
+ ReasonForDismissingCorrectionPanelCancelled = 0,
+ ReasonForDismissingCorrectionPanelIgnored,
+ ReasonForDismissingCorrectionPanelAccepted
+};
+
+#if SUPPORT_AUTOCORRECTION_PANEL
+#define UNLESS_ENABLED(functionBody) ;
+#else
+#define UNLESS_ENABLED(functionBody) functionBody
+#endif
+
+class SpellingCorrectionController {
+ WTF_MAKE_NONCOPYABLE(SpellingCorrectionController); WTF_MAKE_FAST_ALLOCATED;
+public:
+ SpellingCorrectionController(Frame*) UNLESS_ENABLED({})
+ ~SpellingCorrectionController() UNLESS_ENABLED({})
+
+ void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType) UNLESS_ENABLED({})
+ void stopCorrectionPanelTimer() UNLESS_ENABLED({})
+
+ void dismiss(ReasonForDismissingCorrectionPanel) UNLESS_ENABLED({})
+ String dismissSoon(ReasonForDismissingCorrectionPanel) UNLESS_ENABLED({ return String(); })
+ void show(PassRefPtr<Range> rangeToReplace, const String& replacement) UNLESS_ENABLED({ UNUSED_PARAM(rangeToReplace); UNUSED_PARAM(replacement); })
+
+ void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>&) UNLESS_ENABLED({})
+ // Return true if correction was applied, false otherwise.
+ bool applyAutocorrectionBeforeTypingIfAppropriate() UNLESS_ENABLED({ return false; })
+
+ void respondToUnappliedSpellCorrection(const VisibleSelection&, const String& corrected, const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(corrected); UNUSED_PARAM(correction); })
+ void respondToAppliedEditing(PassRefPtr<EditCommand>) UNLESS_ENABLED({})
+ void respondToChangedSelection(const VisibleSelection& oldSelection) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); })
+
+ void stopPendingCorrection(const VisibleSelection& oldSelection) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); })
+ void applyPendingCorrection(const VisibleSelection& selectionAfterTyping) UNLESS_ENABLED({ UNUSED_PARAM(selectionAfterTyping); })
+
+ void handleCorrectionPanelResult(const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(correction); })
+ void handleCancelOperation() UNLESS_ENABLED({})
+
+ bool hasPendingCorrection() const UNLESS_ENABLED({ return false; })
+ bool isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const UNLESS_ENABLED({ UNUSED_PARAM(misspellingRange); return true; })
+ bool isAutomaticSpellingCorrectionEnabled() UNLESS_ENABLED({ return false; })
+ bool shouldRemoveMarkersUponEditing() { return REMOVE_MARKERS_UPON_EDITING; }
+
+ void correctionPanelTimerFired(Timer<SpellingCorrectionController>*) UNLESS_ENABLED({})
+ void recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) UNLESS_ENABLED({ UNUSED_PARAM(replacedString); UNUSED_PARAM(replacementRange); })
+ void markReversed(PassRefPtr<Range> changedRange) UNLESS_ENABLED({ UNUSED_PARAM(changedRange); })
+ void markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) UNLESS_ENABLED({ UNUSED_PARAM(replacedRange); UNUSED_PARAM(replacedString); })
+ void recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(rangeOfCorrection); UNUSED_PARAM(corrected); UNUSED_PARAM(correction); })
+
+#if SUPPORT_AUTOCORRECTION_PANEL
+private:
+ void recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString);
+
+ bool shouldStartTimeFor(const DocumentMarker& marker, int endOffset) const
+ {
+ return (((marker.type == DocumentMarker::Replacement && !marker.description.isNull())
+ || marker.type == DocumentMarker::Spelling) && static_cast<int>(marker.endOffset) == endOffset);
+ }
+
+ EditorClient* client();
+ TextCheckerClient* textChecker();
+ FloatRect windowRectForRange(const Range*) const;
+
+ EditorClient* m_client;
+ Frame* m_frame;
+
+ Timer<SpellingCorrectionController> m_correctionPanelTimer;
+ CorrectionPanelInfo m_correctionPanelInfo;
+ bool m_correctionPanelIsDismissedByEditor;
+#endif
+};
+
+#undef UNLESS_ENABLED
+
+} // namespace WebCore
+
+#endif // SpellingCorrectionController_h
diff --git a/Source/WebCore/editing/TextCheckingHelper.cpp b/Source/WebCore/editing/TextCheckingHelper.cpp
index 009c807..3912a49 100644
--- a/Source/WebCore/editing/TextCheckingHelper.cpp
+++ b/Source/WebCore/editing/TextCheckingHelper.cpp
@@ -274,7 +274,7 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b
unsigned grammarDetailIndex = 0;
Vector<TextCheckingResult> results;
- uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
m_client->textChecker()->checkTextOfParagraph(paragraphString.characters(), paragraphString.length(), checkingTypes, results);
for (unsigned i = 0; i < results.size(); i++) {
@@ -524,7 +524,7 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool
return guesses;
Vector<TextCheckingResult> results;
- uint64_t checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
+ TextCheckingTypeMask checkingTypes = checkGrammar ? (TextCheckingTypeSpelling | TextCheckingTypeGrammar) : TextCheckingTypeSpelling;
m_client->textChecker()->checkTextOfParagraph(paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results);
for (unsigned i = 0; i < results.size(); i++) {
diff --git a/Source/WebCore/editing/TextGranularity.h b/Source/WebCore/editing/TextGranularity.h
index 09cc4ed..965d7a0 100644
--- a/Source/WebCore/editing/TextGranularity.h
+++ b/Source/WebCore/editing/TextGranularity.h
@@ -39,7 +39,10 @@ enum TextGranularity {
SentenceBoundary,
LineBoundary,
ParagraphBoundary,
- DocumentBoundary
+ DocumentBoundary,
+ // FIXME: this is added temporarily for experiment with visually move
+ // caret by wordGranularity. Once all patches are landed, it should be removed.
+ WebKitVisualWordGranularity
};
}
diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp
index 1b25c87..c3be277 100644
--- a/Source/WebCore/editing/TextIterator.cpp
+++ b/Source/WebCore/editing/TextIterator.cpp
@@ -28,6 +28,7 @@
#include "TextIterator.h"
#include "Document.h"
+#include "Frame.h"
#include "HTMLElement.h"
#include "HTMLNames.h"
#include "htmlediting.h"
@@ -256,6 +257,7 @@ TextIterator::TextIterator()
, m_emitsTextWithoutTranscoding(false)
, m_handledFirstLetter(false)
, m_ignoresStyleVisibility(false)
+ , m_emitsObjectReplacementCharacters(false)
{
}
@@ -274,6 +276,7 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior)
, m_emitsTextWithoutTranscoding(behavior & TextIteratorEmitsTextsWithoutTranscoding)
, m_handledFirstLetter(false)
, m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility)
+ , m_emitsObjectReplacementCharacters(behavior & TextIteratorEmitsObjectReplacementCharacters)
{
if (!r)
return;
@@ -638,6 +641,11 @@ bool TextIterator::handleReplacedElement()
m_hasEmitted = true;
+ if (m_emitsObjectReplacementCharacters && renderer && renderer->isReplaced()) {
+ emitCharacter(objectReplacementCharacter, m_node->parentNode(), m_node, 0, 1);
+ return true;
+ }
+
if (m_emitsCharactersBetweenAllVisiblePositions) {
// We want replaced elements to behave like punctuation for boundary
// finding, and to simply take up space for the selection preservation
@@ -2369,6 +2377,38 @@ PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element* scope, int r
return resultRange.release();
}
+bool TextIterator::locationAndLengthFromRange(const Range* range, size_t& location, size_t& length)
+{
+ location = notFound;
+ length = 0;
+
+ if (!range->startContainer())
+ return false;
+
+ Element* selectionRoot = range->ownerDocument()->frame()->selection()->rootEditableElement();
+ Element* scope = selectionRoot ? selectionRoot : range->ownerDocument()->documentElement();
+
+ // The critical assumption is that this only gets called with ranges that
+ // concentrate on a given area containing the selection root. This is done
+ // because of text fields and textareas. The DOM for those is not
+ // directly in the document DOM, so ensure that the range does not cross a
+ // boundary of one of those.
+ if (range->startContainer() != scope && !range->startContainer()->isDescendantOf(scope))
+ return false;
+ if (range->endContainer() != scope && !range->endContainer()->isDescendantOf(scope))
+ return false;
+
+ RefPtr<Range> testRange = Range::create(scope->document(), scope, 0, range->startContainer(), range->startOffset());
+ ASSERT(testRange->startContainer() == scope);
+ location = TextIterator::rangeLength(testRange.get());
+
+ ExceptionCode ec;
+ testRange->setEnd(range->endContainer(), range->endOffset(), ec);
+ ASSERT(testRange->startContainer() == scope);
+ length = TextIterator::rangeLength(testRange.get()) - location;
+ return true;
+}
+
// --------
UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior defaultBehavior)
diff --git a/Source/WebCore/editing/TextIterator.h b/Source/WebCore/editing/TextIterator.h
index 0f1a6fd..9fe4ceb 100644
--- a/Source/WebCore/editing/TextIterator.h
+++ b/Source/WebCore/editing/TextIterator.h
@@ -41,7 +41,8 @@ enum TextIteratorBehavior {
TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0,
TextIteratorEntersTextControls = 1 << 1,
TextIteratorEmitsTextsWithoutTranscoding = 1 << 2,
- TextIteratorIgnoresStyleVisibility = 1 << 3
+ TextIteratorIgnoresStyleVisibility = 1 << 3,
+ TextIteratorEmitsObjectReplacementCharacters = 1 << 4
};
// FIXME: Can't really answer this question correctly without knowing the white-space mode.
@@ -98,6 +99,7 @@ public:
static int rangeLength(const Range*, bool spacesForReplacedElements = false);
static PassRefPtr<Range> rangeFromLocationAndLength(Element* scope, int rangeLocation, int rangeLength, bool spacesForReplacedElements = false);
+ static bool locationAndLengthFromRange(const Range*, size_t& location, size_t& length);
static PassRefPtr<Range> subrange(Range* entireRange, int characterOffset, int characterCount);
private:
@@ -178,6 +180,8 @@ private:
bool m_handledFirstLetter;
// Used when the visibility of the style should not affect text gathering.
bool m_ignoresStyleVisibility;
+ // Used when emitting the special 0xFFFC character is required.
+ bool m_emitsObjectReplacementCharacters;
};
// Iterates through the DOM range, returning all the text, and 0-length boundaries
diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp
index aedda31..da93fab 100644
--- a/Source/WebCore/editing/TypingCommand.cpp
+++ b/Source/WebCore/editing/TypingCommand.cpp
@@ -39,6 +39,7 @@
#include "InsertTextCommand.h"
#include "RenderObject.h"
#include "SelectionController.h"
+#include "TextIterator.h"
#include "VisiblePosition.h"
#include "htmlediting.h"
#include "visible_units.h"
@@ -49,29 +50,34 @@ using namespace HTMLNames;
static bool canAppendNewLineFeed(const VisibleSelection& selection)
{
- ExceptionCode ec = 0;
+ Node* node = selection.rootEditableElement();
+ if (!node)
+ return false;
+
RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n"));
- selection.rootEditableElement()->dispatchEvent(event, ec);
+ ExceptionCode ec = 0;
+ node->dispatchEvent(event, ec);
return event->text().length();
}
-TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, TypingCommandOptions options, TextGranularity granularity, TextCompositionType compositionType)
+TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
: CompositeEditCommand(document)
, m_commandType(commandType)
, m_textToInsert(textToInsert)
, m_openForMoreTyping(true)
, m_selectInsertedText(options & SelectInsertedText)
- , m_smartDelete(false)
+ , m_smartDelete(options & SmartDelete)
, m_granularity(granularity)
, m_compositionType(compositionType)
, m_killRing(options & KillRing)
, m_openedByBackwardDelete(false)
, m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
+ , m_shouldPreventSpellChecking(options & PreventSpellChecking)
{
updatePreservesTypingStyle(m_commandType);
}
-void TypingCommand::deleteSelection(Document* document, bool smartDelete)
+void TypingCommand::deleteSelection(Document* document, Options options)
{
ASSERT(document);
@@ -83,16 +89,16 @@ void TypingCommand::deleteSelection(Document* document, bool smartDelete)
EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand*>(lastEditCommand)->deleteSelection(smartDelete);
+ TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
+ lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
+ lastTypingCommand->deleteSelection(options & SmartDelete);
return;
}
- RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, DeleteSelection, "", 0);
- typingCommand->setSmartDelete(smartDelete);
- typingCommand->apply();
+ TypingCommand::create(document, DeleteSelection, "", options)->apply();
}
-void TypingCommand::deleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity, bool killRing)
+void TypingCommand::deleteKeyPressed(Document *document, Options options, TextGranularity granularity)
{
ASSERT(document);
@@ -101,18 +107,17 @@ void TypingCommand::deleteKeyPressed(Document *document, bool smartDelete, TextG
EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
if (granularity == CharacterGranularity && isOpenForMoreTypingCommand(lastEditCommand)) {
- updateSelectionIfDifferentFromCurrentSelection(static_cast<TypingCommand*>(lastEditCommand), frame);
- static_cast<TypingCommand*>(lastEditCommand)->deleteKeyPressed(granularity, killRing);
+ TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
+ updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand, frame);
+ lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
+ lastTypingCommand->deleteKeyPressed(granularity, options & KillRing);
return;
}
- TypingCommandOptions options = killRing ? KillRing : 0;
- RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, DeleteKey, "", options, granularity);
- typingCommand->setSmartDelete(smartDelete);
- typingCommand->apply();
+ TypingCommand::create(document, DeleteKey, "", options, granularity)->apply();
}
-void TypingCommand::forwardDeleteKeyPressed(Document *document, bool smartDelete, TextGranularity granularity, bool killRing)
+void TypingCommand::forwardDeleteKeyPressed(Document *document, Options options, TextGranularity granularity)
{
// FIXME: Forward delete in TextEdit appears to open and close a new typing command.
ASSERT(document);
@@ -122,15 +127,14 @@ void TypingCommand::forwardDeleteKeyPressed(Document *document, bool smartDelete
EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
if (granularity == CharacterGranularity && isOpenForMoreTypingCommand(lastEditCommand)) {
- updateSelectionIfDifferentFromCurrentSelection(static_cast<TypingCommand*>(lastEditCommand), frame);
- static_cast<TypingCommand*>(lastEditCommand)->forwardDeleteKeyPressed(granularity, killRing);
+ TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
+ updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand, frame);
+ lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
+ lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing);
return;
}
- TypingCommandOptions options = killRing ? KillRing : 0;
- RefPtr<TypingCommand> typingCommand = TypingCommand::create(document, ForwardDeleteKey, "", options, granularity);
- typingCommand->setSmartDelete(smartDelete);
- typingCommand->apply();
+ TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply();
}
void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame)
@@ -144,23 +148,21 @@ void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand
typingCommand->setEndingSelection(currentSelection);
}
-void TypingCommand::insertText(Document* document, const String& text, TypingCommandOptions options, TextCompositionType composition)
+void TypingCommand::insertText(Document* document, const String& text, Options options, TextCompositionType composition)
{
ASSERT(document);
Frame* frame = document->frame();
ASSERT(frame);
-#if REMOVE_MARKERS_UPON_EDITING
if (!text.isEmpty())
- document->frame()->editor()->removeSpellAndCorrectionMarkersFromWordsToBeEdited(isSpaceOrNewline(text.characters()[0]));
-#endif
-
+ document->frame()->editor()->updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text.characters()[0]));
+
insertText(document, text, frame->selection()->selection(), options, composition);
}
// FIXME: We shouldn't need to take selectionForInsertion. It should be identical to SelectionController's current selection.
-void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, TypingCommandOptions options, TextCompositionType compositionType)
+void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
{
ASSERT(document);
@@ -196,6 +198,7 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis
lastTypingCommand->setCompositionType(compositionType);
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
+ lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->insertText(newText, options & SelectInsertedText);
return;
}
@@ -212,7 +215,7 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis
}
}
-void TypingCommand::insertLineBreak(Document *document, TypingCommandOptions options)
+void TypingCommand::insertLineBreak(Document *document, Options options)
{
ASSERT(document);
@@ -246,7 +249,7 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent(Document *document)
applyCommand(TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent));
}
-void TypingCommand::insertParagraphSeparator(Document *document, TypingCommandOptions options)
+void TypingCommand::insertParagraphSeparator(Document *document, Options options)
{
ASSERT(document);
@@ -338,14 +341,14 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
if (previous.isNotNull()) {
VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
- if (p1 != p2)
- document()->frame()->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection());
-#if SUPPORT_AUTOCORRECTION_PANEL
- else if (commandType == TypingCommand::InsertText)
- document()->frame()->editor()->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection);
-#else
- UNUSED_PARAM(commandType);
-#endif
+ if (p1 != p2) {
+ RefPtr<Range> range = makeRange(p1, p2);
+ String strippedPreviousWord;
+ if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent))
+ strippedPreviousWord = plainText(range.get()).stripWhiteSpace();
+ document()->frame()->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty());
+ } else if (commandType == TypingCommand::InsertText)
+ document()->frame()->editor()->startCorrectionPanelTimer();
}
}
@@ -356,7 +359,8 @@ void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedT
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
document()->frame()->editor()->appliedEditing(this);
// Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes.
- markMisspellingsAfterTyping(commandTypeForAddedTyping);
+ if (!m_shouldPreventSpellChecking)
+ markMisspellingsAfterTyping(commandTypeForAddedTyping);
#else
// The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
markMisspellingsAfterTyping(commandTypeForAddedTyping);
@@ -463,9 +467,8 @@ bool TypingCommand::makeEditableRootEmpty()
void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
{
-#if REMOVE_MARKERS_UPON_EDITING
- document()->frame()->editor()->removeSpellAndCorrectionMarkersFromWordsToBeEdited(false);
-#endif
+ document()->frame()->editor()->updateMarkersForWordsAffectedByEditing(false);
+
VisibleSelection selectionToDelete;
VisibleSelection selectionAfterUndo;
@@ -562,9 +565,8 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
{
-#if REMOVE_MARKERS_UPON_EDITING
- document()->frame()->editor()->removeSpellAndCorrectionMarkersFromWordsToBeEdited(false);
-#endif
+ document()->frame()->editor()->updateMarkersForWordsAffectedByEditing(false);
+
VisibleSelection selectionToDelete;
VisibleSelection selectionAfterUndo;
diff --git a/Source/WebCore/editing/TypingCommand.h b/Source/WebCore/editing/TypingCommand.h
index beb4d02..a10634f 100644
--- a/Source/WebCore/editing/TypingCommand.h
+++ b/Source/WebCore/editing/TypingCommand.h
@@ -48,20 +48,22 @@ public:
TextCompositionConfirm
};
- enum TypingCommandOption {
+ enum Option {
SelectInsertedText = 1 << 0,
KillRing = 1 << 1,
- RetainAutocorrectionIndicator = 1 << 2
+ RetainAutocorrectionIndicator = 1 << 2,
+ PreventSpellChecking = 1 << 3,
+ SmartDelete = 1 << 4
};
- typedef unsigned TypingCommandOptions;
-
- static void deleteSelection(Document*, bool smartDelete = false);
- static void deleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false);
- static void forwardDeleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false);
- static void insertText(Document*, const String&, TypingCommandOptions, TextCompositionType = TextCompositionNone);
- static void insertText(Document*, const String&, const VisibleSelection&, TypingCommandOptions, TextCompositionType = TextCompositionNone);
- static void insertLineBreak(Document*, TypingCommandOptions);
- static void insertParagraphSeparator(Document*, TypingCommandOptions);
+ typedef unsigned Options;
+
+ static void deleteSelection(Document*, Options = 0);
+ static void deleteKeyPressed(Document*, Options = 0, TextGranularity = CharacterGranularity);
+ static void forwardDeleteKeyPressed(Document*, Options = 0, TextGranularity = CharacterGranularity);
+ static void insertText(Document*, const String&, Options, TextCompositionType = TextCompositionNone);
+ static void insertText(Document*, const String&, const VisibleSelection&, Options, TextCompositionType = TextCompositionNone);
+ static void insertLineBreak(Document*, Options);
+ static void insertParagraphSeparator(Document*, Options);
static void insertParagraphSeparatorInQuotedContent(Document*);
static bool isOpenForMoreTypingCommand(const EditCommand*);
static void closeTyping(EditCommand*);
@@ -80,17 +82,17 @@ public:
void setCompositionType(TextCompositionType type) { m_compositionType = type; }
private:
- static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text = "", TypingCommandOptions options = 0, TextGranularity granularity = CharacterGranularity)
+ static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text = "", Options options = 0, TextGranularity granularity = CharacterGranularity)
{
return adoptRef(new TypingCommand(document, command, text, options, granularity, TextCompositionNone));
}
- static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text, TypingCommandOptions options, TextCompositionType compositionType)
+ static PassRefPtr<TypingCommand> create(Document* document, ETypingCommand command, const String& text, Options options, TextCompositionType compositionType)
{
return adoptRef(new TypingCommand(document, command, text, options, CharacterGranularity, compositionType));
}
- TypingCommand(Document*, ETypingCommand, const String& text, TypingCommandOptions, TextGranularity, TextCompositionType);
+ TypingCommand(Document*, ETypingCommand, const String& text, Options, TextGranularity, TextCompositionType);
bool smartDelete() const { return m_smartDelete; }
void setSmartDelete(bool smartDelete) { m_smartDelete = smartDelete; }
@@ -101,6 +103,7 @@ private:
virtual bool preservesTypingStyle() const { return m_preservesTypingStyle; }
virtual bool shouldRetainAutocorrectionIndicator() const { return m_shouldRetainAutocorrectionIndicator; }
virtual void setShouldRetainAutocorrectionIndicator(bool retain) { m_shouldRetainAutocorrectionIndicator = retain; }
+ void setShouldPreventSpellChecking(bool prevent) { m_shouldPreventSpellChecking = prevent; }
static void updateSelectionIfDifferentFromCurrentSelection(TypingCommand*, Frame*);
@@ -125,6 +128,7 @@ private:
bool m_openedByBackwardDelete;
bool m_shouldRetainAutocorrectionIndicator;
+ bool m_shouldPreventSpellChecking;
};
} // namespace WebCore
diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp
index 5633c90..9e9692e 100644
--- a/Source/WebCore/editing/VisibleSelection.cpp
+++ b/Source/WebCore/editing/VisibleSelection.cpp
@@ -380,6 +380,8 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text
m_start = startOfSentence(VisiblePosition(m_start, m_affinity)).deepEquivalent();
m_end = endOfSentence(VisiblePosition(m_end, m_affinity)).deepEquivalent();
break;
+ case WebKitVisualWordGranularity:
+ break;
}
// Make sure we do not have a dangling start or end.
diff --git a/Source/WebCore/editing/htmlediting.cpp b/Source/WebCore/editing/htmlediting.cpp
index 564eff6..d89e6c5 100644
--- a/Source/WebCore/editing/htmlediting.cpp
+++ b/Source/WebCore/editing/htmlediting.cpp
@@ -122,7 +122,8 @@ int comparePositions(const Position& a, const Position& b)
}
}
- int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB);
+ ExceptionCode ec;
+ int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB, ec);
return result ? result : bias;
}
@@ -638,7 +639,7 @@ Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const N
{
Node* highest = 0;
Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0;
- for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) {
+ for (Node* n = p.containerNode(); n; n = n->parentNode()) {
if (root && !n->rendererIsEditable())
continue;
if (nodeIsOfType(n))
@@ -894,14 +895,17 @@ bool isNodeInTextFormControl(Node* node)
return ancestor->isElementNode() && static_cast<Element*>(ancestor)->isTextFormControl();
}
-Position positionBeforeTabSpan(const Position& pos)
+Position positionOutsideTabSpan(const Position& pos)
{
- Node* node = pos.deprecatedNode();
+ Node* node = pos.containerNode();
if (isTabSpanTextNode(node))
node = tabSpanNode(node);
else if (!isTabSpanNode(node))
return pos;
-
+
+ if (node && VisiblePosition(pos) == lastPositionInNode(node))
+ return positionInParentAfterNode(node);
+
return positionInParentBeforeNode(node);
}
diff --git a/Source/WebCore/editing/htmlediting.h b/Source/WebCore/editing/htmlediting.h
index cb2b2a4..a006da6 100644
--- a/Source/WebCore/editing/htmlediting.h
+++ b/Source/WebCore/editing/htmlediting.h
@@ -111,7 +111,7 @@ Position previousCandidate(const Position&);
Position nextVisuallyDistinctCandidate(const Position&);
Position previousVisuallyDistinctCandidate(const Position&);
-Position positionBeforeTabSpan(const Position&);
+Position positionOutsideTabSpan(const Position&);
Position positionBeforeContainingSpecialElement(const Position&, Node** containingSpecialElement=0);
Position positionAfterContainingSpecialElement(const Position&, Node** containingSpecialElement=0);
Position positionOutsideContainingSpecialElement(const Position&, Node** containingSpecialElement=0);
@@ -224,6 +224,15 @@ inline bool isWhitespace(UChar c)
{
return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t';
}
+
+inline bool isAmbiguousBoundaryCharacter(UChar character)
+{
+ // These are characters that can behave as word boundaries, but can appear within words.
+ // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed.
+ // FIXME: this is required until 6853027 is fixed and text checking can do this for us.
+ return character == '\'' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim;
+}
+
String stringWithRebalancedWhitespace(const String&, bool startIsStartOfParagraph, bool endIsEndOfParagraph);
const String& nonBreakingSpaceString();
diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp
index 316cec7..b93a78e 100644
--- a/Source/WebCore/editing/markup.cpp
+++ b/Source/WebCore/editing/markup.cpp
@@ -465,7 +465,7 @@ static bool isElementPresentational(const Node* node)
RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
if (!style)
return false;
- return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
+ return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration) || !Editor::hasTransparentBackgroundColor(style.get());
}
static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CSSMutableStyleDeclaration* style)
@@ -571,11 +571,14 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc
accumulator.appendString(interchangeNewlineString);
startNode = visibleStart.next().deepEquivalent().deprecatedNode();
- if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0) >= 0) {
+ ExceptionCode ec = 0;
+ if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ec) >= 0) {
+ ASSERT(!ec);
if (deleteButton)
deleteButton->enable();
return interchangeNewlineString;
}
+ ASSERT(!ec);
}
Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag);
diff --git a/Source/WebCore/editing/qt/EditorQt.cpp b/Source/WebCore/editing/qt/EditorQt.cpp
index 7fb3634..ffb24e7 100644
--- a/Source/WebCore/editing/qt/EditorQt.cpp
+++ b/Source/WebCore/editing/qt/EditorQt.cpp
@@ -31,9 +31,9 @@
#include "ClipboardQt.h"
#include "Document.h"
#include "Element.h"
-#include "VisibleSelection.h"
#include "SelectionController.h"
#include "TextIterator.h"
+#include "VisibleSelection.h"
#include "htmlediting.h"
#include "visible_units.h"
diff --git a/Source/WebCore/editing/qt/SmartReplaceQt.cpp b/Source/WebCore/editing/qt/SmartReplaceQt.cpp
index 1436afe..dcd8bf7 100644
--- a/Source/WebCore/editing/qt/SmartReplaceQt.cpp
+++ b/Source/WebCore/editing/qt/SmartReplaceQt.cpp
@@ -40,15 +40,15 @@ bool isCharacterSmartReplaceExempt(UChar32 c, bool isPreviousCharacter)
if (!isPreviousCharacter && d.isPunct())
return true;
- if ((c >= 0x1100 && c <= (0x1100 + 256)) // Hangul Jamo (0x1100 - 0x11FF)
- || (c >= 0x2E80 && c <= (0x2E80 + 352)) // CJK & Kangxi Radicals (0x2E80 - 0x2FDF)
- || (c >= 0x2FF0 && c <= (0x2FF0 + 464)) // Ideograph Deseriptions, CJK Symbols, Hiragana, Katakana, Bopomofo, Hangul Compatibility Jamo, Kanbun, & Bopomofo Ext (0x2FF0 - 0x31BF)
- || (c >= 0x3200 && c <= (0x3200 + 29392)) // Enclosed CJK, CJK Ideographs (Uni Han & Ext A), & Yi (0x3200 - 0xA4CF)
- || (c >= 0xAC00 && c <= (0xAC00 + 11183)) // Hangul Syllables (0xAC00 - 0xD7AF)
- || (c >= 0xF900 && c <= (0xF900 + 352)) // CJK Compatibility Ideographs (0xF900 - 0xFA5F)
- || (c >= 0xFE30 && c <= (0xFE30 + 32)) // CJK Compatibility From (0xFE30 - 0xFE4F)
- || (c >= 0xFF00 && c <= (0xFF00 + 240)) // Half/Full Width Form (0xFF00 - 0xFFEF)
- || (c >= 0x20000 && c <= (0x20000 + 0xA6D7)) // CJK Ideograph Exntension B
+ if ((c >= 0x1100 && c <= (0x1100 + 256)) // Hangul Jamo (0x1100 - 0x11FF)
+ || (c >= 0x2E80 && c <= (0x2E80 + 352)) // CJK & Kangxi Radicals (0x2E80 - 0x2FDF)
+ || (c >= 0x2FF0 && c <= (0x2FF0 + 464)) // Ideograph Deseriptions, CJK Symbols, Hiragana, Katakana, Bopomofo, Hangul Compatibility Jamo, Kanbun, & Bopomofo Ext (0x2FF0 - 0x31BF)
+ || (c >= 0x3200 && c <= (0x3200 + 29392)) // Enclosed CJK, CJK Ideographs (Uni Han & Ext A), & Yi (0x3200 - 0xA4CF)
+ || (c >= 0xAC00 && c <= (0xAC00 + 11183)) // Hangul Syllables (0xAC00 - 0xD7AF)
+ || (c >= 0xF900 && c <= (0xF900 + 352)) // CJK Compatibility Ideographs (0xF900 - 0xFA5F)
+ || (c >= 0xFE30 && c <= (0xFE30 + 32)) // CJK Compatibility From (0xFE30 - 0xFE4F)
+ || (c >= 0xFF00 && c <= (0xFF00 + 240)) // Half/Full Width Form (0xFF00 - 0xFFEF)
+ || (c >= 0x20000 && c <= (0x20000 + 0xA6D7)) // CJK Ideograph Exntension B
|| (c >= 0x2F800 && c <= (0x2F800 + 0x021E))) // CJK Compatibility Ideographs (0x2F800 - 0x2FA1D)
return true;
diff --git a/Source/WebCore/editing/visible_units.cpp b/Source/WebCore/editing/visible_units.cpp
index c9ca8c0..e4a84ba 100644
--- a/Source/WebCore/editing/visible_units.cpp
+++ b/Source/WebCore/editing/visible_units.cpp
@@ -37,6 +37,7 @@
#include "TextBreakIterator.h"
#include "TextIterator.h"
#include "VisiblePosition.h"
+#include "VisibleSelection.h"
#include "htmlediting.h"
#include <wtf/unicode/Unicode.h>
@@ -1146,4 +1147,396 @@ VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection dire
return direction == LTR ? logicalEndOfLine(c) : logicalStartOfLine(c);
}
+static const int invalidOffset = -1;
+
+static VisiblePosition previousWordBreakInBoxInsideBlockWithSameDirectionality(const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak)
+{
+ bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
+ // In a LTR block, the word break should be on the left boundary of a word.
+ // In a RTL block, the word break should be on the right boundary of a word.
+ // Because nextWordPosition() returns the word break on the right boundary of the word for LTR text,
+ // we need to use previousWordPosition() to traverse words within the inline boxes from right to left
+ // to find the previous word break (i.e. the first word break on the left). The same applies to RTL text.
+
+ VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak : Position(box->renderer()->node(), box->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+ // FIXME: handle multi-spaces (http://webkit.org/b/57543).
+
+ wordBreak = previousWordPosition(wordBreak);
+ if (previousWordBreak == wordBreak)
+ return VisiblePosition();
+
+ InlineBox* boxContainingPreviousWordBreak;
+ wordBreak.getInlineBoxAndOffset(boxContainingPreviousWordBreak, offsetOfWordBreak);
+ if (boxContainingPreviousWordBreak != box)
+ return VisiblePosition();
+ return wordBreak;
+}
+
+static VisiblePosition leftmostPositionInRTLBoxInLTRBlock(const InlineBox* box)
+{
+ // FIXME: Probably need to take care of bidi level too.
+ Node* node = box->renderer()->node();
+ InlineBox* previousLeaf = box->prevLeafChild();
+ InlineBox* nextLeaf = box->nextLeafChild();
+
+ if (previousLeaf && !previousLeaf->isLeftToRightDirection())
+ return Position(node, box->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+ if (nextLeaf && !nextLeaf->isLeftToRightDirection()) {
+ if (previousLeaf)
+ return Position(previousLeaf->renderer()->node(), previousLeaf->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+ InlineBox* lastRTLLeaf;
+ do {
+ lastRTLLeaf = nextLeaf;
+ nextLeaf = nextLeaf->nextLeafChild();
+ } while (nextLeaf && !nextLeaf->isLeftToRightDirection());
+ return Position(lastRTLLeaf->renderer()->node(), lastRTLLeaf->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+ }
+
+ return Position(node, box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+}
+
+static VisiblePosition rightmostPositionInLTRBoxInRTLBlock(const InlineBox* box)
+{
+ // FIXME: Probably need to take care of bidi level too.
+ Node* node = box->renderer()->node();
+ InlineBox* previousLeaf = box->prevLeafChild();
+ InlineBox* nextLeaf = box->nextLeafChild();
+
+ if (nextLeaf && nextLeaf->isLeftToRightDirection())
+ return Position(node, box->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+ if (previousLeaf && previousLeaf->isLeftToRightDirection()) {
+ if (nextLeaf)
+ return Position(nextLeaf->renderer()->node(), nextLeaf->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+ InlineBox* firstLTRLeaf;
+ do {
+ firstLTRLeaf = previousLeaf;
+ previousLeaf = previousLeaf->prevLeafChild();
+ } while (previousLeaf && previousLeaf->isLeftToRightDirection());
+ return Position(firstLTRLeaf->renderer()->node(), firstLTRLeaf->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+ }
+
+ return Position(node, box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+}
+
+static VisiblePosition lastWordBreakInBox(const InlineBox* box, int& offsetOfWordBreak)
+{
+ // Add the leftmost word break for RTL box or rightmost word break for LTR box.
+ InlineBox* previousLeaf = box->prevLeafChild();
+ InlineBox* nextLeaf = box->nextLeafChild();
+ VisiblePosition boundaryPosition;
+ if (box->direction() == RTL && (!previousLeaf || previousLeaf->isLeftToRightDirection()))
+ boundaryPosition = leftmostPositionInRTLBoxInLTRBlock(box);
+ else if (box->direction() == LTR && (!nextLeaf || !nextLeaf->isLeftToRightDirection()))
+ boundaryPosition = rightmostPositionInLTRBoxInRTLBlock(box);
+
+ if (boundaryPosition.isNull())
+ return VisiblePosition();
+
+ VisiblePosition wordBreak = nextWordPosition(boundaryPosition);
+ if (wordBreak != boundaryPosition)
+ wordBreak = previousWordPosition(wordBreak);
+
+ InlineBox* boxOfWordBreak;
+ wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+ if (boxOfWordBreak == box)
+ return wordBreak;
+ return VisiblePosition();
+}
+
+static bool positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak)
+{
+ int previousOffset = offsetOfWordBreak;
+ InlineBox* boxOfWordBreak;
+ wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+ if (boxOfWordBreak == box && (previousOffset == invalidOffset || previousOffset < offsetOfWordBreak))
+ return true;
+ return false;
+}
+
+static VisiblePosition nextWordBreakInBoxInsideBlockWithDifferentDirectionality(
+ const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak, bool& isLastWordBreakInBox)
+{
+ // FIXME: Probably need to take care of bidi level too.
+
+ // In a LTR block, the word break should be on the left boundary of a word.
+ // In a RTL block, the word break should be on the right boundary of a word.
+ // Because previousWordPosition() returns the word break on the right boundary of the word for RTL text,
+ // we need to use nextWordPosition() to traverse words within the inline boxes from right to left to find the next word break.
+ // The same applies to LTR text, in which words are traversed within the inline boxes from left to right.
+
+ // FIXME: handle multi-spaces (http://webkit.org/b/57543).
+
+ bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
+ VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak : Position(box->renderer()->node(), box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+ wordBreak = nextWordPosition(wordBreak);
+
+ if (wordBreak == previousWordBreak) {
+ isLastWordBreakInBox = true;
+ return VisiblePosition();
+ }
+
+
+ // Given RTL box "ABC DEF" either follows a LTR box or is the first visual box in an LTR block as an example,
+ // the visual display of the RTL box is: "(0)J(10)I(9)H(8) (7)F(6)E(5)D(4) (3)C(2)B(1)A(11)",
+ // where the number in parenthesis represents offset in visiblePosition.
+ // Start at offset 0, the first word break is at offset 3, the 2nd word break is at offset 7, and the 3rd word break should be at offset 0.
+ // But nextWordPosition() of offset 7 is offset 11, which should be ignored,
+ // and the position at offset 0 should be manually added as the last word break within the box.
+ if (positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
+ isLastWordBreakInBox = false;
+ return wordBreak;
+ }
+
+ isLastWordBreakInBox = true;
+ return lastWordBreakInBox(box, offsetOfWordBreak);
+}
+
+struct WordBoundaryEntry {
+ WordBoundaryEntry()
+ : offsetInInlineBox(invalidOffset)
+ {
+ }
+
+ WordBoundaryEntry(const VisiblePosition& position, int offset)
+ : visiblePosition(position)
+ , offsetInInlineBox(offset)
+ {
+ }
+
+ VisiblePosition visiblePosition;
+ int offsetInInlineBox;
+};
+
+typedef Vector<WordBoundaryEntry, 50> WordBoundaryVector;
+
+static void collectWordBreaksInBoxInsideBlockWithSameDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
+{
+ orderedWordBoundaries.clear();
+
+ VisiblePosition wordBreak;
+ int offsetOfWordBreak = invalidOffset;
+ while (1) {
+ wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
+ if (wordBreak.isNull())
+ break;
+ WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
+ orderedWordBoundaries.append(wordBoundaryEntry);
+ }
+}
+
+static void collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
+{
+ orderedWordBoundaries.clear();
+
+ VisiblePosition wordBreak;
+ int offsetOfWordBreak = invalidOffset;
+ while (1) {
+ bool isLastWordBreakInBox = false;
+ wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
+ if (wordBreak.isNotNull()) {
+ WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
+ orderedWordBoundaries.append(wordBoundaryEntry);
+ }
+ if (isLastWordBreakInBox)
+ break;
+ }
+}
+
+static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset, TextDirection blockDirection)
+{
+ int offsetOfWordBreak = 0;
+ VisiblePosition wordBreak;
+ while (true) {
+ if (box->direction() == blockDirection)
+ wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
+ // FIXME: Implement the 'else' case when the box direction is not equal to the block direction.
+ if (wordBreak.isNull())
+ break;
+ if (offset == invalidOffset || offsetOfWordBreak != offset)
+ return wordBreak;
+ }
+ return VisiblePosition();
+}
+
+static int greatestValueUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
+{
+ if (!orderedWordBoundaries.size())
+ return invalidOffset;
+ // FIXME: binary search.
+ if (boxAndBlockAreInSameDirection) {
+ for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
+ if (orderedWordBoundaries[i].offsetInInlineBox < offset)
+ return i;
+ }
+ return invalidOffset;
+ }
+ for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
+ if (orderedWordBoundaries[i].offsetInInlineBox < offset)
+ return i;
+ }
+ return invalidOffset;
+}
+
+static int smallestOffsetAbove(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
+{
+ if (!orderedWordBoundaries.size())
+ return invalidOffset;
+ // FIXME: binary search.
+ if (boxAndBlockAreInSameDirection) {
+ for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
+ if (orderedWordBoundaries[i].offsetInInlineBox > offset)
+ return i;
+ }
+ return invalidOffset;
+ }
+ for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
+ if (orderedWordBoundaries[i].offsetInInlineBox > offset)
+ return i;
+ }
+ return invalidOffset;
+}
+
+static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
+{
+ VisiblePosition wordBreak;
+ for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->prevLeafChild()) {
+ if (blockDirection == LTR)
+ wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
+ // FIXME: Implement the "else" case.
+ if (wordBreak.isNotNull())
+ return wordBreak;
+ }
+ return VisiblePosition();
+}
+
+static VisiblePosition rightWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
+{
+
+ VisiblePosition wordBreak;
+ for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->nextLeafChild()) {
+ if (blockDirection == RTL)
+ wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
+ // FIXME: Implement the "else" case.
+ if (!wordBreak.isNull())
+ return wordBreak;
+ }
+ return VisiblePosition();
+}
+
+static bool positionIsInsideBox(const VisiblePosition& wordBreak, const InlineBox* box)
+{
+ InlineBox* boxOfWordBreak;
+ int offsetOfWordBreak;
+ wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+ return box == boxOfWordBreak && offsetOfWordBreak != box->caretMaxOffset() && offsetOfWordBreak != box->caretMinOffset();
+}
+
+static VisiblePosition positionBeforeNextWord(const VisiblePosition& position)
+{
+ VisiblePosition positionAfterCurrentWord = nextWordPosition(position);
+ VisiblePosition positionAfterNextWord = nextWordPosition(positionAfterCurrentWord);
+ if (positionAfterCurrentWord == positionAfterNextWord)
+ return positionAfterCurrentWord;
+ return previousWordPosition(positionAfterNextWord);
+}
+
+static VisiblePosition positionAfterPreviousWord(const VisiblePosition& position)
+{
+ VisiblePosition positionBeforeCurrentWord = previousWordPosition(position);
+ VisiblePosition positionBeforePreviousWord = previousWordPosition(positionBeforeCurrentWord);
+ if (positionBeforeCurrentWord == positionBeforePreviousWord)
+ return positionBeforeCurrentWord;
+ return nextWordPosition(positionBeforePreviousWord);
+}
+
+VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
+{
+ InlineBox* box;
+ int offset;
+ visiblePosition.getInlineBoxAndOffset(box, offset);
+ TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
+
+ // FIXME: If the box's directionality is the same as that of the enclosing block, when the offset is at the box boundary
+ // and the direction is towards inside the box, do I still need to make it a special case? For example, a LTR box inside a LTR block,
+ // when offset is at box's caretMinOffset and the direction is DirectionRight, should it be taken care as a general case?
+ if (offset == box->caretLeftmostOffset())
+ return leftWordBoundary(box->prevLeafChild(), invalidOffset, blockDirection);
+ if (offset == box->caretRightmostOffset())
+ return leftWordBoundary(box, offset, blockDirection);
+
+
+ VisiblePosition wordBreak;
+ if (box->direction() == blockDirection) {
+ if (blockDirection == RTL)
+ wordBreak = positionBeforeNextWord(visiblePosition);
+ else
+ wordBreak = previousWordPosition(visiblePosition);
+ } else {
+ if (blockDirection == RTL)
+ wordBreak = positionAfterPreviousWord(visiblePosition);
+ else
+ wordBreak = nextWordPosition(visiblePosition);
+ }
+ if (positionIsInsideBox(wordBreak, box))
+ return wordBreak;
+
+ WordBoundaryVector orderedWordBoundaries;
+ if (box->direction() == blockDirection)
+ collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+ else
+ collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+
+ int index = box->isLeftToRightDirection() ? greatestValueUnder(offset, blockDirection == LTR, orderedWordBoundaries) :
+ smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries);
+ if (index != invalidOffset)
+ return orderedWordBoundaries[index].visiblePosition;
+
+ return leftWordBoundary(box->prevLeafChild(), invalidOffset, blockDirection);
+}
+
+VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
+{
+ InlineBox* box;
+ int offset;
+ visiblePosition.getInlineBoxAndOffset(box, offset);
+ TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
+
+ if (offset == box->caretLeftmostOffset())
+ return rightWordBoundary(box, offset, blockDirection);
+ if (offset == box->caretRightmostOffset())
+ return rightWordBoundary(box->nextLeafChild(), invalidOffset, blockDirection);
+
+ VisiblePosition wordBreak;
+ if (box->direction() == blockDirection) {
+ if (blockDirection == LTR)
+ wordBreak = positionBeforeNextWord(visiblePosition);
+ else
+ wordBreak = previousWordPosition(visiblePosition);
+ } else {
+ if (blockDirection == LTR)
+ wordBreak = positionAfterPreviousWord(visiblePosition);
+ else
+ wordBreak = nextWordPosition(visiblePosition);
+ }
+ if (positionIsInsideBox(wordBreak, box))
+ return wordBreak;
+
+ WordBoundaryVector orderedWordBoundaries;
+ if (box->direction() == blockDirection)
+ collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+ else
+ collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+ int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries) :
+ greatestValueUnder(offset, blockDirection == RTL, orderedWordBoundaries);
+ if (index != invalidOffset)
+ return orderedWordBoundaries[index].visiblePosition;
+
+ return rightWordBoundary(box->nextLeafChild(), invalidOffset, blockDirection);
+}
+
}
diff --git a/Source/WebCore/editing/visible_units.h b/Source/WebCore/editing/visible_units.h
index 18b9665..d734f78 100644
--- a/Source/WebCore/editing/visible_units.h
+++ b/Source/WebCore/editing/visible_units.h
@@ -42,6 +42,8 @@ VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBo
VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary);
VisiblePosition previousWordPosition(const VisiblePosition &);
VisiblePosition nextWordPosition(const VisiblePosition &);
+VisiblePosition rightWordPosition(const VisiblePosition&);
+VisiblePosition leftWordPosition(const VisiblePosition&);
// sentences
VisiblePosition startOfSentence(const VisiblePosition &);