Commit d6ceec82 authored by mitz@apple.com's avatar mitz@apple.com

WebCore:

        Reviewed by Darin Adler.

        - <rdar://problem/6234307> Support action methods for setting and clearing character-level directionality
        - WebCore part of <rdar://problem/6234337> Add a Text Direction menu to the default context menu when appropriate

        * WebCore.base.exp: Exported
        WebCore::Settings::setTextDirectionSubmenuInclusionBehavior().

        * editing/ApplyStyleCommand.cpp:
        (StyleChange::init): Changed to always include the direction property
        in the result if the unicode-bidi property is included.
        (ApplyStyleCommand::splitAncestorsWithUnicodeBidi): Added. Finds the
        highest ancestor of the given node that establishes bidi embedding. If
        that embedding agrees with the given allowed direction, finds the
        second-highest embedding ancestor. Splits all nodes through that
        ancestor. If the highest embedding ancestor did not need to be split,
        it is returned.
        (ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock): Added. Removes
        bidi embedding attributes and styles from all ancestors of the given
        node up to its enclosing block or the given node.
        (ApplyStyleCommand::applyInlineStyle): Added code to handle the
        unicode-bidi property. Applying style the includes this property
        involves removing all bidi embedding in effect, except  for one-level
        embedding that agrees with the desired embedding, then applying the
        desired embedding where it is not already in effect.
        (ApplyStyleCommand::applyInlineStyleToRange): Factored out from
        applyInlineStyle().
        (ApplyStyleCommand::removeHTMLBidiEmbeddingStyle): Added. Removed the
        "dir" attribute if necessary and the element if it becomes an unstyled
        style span.
        (ApplyStyleCommand::removeCSSStyle): Changed to remove the direction
        property when removing the unicode-bidi property.
        (ApplyStyleCommand::removeInlineStyle): Added a call to
        removeHTMLBidiEmbeddingStyle().

        * editing/ApplyStyleCommand.h:
        * editing/Editor.cpp:
        (Editor::textDirectionForSelection): Added. Returns the character-level
        writing direction of the selection if it is uniform and simple (at most
        one level of embedding).
        (Editor::hasBidiSelection): Added. Returns true if the selection lies
        entirely within a single block, and that block has direction:rtl or
        contains any inline boxes with non-zero bidi embedding level.

        * editing/Editor.h:
        * editing/EditorCommand.cpp:
        (stateTextWritingDirection): Added this helper function for deciding
        the state of Text Direction submenu items.
        (executeMakeTextWritingDirectionLeftToRight): Added. Applies
        "unicode-bidi: embed; direction: ltr;".
        (executeMakeTextWritingDirectionNatural): Added. Applies
        "unicode-bidi: normal;"
        (executeMakeTextWritingDirectionRightToLeft): Added. Applies
        "unicode-bidi: embed; direction: rtl;".
        (stateTextWritingDirectionLeftToRight): Added.
        (stateTextWritingDirectionNatural): Added.
        (stateTextWritingDirectionRightToLeft): Added.
        (createCommandMap): Added "MakeTextWritingDirectionLeftToRight",
        "MakeTextWritingDirectionNatural",
        and "MakeTextWritingDirectionRightToLeft".

        * editing/InsertTextCommand.cpp:
        (InsertTextCommand::input): Added code to maintain the unicode-bidi
        and direction properties in the typing style. Even if they have the
        same values as the computed style at the caret, they need to be included
        in the typing style so that inserted text will not inherit any nested
        embedding.

        * page/ContextMenuController.cpp:
        (ContextMenuController::contextMenuItemSelected): Added calls to
        the editor for the text direction menu items.

        * page/Frame.cpp
        (Frame::computeAndSetTypingStyle): Added code to maintain the
        unicode-bidi and direction properties in the typing style.

        * page/Settings.cpp:
        (Settings::setTextDirectionSubmenuInclusionBehavior): Added this setter.

        * page/Settings.h: Added a TextDirectionSubmenuInclusionBehavior enum,
        an m_textDirectionSubmenuInclusionBehavior member, and accessors.

        * page/mac/WebCoreViewFactory.h:
        * platform/ContextMenu.cpp:
        (WebCore::createAndAppendTextDirectionSubMenu): Added.
        (ContextMenu::populate): Added the Text Direction submenu item based on
        the inclusion behavior and the existence of a bidi selection.
        (ContextMenu::checkOrEnableIfNeeded): Added code for the text direction
        menu items.

        * platform/ContextMenuItem.h:
        * platform/LocalizedStrings.h: Declared
        contextMenuItemTagTextDirectionMenu().

        * platform/mac/LocalizedStringsMac.mm: Defined
        contextMenuItemTagTextDirectionMenu().

        * rendering/RenderBlock.cpp:
        (RenderBlock::containsNonZeroBidiLevel): Added.
        * rendering/RenderBlock.h:

WebKit:

        Reviewed by Darin Adler.

        - part of <rdar://problem/6234337> Add a Text Direction menu to the default context menu when appropriate

        * English.lproj/Localizable.strings: Added the Text Direction submenu
        title.

WebKit/mac:

        Reviewed by Darin Adler.

        - <rdar://problem/6234333> Implement action methods for setting and clearing character-level directionality
        - part of <rdar://problem/6234337> Add a Text Direction menu to the default context menu when appropriate

        * WebCoreSupport/WebViewFactory.mm:
        (-[WebViewFactory contextMenuItemTagTextDirectionMenu]): Added.
        * WebView/WebFrame.mm:
        (core): Added a convertor from WebTextDirectionSubmenuInclusionBehavior
        to WebCore::TextDirectionSubmenuInclusionBehavior.
        * WebView/WebFrameInternal.h:
        * WebView/WebHTMLView.mm:
        Added makeTextWritingDirectionLeftToRight:,
        makeTextWritingDirectionNatural: and
        makeTextWritingDirectionRightToLeft: using the WEBCORE_COMMAND macro.
        * WebView/WebPreferenceKeysPrivate.h:
        Added WebKitTextDirectionSubmenuInclusionBehaviorPreferenceKey.
        * WebView/WebPreferences.mm:
        (+[WebPreferences initialize]): Set the default Text Direction
        submenu inclusion behavior to never include.
        (-[WebPreferences textDirectionSubmenuInclusionBehavior]): Added this
        accessor.
        (-[WebPreferences setTextDirectionSubmenuInclusionBehavior:]): Ditto.
        * WebView/WebPreferencesPrivate.h: Defined the
        WebTextDirectionSubmenuInclusionBehavior enum and declared
        accessors.
        * WebView/WebUIDelegatePrivate.h:
        * WebView/WebView.mm:
        (-[WebView _preferencesChangedNotification:]): Added code to transfer
        the Text Direction submenu inclusion behavior preference to WebCore
        settings.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@38410 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent f504550c
2008-11-14 Dan Bernstein <mitz@apple.com>
Reviewed by Darin Adler.
- <rdar://problem/6234307> Support action methods for setting and clearing character-level directionality
- WebCore part of <rdar://problem/6234337> Add a Text Direction menu to the default context menu when appropriate
* WebCore.base.exp: Exported
WebCore::Settings::setTextDirectionSubmenuInclusionBehavior().
* editing/ApplyStyleCommand.cpp:
(StyleChange::init): Changed to always include the direction property
in the result if the unicode-bidi property is included.
(ApplyStyleCommand::splitAncestorsWithUnicodeBidi): Added. Finds the
highest ancestor of the given node that establishes bidi embedding. If
that embedding agrees with the given allowed direction, finds the
second-highest embedding ancestor. Splits all nodes through that
ancestor. If the highest embedding ancestor did not need to be split,
it is returned.
(ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock): Added. Removes
bidi embedding attributes and styles from all ancestors of the given
node up to its enclosing block or the given node.
(ApplyStyleCommand::applyInlineStyle): Added code to handle the
unicode-bidi property. Applying style the includes this property
involves removing all bidi embedding in effect, except for one-level
embedding that agrees with the desired embedding, then applying the
desired embedding where it is not already in effect.
(ApplyStyleCommand::applyInlineStyleToRange): Factored out from
applyInlineStyle().
(ApplyStyleCommand::removeHTMLBidiEmbeddingStyle): Added. Removed the
"dir" attribute if necessary and the element if it becomes an unstyled
style span.
(ApplyStyleCommand::removeCSSStyle): Changed to remove the direction
property when removing the unicode-bidi property.
(ApplyStyleCommand::removeInlineStyle): Added a call to
removeHTMLBidiEmbeddingStyle().
* editing/ApplyStyleCommand.h:
* editing/Editor.cpp:
(Editor::textDirectionForSelection): Added. Returns the character-level
writing direction of the selection if it is uniform and simple (at most
one level of embedding).
(Editor::hasBidiSelection): Added. Returns true if the selection lies
entirely within a single block, and that block has direction:rtl or
contains any inline boxes with non-zero bidi embedding level.
* editing/Editor.h:
* editing/EditorCommand.cpp:
(stateTextWritingDirection): Added this helper function for deciding
the state of Text Direction submenu items.
(executeMakeTextWritingDirectionLeftToRight): Added. Applies
"unicode-bidi: embed; direction: ltr;".
(executeMakeTextWritingDirectionNatural): Added. Applies
"unicode-bidi: normal;"
(executeMakeTextWritingDirectionRightToLeft): Added. Applies
"unicode-bidi: embed; direction: rtl;".
(stateTextWritingDirectionLeftToRight): Added.
(stateTextWritingDirectionNatural): Added.
(stateTextWritingDirectionRightToLeft): Added.
(createCommandMap): Added "MakeTextWritingDirectionLeftToRight",
"MakeTextWritingDirectionNatural",
and "MakeTextWritingDirectionRightToLeft".
* editing/InsertTextCommand.cpp:
(InsertTextCommand::input): Added code to maintain the unicode-bidi
and direction properties in the typing style. Even if they have the
same values as the computed style at the caret, they need to be included
in the typing style so that inserted text will not inherit any nested
embedding.
* page/ContextMenuController.cpp:
(ContextMenuController::contextMenuItemSelected): Added calls to
the editor for the text direction menu items.
* page/Frame.cpp
(Frame::computeAndSetTypingStyle): Added code to maintain the
unicode-bidi and direction properties in the typing style.
* page/Settings.cpp:
(Settings::setTextDirectionSubmenuInclusionBehavior): Added this setter.
* page/Settings.h: Added a TextDirectionSubmenuInclusionBehavior enum,
an m_textDirectionSubmenuInclusionBehavior member, and accessors.
* page/mac/WebCoreViewFactory.h:
* platform/ContextMenu.cpp:
(WebCore::createAndAppendTextDirectionSubMenu): Added.
(ContextMenu::populate): Added the Text Direction submenu item based on
the inclusion behavior and the existence of a bidi selection.
(ContextMenu::checkOrEnableIfNeeded): Added code for the text direction
menu items.
* platform/ContextMenuItem.h:
* platform/LocalizedStrings.h: Declared
contextMenuItemTagTextDirectionMenu().
* platform/mac/LocalizedStringsMac.mm: Defined
contextMenuItemTagTextDirectionMenu().
* rendering/RenderBlock.cpp:
(RenderBlock::containsNonZeroBidiLevel): Added.
* rendering/RenderBlock.h:
2008-11-14 Greg Bolsinga <bolsinga@apple.com>
Reviewed by Darin Adler.
......@@ -608,6 +608,7 @@ __ZN7WebCore8Settings33setEnforceCSSMIMETypeInStrictModeEb
__ZN7WebCore8Settings35disableRangeMutationForOldAppleMailEb
__ZN7WebCore8Settings36setOfflineWebApplicationCacheEnabledEb
__ZN7WebCore8Settings40setJavaScriptCanOpenWindowsAutomaticallyEb
__ZN7WebCore8Settings40setTextDirectionSubmenuInclusionBehaviorENS_37TextDirectionSubmenuInclusionBehaviorE
__ZN7WebCore8Settings41setNeedsKeyboardEventDisambiguationQuirksEb
__ZN7WebCore8blankURLEv
__ZN7WebCore8parseURLERKNS_6StringE
......
......@@ -30,6 +30,7 @@
#include "CSSParser.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "Document.h"
#include "HTMLElement.h"
#include "HTMLInterchange.h"
......@@ -102,6 +103,7 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &po
String styleText("");
bool addedDirection = false;
DeprecatedValueListConstIterator<CSSProperty> end;
for (DeprecatedValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
const CSSProperty *property = &*it;
......@@ -119,6 +121,12 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &po
if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
continue;
if (property->id() == CSSPropertyDirection) {
if (addedDirection)
continue;
addedDirection = true;
}
// Add this property
if (property->id() == CSSPropertyWebkitTextDecorationsInEffect) {
......@@ -127,6 +135,11 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &po
styleText += alteredProperty.cssText();
} else
styleText += property->cssText();
if (!addedDirection && property->id() == CSSPropertyUnicodeBidi) {
styleText += "direction: " + style->getPropertyValue(CSSPropertyDirection) + "; ";
addedDirection = true;
}
}
// Save the result for later
......@@ -576,6 +589,100 @@ void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
}
}
HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, RefPtr<CSSPrimitiveValue> allowedDirection)
{
// We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
// In that case, we return the unsplit ancestor. Otherwise, we return 0.
Node* block = enclosingBlock(node);
if (!block)
return 0;
Node* highestAncestorWithUnicodeBidi = 0;
Node* nextHighestAncestorWithUnicodeBidi = 0;
RefPtr<CSSPrimitiveValue> highestAncestorUnicodeBidi;
for (Node* n = node->parent(); n != block; n = n->parent()) {
RefPtr<CSSValue> unicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (unicodeBidi) {
ASSERT(unicodeBidi->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
highestAncestorUnicodeBidi = static_cast<CSSPrimitiveValue*>(unicodeBidi.get());
nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
highestAncestorWithUnicodeBidi = n;
}
}
}
if (!highestAncestorWithUnicodeBidi)
return 0;
HTMLElement* unsplitAncestor = 0;
if (allowedDirection && highestAncestorUnicodeBidi->getIdent() != CSSValueBidiOverride) {
RefPtr<CSSValue> highestAncestorDirection = computedStyle(highestAncestorWithUnicodeBidi)->getPropertyCSSValue(CSSPropertyDirection);
ASSERT(highestAncestorDirection->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(highestAncestorDirection.get())->getIdent() == allowedDirection->getIdent() && highestAncestorWithUnicodeBidi->isHTMLElement()) {
if (!nextHighestAncestorWithUnicodeBidi)
return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
}
}
// Split every ancestor through highest ancestor with embedding.
Node* n = node;
while (true) {
Element* parent = static_cast<Element*>(n->parent());
if (before ? n->previousSibling() : n->nextSibling())
splitElement(parent, before ? n : n->nextSibling());
if (parent == highestAncestorWithUnicodeBidi)
break;
n = n->parent();
}
return unsplitAncestor;
}
void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
{
Node* block = enclosingBlock(node);
if (!block)
return;
Node* n = node->parent();
while (n != block && n != unsplitAncestor) {
Node* parent = n->parent();
if (!n->isStyledElement()) {
n = parent;
continue;
}
StyledElement* element = static_cast<StyledElement*>(n);
RefPtr<CSSValue> unicodeBidi = computedStyle(element)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (unicodeBidi) {
ASSERT(unicodeBidi->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
// FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
// and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
// For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
// otherwise it sets the property in the inline style declaration.
if (element->hasAttribute(dirAttr)) {
// FIXME: If this is a BDO element, we should probably just remove it if it has no
// other attributes, like we (should) do with B and I elements.
removeNodeAttribute(element, dirAttr);
} else {
RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
inlineStyle->removeProperty(CSSPropertyDirection);
setNodeAttribute(element, styleAttr, inlineStyle->cssText());
if (isUnstyledStyleSpan(element))
removeNodePreservingChildren(element);
}
}
}
n = parent;
}
}
void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
{
Node* startDummySpanAncestor = 0;
......@@ -611,12 +718,55 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
endDummySpanAncestor = dummySpanAncestorForNode(end.node());
}
RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
RefPtr<CSSValue> direction;
HTMLElement* startUnsplitAncestor = 0;
HTMLElement* endUnsplitAncestor = 0;
if (unicodeBidi) {
RefPtr<CSSPrimitiveValue> allowedDirection;
ASSERT(unicodeBidi->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() == CSSValueEmbed) {
// Leave alone an ancestor that provides the desired single level embedding, if there is one.
direction = style->getPropertyCSSValue(CSSPropertyDirection);
ASSERT(direction->isPrimitiveValue());
allowedDirection = static_cast<CSSPrimitiveValue*>(direction.get());
}
startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, allowedDirection);
endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, allowedDirection);
removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor);
removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor);
}
// Remove style from the selection.
// Use the upstream position of the start for removing style.
// This will ensure we remove all traces of the relevant styles from the selection
// and prevent us from adding redundant ones, as described in:
// <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
removeInlineStyle(style, start.upstream(), end);
Position removeStart = start.upstream();
Position embeddingRemoveStart = removeStart;
Position embeddingRemoveEnd = end;
if (unicodeBidi) {
// Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
embeddingRemoveStart = positionAfterNode(startUnsplitAncestor);
if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
embeddingRemoveEnd = positionBeforeNode(endUnsplitAncestor).downstream();
}
if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) {
RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
if (Range::compareBoundaryPoints(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
removeInlineStyle(styleWithoutEmbedding, removeStart, end);
} else
removeInlineStyle(style, removeStart, end);
start = startPosition();
end = endPosition();
......@@ -638,9 +788,66 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
// so that we avoid the expense of updating before each and every call
// to check a computed style
updateLayout();
Position embeddingApplyStart = start;
Position embeddingApplyEnd = end;
if (unicodeBidi) {
// Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
Node* startEnclosingBlock = enclosingBlock(start.node());
for (Node* n = start.node(); n != startEnclosingBlock; n = n->parent()) {
if (n->isHTMLElement()) {
RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (ancestorUnicodeBidi) {
ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
embeddingApplyStart = positionAfterNode(n);
break;
}
}
}
}
Node* endEnclosingBlock = enclosingBlock(end.node());
for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) {
if (n->isHTMLElement()) {
RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (ancestorUnicodeBidi) {
ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
embeddingApplyEnd = positionBeforeNode(n);
break;
}
}
}
}
}
if (embeddingApplyStart != start || embeddingApplyEnd != end) {
if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) {
RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
}
RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end);
} else
applyInlineStyleToRange(style, start, end);
// Remove dummy style spans created by splitting text elements.
cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
if (endDummySpanAncestor != startDummySpanAncestor)
cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
}
void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* style, const Position& start, const Position& rangeEnd)
{
Node* node = start.node();
Position end = rangeEnd;
bool rangeIsEmpty = false;
if (start.offset() >= caretMaxOffset(start.node())) {
......@@ -649,7 +856,7 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
if (Range::compareBoundaryPoints(end, newStart) < 0)
rangeIsEmpty = true;
}
if (!rangeIsEmpty) {
// FIXME: Callers should perform this operation on a Range that includes the br
// if they want style applied to the empty line.
......@@ -701,11 +908,6 @@ void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
addInlineStyleIfNeeded(style, runStart, node);
}
}
// Remove dummy style spans created by splitting text elements.
cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
if (endDummySpanAncestor != startDummySpanAncestor)
cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
}
bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration *style, HTMLElement *elem)
......@@ -763,6 +965,23 @@ void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, H
removeNodePreservingChildren(elem);
}
void ApplyStyleCommand::removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
{
ASSERT(style);
ASSERT(elem);
if (!elem->hasAttribute(dirAttr))
return;
if (!style->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection))
return;
removeNodeAttribute(elem, dirAttr);
if (isUnstyledStyleSpan(elem))
removeNodePreservingChildren(elem);
}
void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
{
ASSERT(style);
......@@ -776,8 +995,11 @@ void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration *style, HTMLEl
for (DeprecatedValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
int propertyID = (*it).id();
RefPtr<CSSValue> value = decl->getPropertyCSSValue(propertyID);
if (value && (propertyID != CSSPropertyWhiteSpace || !isTabSpanNode(elem)))
if (value && (propertyID != CSSPropertyWhiteSpace || !isTabSpanNode(elem))) {
removeCSSProperty(decl, propertyID);
if (propertyID == CSSPropertyUnicodeBidi && !decl->getPropertyValue(CSSPropertyDirection).isEmpty())
removeCSSProperty(decl, CSSPropertyDirection);
}
}
if (isUnstyledStyleSpan(elem))
......@@ -985,6 +1207,7 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
removeHTMLStyleNode(elem);
else {
removeHTMLFontStyle(style.get(), elem);
removeHTMLBidiEmbeddingStyle(style.get(), elem);
removeCSSStyle(style.get(), elem);
}
if (!elem->inDocument()) {
......
......@@ -30,6 +30,7 @@
namespace WebCore {
class CSSPrimitiveValue;
class HTMLElement;
class StyleChange;
......@@ -64,6 +65,7 @@ private:
bool isHTMLStyleNode(CSSMutableStyleDeclaration*, HTMLElement*);
void removeHTMLStyleNode(HTMLElement*);
void removeHTMLFontStyle(CSSMutableStyleDeclaration*, HTMLElement*);
void removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration*, HTMLElement*);
void removeCSSStyle(CSSMutableStyleDeclaration*, HTMLElement*);
void removeBlockStyle(CSSMutableStyleDeclaration*, const Position& start, const Position& end);
void removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>, const Position& start, const Position& end);
......@@ -79,6 +81,7 @@ private:
void applyBlockStyle(CSSMutableStyleDeclaration*);
void applyRelativeFontStyleChange(CSSMutableStyleDeclaration*);
void applyInlineStyle(CSSMutableStyleDeclaration*);
void applyInlineStyleToRange(CSSMutableStyleDeclaration*, const Position& start, const Position& end);
void addBlockStyle(const StyleChange&, HTMLElement*);
void addInlineStyleIfNeeded(CSSMutableStyleDeclaration*, Node* start, Node* end);
bool splitTextAtStartIfNeeded(const Position& start, const Position& end);
......@@ -93,6 +96,9 @@ private:
float computedFontSize(const Node*);
void joinChildTextNodes(Node*, const Position& start, const Position& end);
HTMLElement* splitAncestorsWithUnicodeBidi(Node*, bool before, RefPtr<CSSPrimitiveValue> allowedDirection);
void removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor);
void updateStartEnd(const Position& newStart, const Position& newEnd);
Position startPosition();
Position endPosition();
......
......@@ -32,6 +32,7 @@
#include "CSSComputedStyleDeclaration.h"
#include "CSSProperty.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "ClipboardEvent.h"
#include "DeleteButtonController.h"
#include "DeleteSelectionCommand.h"
......@@ -461,6 +462,135 @@ const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
#endif
}
WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbeddings) const
{
hasNestedOrMultipleEmbeddings = true;
if (m_frame->selection()->isNone())
return NaturalWritingDirection;
Position pos = m_frame->selection()->selection().start().downstream();
Node* node = pos.node();
if (!node)
return NaturalWritingDirection;
Position end;
if (m_frame->selection()->isRange()) {
end = m_frame->selection()->selection().end().upstream();
Node* pastLast = Range::create(m_frame->document(), rangeCompliantEquivalent(pos), rangeCompliantEquivalent(end))->pastLastNode();
for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) {
if (!n->isStyledElement())
continue;
RefPtr<CSSComputedStyleDeclaration> style = computedStyle(n);
RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicodeBidi)
continue;
ASSERT(unicodeBidi->isPrimitiveValue());
int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
return NaturalWritingDirection;
}
}
if (m_frame->selection()->isCaret()) {
if (CSSMutableStyleDeclaration *typingStyle = m_frame->typingStyle()) {
RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (unicodeBidi) {
ASSERT(unicodeBidi->isPrimitiveValue());
int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
if (unicodeBidiValue == CSSValueEmbed) {
RefPtr<CSSValue> direction = typingStyle->getPropertyCSSValue(CSSPropertyDirection);
ASSERT(!direction || direction->isPrimitiveValue());
if (direction) {
hasNestedOrMultipleEmbeddings = false;
return static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
}
} else if (unicodeBidiValue == CSSValueNormal) {
hasNestedOrMultipleEmbeddings = false;
return NaturalWritingDirection;
}
}
}
node = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
}
// The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position
// to decide.
Node* block = enclosingBlock(node);
WritingDirection foundDirection = NaturalWritingDirection;
for (; node != block; node = node->parent()) {
if (!node->isStyledElement())
continue;
RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node);
RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicodeBidi)
continue;
ASSERT(unicodeBidi->isPrimitiveValue());
int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
if (unicodeBidiValue == CSSValueNormal)
continue;
if (unicodeBidiValue == CSSValueBidiOverride)
return NaturalWritingDirection;
ASSERT(unicodeBidiValue == CSSValueEmbed);
RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
if (!direction)
continue;
ASSERT(direction->isPrimitiveValue());
int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent();
if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
continue;
if (foundDirection != NaturalWritingDirection)
return NaturalWritingDirection;
// In the range case, make sure that the embedding element persists until the end of the range.
if (m_frame->selection()->isRange() && !end.node()->isDescendantOf(node))
return NaturalWritingDirection;
foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
}
hasNestedOrMultipleEmbeddings = false;
return foundDirection;
}
bool Editor::hasBidiSelection() const
{
if (m_frame->selection()->isNone())
return false;
Node* startNode;
if (m_frame->selection()->isRange()) {
startNode = m_frame->selection()->selection().start().downstream().node();
Node* endNode = m_frame->selection()->selection().end().upstream().node();
if (enclosingBlock(startNode) != enclosingBlock(endNode))
return false;
} else
startNode = m_frame->selection()->selection().visibleStart().deepEquivalent().node();
RenderObject* renderer = startNode->renderer();
while (renderer && !renderer->isRenderBlock())
renderer = renderer->parent();
if (!renderer)
return false;
RenderStyle* style = renderer->style();
if (style->direction() == RTL)
return true;
return static_cast