Toggling underline or strike through affects each other
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 May 2015 20:42:41 +0000 (20:42 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 May 2015 20:42:41 +0000 (20:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=27818

Reviewed by Darin Adler.

Source/WebCore:

This patch introduces a new mechanism to apply and remove text decorations. This is necessary because text
decorations are always additive and we can't differentiate whether we're adding or removing a text decoration.
Conceptually, we need four values for text decorations: adding underline, removing underline, adding
line-through, and removing line-through but we have only three: underline, line-through, none.

After this patch, there are three mechanism by which text decorations states are kept tracked. While applying
or removing text decorations, we use newly added m_underlineChange and m_strikeThroughChange in EditingStyle.
For the typing style, we use -webkit-text-decorations-in-effect to store the state since we need to preserve
every type of text decorations such as overline in addition to underline and line-through. Once applied, all
text decorations should be expressed in terms of the standard text-decoration property.

Test: editing/execCommand/toggle-mixed-text-decorations.html

* editing/ApplyStyleCommand.cpp:
(WebCore::ApplyStyleCommand::applyBlockStyle):
(WebCore::ApplyStyleCommand::removeCSSStyle): conflictsWithInlineStyleOfElement now creates a new inline style
instead of a list of properties to remove.
(WebCore::ApplyStyleCommand::addBlockStyle):
(WebCore::ApplyStyleCommand::applyInlineStyleChange): Merge inline styles instead of adding as string.
Otherwise it would generate style content attribute with multiple text-decoration properties.

* editing/EditingStyle.cpp:
(WebCore::HTMLElementEquivalent::matches):
(WebCore::HTMLElementEquivalent::propertyExistsInStyle): Takes an EditingStyle instead of StyleProperties.
(WebCore::HTMLElementEquivalent::valueIsPresentInStyle):
(WebCore::HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent):
(WebCore::HTMLTextDecorationEquivalent::propertyExistsInStyle): Respect newly added m_strikeThroughChange and
m_underlineChange in EditingStyle.
(WebCore::HTMLTextDecorationEquivalent::valueIsPresentInStyle): Ditto.
(WebCore::HTMLTextDecorationEquivalent::changeInStyle): Added. Retrieves the change enum for the associated
type of text-decoration (underline or strike through).
(WebCore::HTMLAttributeEquivalent::matches):
(WebCore::HTMLAttributeEquivalent::valueIsPresentInStyle):
(WebCore::EditingStyle::EditingStyle): Initialize m_underlineChange and m_strikeThroughChange. Also use the
delegating constructor elsewhere. Also added the missing call to extractFontSizeDelta() in the variant that
takes CSSPropertyID and String, and added a variant that takes CSSPropertyID and CSSValueID.
(WebCore::EditingStyle::isEmpty): Return false when m_underlineChange and m_strikeThroughChange are not "none".
(WebCore::applyTextDecorationChangeToValueList): Added.
(WebCore::EditingStyle::overrideTypingStyleAt): Added. Used by Editor::computeAndSetTypingStyle to set a new
typing style. Resolve m_underlineChange and m_strikeThroughChange into -webkit-text-decorations-in-effect.
(WebCore::EditingStyle::clear): Clear m_underlineChange and m_strikeThroughChange.
(WebCore::EditingStyle::copy): Copy m_underlineChange and m_strikeThroughChange.
(WebCore::textDecorationValueList): Added.
(WebCore::EditingStyle::conflictsWithInlineStyleOfElement): Now takes a pointer to MutableStyleProperties
instead of a vector. This was necessary we can't simply remove text-decoration property in ApplyStyleCommand's
removeCSSStyle as that would result in unrelated text decorations also getting removed. Also added the code
for m_underlineChange and m_strikeThroughChange. Only removing text decoration changes can cause a conflict
since text decorations are always additive.
(WebCore::EditingStyle::conflictsWithImplicitStyleOfElement): Check isEmpty() instead of the nullity of
m_mutableStyle to respect m_underlineChange and m_strikeThroughChange.
(WebCore::EditingStyle::conflictsWithImplicitStyleOfAttributes):
(WebCore::EditingStyle::extractConflictingImplicitStyleOfAttributes):
(WebCore::EditingStyle::styleIsPresentInComputedStyleOfNode): Respect the values of m_underlineChange and
m_strikeThroughChange. Here, the style is considered present if it has text decorations that are being added.
(WebCore::EditingStyle::elementIsStyledSpanOrHTMLEquivalent):
(WebCore::elementMatchesAndPropertyIsNotInInlineStyleDecl): Takes EditingStyle instead of StyleProperties to
respect m_underlineChange and m_strikeThroughChange.
(WebCore::EditingStyle::mergeInlineAndImplicitStyleOfElement):
(WebCore::mergeTextDecorationValues):
(WebCore::EditingStyle::mergeStyle): Make a copy of CSSValueList before modifying it since CSSValueList's are
shared with other immutable StyleProperties.
(WebCore::StyleChange::StyleChange): Set m_applyUnderline, m_applyLineThrough, and m_cssStyle if either
m_underlineChange or m_strikeThroughChange are TextDecorationChange::Add in EditingStyle if the current position
doesn't already have the matching style.
(WebCore::StyleChange::operator==): Moved from the header file. Also added the logic to compare m_cssStyle now
that it's a StyleProperties instead of String.

* editing/EditingStyle.h: Added TextDecorationChange.
(WebCore::EditingStyle::create): Added a variant that takes CSSPropertyID and CSSValueID.
(WebCore::EditingStyle::conflictsWithInlineStyleOfElement):
(WebCore::EditingStyle::setUnderlineChange): Added.
(WebCore::EditingStyle::underlineChange): Added.
(WebCore::EditingStyle::setStrikeThroughChange): Added.
(WebCore::EditingStyle::strikeThroughChange): Added.
(WebCore::StyleChange::cssStyle): Now returns StyleProperties* instead of String so that ApplyStyleCommand's
applyInlineStyleChange could merge inline styles instead of just appending it to the end.
(WebCore::StyleChange::operator==): Moved into the cpp file.

* editing/Editor.cpp:
(WebCore::Editor::applyStyle): Added. This variant takes EditingStyle instead of StyleProperties.
(WebCore::Editor::applyStyleToSelection): Ditto.
(WebCore::Editor::computeAndSetTypingStyle): Added a variant for EditingStyle. Also use overrideTypingStyleAt
to set -webkit-text-decorations-in-effect based on m_underlineChange and m_strikeThroughChange

* editing/Editor.h:
* editing/EditorCommand.cpp:
(WebCore::applyCommandToFrame):
(WebCore::isStylePresent): Extracted from executeToggleStyle.
(WebCore::executeApplyStyle):
(WebCore::executeToggleStyle):
(WebCore::executeToggleStyleInList): Deleted.
(WebCore::textDecorationChangeForToggling): Added. Used in executeStrikethrough and executeUnderline.
(WebCore::executeStrikethrough):
(WebCore::executeUnderline):

Source/WebKit/mac:

* WebView/WebFrame.mm:
(-[WebFrame _setTypingStyle:withUndoAction:]):

LayoutTests:

Added a regression test and rebaselined various tests as explained below.

* editing/execCommand/script-tests/toggle-style-2.js: The order in which u and strike elements appear have switched.
* editing/execCommand/script-tests/toggle-text-decorations.js: Ditto for line-through and overline.
* editing/execCommand/toggle-mixed-text-decorations-expected.txt: Added.
* editing/execCommand/toggle-mixed-text-decorations.html: Added.
* editing/execCommand/toggle-style-2-expected.txt: Rebaselined.
* editing/execCommand/toggle-text-decorations-expected.txt: Rebaselined.
* editing/undo/remove-css-property-and-remove-style-expected.txt: The order in which color and font-weight properties
appear have switched.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@183770 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/execCommand/script-tests/toggle-style-2.js
LayoutTests/editing/execCommand/script-tests/toggle-text-decorations.js
LayoutTests/editing/execCommand/toggle-mixed-text-decorations-expected.txt [new file with mode: 0644]
LayoutTests/editing/execCommand/toggle-mixed-text-decorations.html [new file with mode: 0644]
LayoutTests/editing/execCommand/toggle-style-2-expected.txt
LayoutTests/editing/execCommand/toggle-text-decorations-expected.txt
LayoutTests/editing/undo/remove-css-property-and-remove-style-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/editing/ApplyStyleCommand.cpp
Source/WebCore/editing/EditingStyle.cpp
Source/WebCore/editing/EditingStyle.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/EditorCommand.cpp
Source/WebKit/mac/ChangeLog
Source/WebKit/mac/WebView/WebFrame.mm

index b66cff6fc6b2a1be2e987b931bde58279468cb51..f791f7d80fb7cd38f22956b650aa04c6bb3c923a 100644 (file)
@@ -1,3 +1,21 @@
+2015-05-04  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Toggling underline or strike through affects each other
+        https://bugs.webkit.org/show_bug.cgi?id=27818
+
+        Reviewed by Darin Adler.
+
+        Added a regression test and rebaselined various tests as explained below.
+
+        * editing/execCommand/script-tests/toggle-style-2.js: The order in which u and strike elements appear have switched.
+        * editing/execCommand/script-tests/toggle-text-decorations.js: Ditto for line-through and overline.
+        * editing/execCommand/toggle-mixed-text-decorations-expected.txt: Added.
+        * editing/execCommand/toggle-mixed-text-decorations.html: Added.
+        * editing/execCommand/toggle-style-2-expected.txt: Rebaselined.
+        * editing/execCommand/toggle-text-decorations-expected.txt: Rebaselined.
+        * editing/undo/remove-css-property-and-remove-style-expected.txt: The order in which color and font-weight properties
+        appear have switched.
+
 2015-05-04  Chris Dumez  <cdumez@apple.com>
 
         REGRESSION (r178156): CSS Parser incorrectly rejects valid calc() in padding-right property
index 1f6deed64dcfc72a017b70ae6ca7f843307bae47..b4d152b645ce689ae9407b59d1092b28d2144d85 100644 (file)
@@ -37,7 +37,7 @@ testSingleToggle("strikethrough", "<u><b><strike>test</strike></b></u>", "<u><b>
 testDoubleToggle("strikethrough", "test", "test");
 
 testSingleToggle("strikethrough", "<u>test</u>", "<u><strike>test</strike></u>");
-testSingleToggle("underline", "<strike>test</strike>", "<u><strike>test</strike></u>");
+testSingleToggle("underline", "<strike>test</strike>", "<strike><u>test</u></strike>");
 
 testSingleToggle("strikethrough", '<span style="text-decoration: overline;">test</span>', '<span style="text-decoration: overline;"><strike>test</strike></span>');
 testSingleToggle("underline", '<span style="text-decoration: overline;">test</span>', '<span style="text-decoration: overline;"><u>test</u></span>');
index a8bb37d26d8e02ca6f33dbce3afeee26cafaab0a..0b7fe0dfae9d15060143303fd16af0e61cb3af7d 100644 (file)
@@ -19,7 +19,8 @@ function testSingleToggle(toggleCommand, initialContents, expectedContents)
 
 testSingleToggle("underline", "test", "<span style=\"text-decoration: underline;\">test</span>");
 testSingleToggle("underline", "<span style=\"text-decoration: underline;\">test</span>", "test");
-testSingleToggle("underline", "<span style=\"text-decoration: underline line-through overline;\">test</span>", "<span style=\"text-decoration: overline line-through;\">test</span>");
+testSingleToggle("underline", "<span style=\"text-decoration: underline line-through overline;\">test</span>",
+    "<span style=\"text-decoration: line-through overline;\">test</span>");
 testSingleToggle("strikethrough", "test", "<span style=\"text-decoration: line-through;\">test</span>");
 testSingleToggle("strikethrough", "<span style=\"text-decoration: line-through;\">test</span>", "test");
 testSingleToggle("strikethrough", "<span style=\"text-decoration: underline line-through overline;\">test</span>", "<span style=\"text-decoration: underline overline;\">test</span>");
diff --git a/LayoutTests/editing/execCommand/toggle-mixed-text-decorations-expected.txt b/LayoutTests/editing/execCommand/toggle-mixed-text-decorations-expected.txt
new file mode 100644 (file)
index 0000000..8da067e
--- /dev/null
@@ -0,0 +1,79 @@
+Test to make sure we can toggle underline and strike through separately.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+document.execCommand("styleWithCSS", false, false);
+
+Toggling strikeThrough
+PASS content("<s><u>a</u>b</s>"); select(0, 2); toggle("strikeThrough") is "<u>a</u>b"
+PASS content("<s><u>a</u>b<u>c</u></s>"); select(0, 3); toggle("strikeThrough") is "<u>a</u>b<u>c</u>"
+PASS content("<s>a<u>b</u>c</s>"); select(0, 3); toggle("strikeThrough") is "a<u>b</u>c"
+PASS content("<s>a<u>b</u>c</s>"); select(1, 3); toggle("strikeThrough") is "<strike>a</strike><u>b</u>c"
+PASS content("<s>a<u>b</u>c</s>"); select(0, 2); toggle("strikeThrough") is "a<u>b</u><strike>c</strike>"
+PASS content("<s><u>ab</u></s>c"); select(1, 3); toggle("strikeThrough") is "<u><strike>a</strike>b</u>c"
+PASS content("<s>a<u>b</u></s>c"); select(1, 3); toggle("strikeThrough") is "<strike>a</strike><u>b</u>c"
+PASS content("a<s><u>b</u>c</s>"); select(0, 2); toggle("strikeThrough") is "<strike>a</strike><s><u>b</u>c</s>"
+PASS content("a<strike><u>b</u>c</strike>"); select(0, 2); toggle("strikeThrough") is "<strike>a<u>b</u>c</strike>"
+PASS content("a<u><s>bc</s></u>"); select(0, 2); toggle("strikeThrough") is "<strike>a</strike><u><s>bc</s></u>"
+PASS content("a<s><b><u>bc</u></b></s>"); select(0, 2); toggle("strikeThrough") is "<strike>a</strike><s><b><u>bc</u></b></s>"
+PASS content("a<strike><b><u>bc</u></b></strike>"); select(0, 2); toggle("strikeThrough") is "<strike>a<b><u>bc</u></b></strike>"
+
+Toggling underline
+PASS content("<u><s>a</s>b</u>"); select(0, 2); toggle("underline") is "<s>a</s>b"
+PASS content("<u><s>a</s>b<s>c</s></u>"); select(0, 3); toggle("underline") is "<s>a</s>b<s>c</s>"
+PASS content("<u>a<s>b</s>c</u>"); select(0, 3); toggle("underline") is "a<s>b</s>c"
+PASS content("<u>a<s>b</s>c</u>"); select(1, 3); toggle("underline") is "<u>a</u><s>b</s>c"
+PASS content("<u>a<s>b</s>c</u>"); select(0, 2); toggle("underline") is "a<s>b</s><u>c</u>"
+PASS content("<u><s>ab</s></u>c"); select(1, 3); toggle("underline") is "<s><u>a</u>b</s>c"
+PASS content("<u>a<s>b</s></u>c"); select(1, 3); toggle("underline") is "<u>a</u><s>b</s>c"
+PASS content("a<u><s>b</s>c</u>"); select(0, 2); toggle("underline") is "<u>a<s>b</s>c</u>"
+PASS content("a<s><u>bc</u></s>"); select(0, 2); toggle("underline") is "<u>a</u><s><u>bc</u></s>"
+PASS content("a<u><b><s>bc</s></b></u>"); select(0, 2); toggle("underline") is "<u>a<b><s>bc</s></b></u>"
+document.execCommand("styleWithCSS", false, true);
+
+Toggling strikeThrough
+PASS content('<span style="text-decoration: line-through;"><span style="text-decoration: underline">a</span>b</span>');
+select(0, 2); toggle("strikeThrough") is '<span style="text-decoration: underline;">a</span>b'
+PASS content('<span style="text-decoration: line-through;"><span style="text-decoration: underline;">a</span>b<span style="text-decoration: underline">c</span></span>');
+select(0, 3); toggle("strikeThrough") is '<span style="text-decoration: underline;">a</span>b<span style="text-decoration: underline;">c</span>'
+PASS content('<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>');
+select(0, 3); toggle("strikeThrough") is 'a<span style="text-decoration: underline;">b</span>c'
+PASS content('<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>');
+select(1, 3); toggle("strikeThrough") is '<span style="text-decoration: line-through;">a</span><span style="text-decoration: underline;">b</span>c'
+PASS content('<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>');
+select(0, 2); toggle("strikeThrough") is 'a<span style="text-decoration: underline;">b</span><span style="text-decoration: line-through;">c</span>'
+PASS content('<span style="text-decoration: line-through;"><span style="text-decoration: underline;">ab</span></span>c');
+select(1, 3); toggle("strikeThrough") is '<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b</span>c'
+PASS content('a<span style="text-decoration: line-through;"><span style="text-decoration: underline;">b</span>c</span>');
+select(0, 2); toggle("strikeThrough") is '<span style="text-decoration: line-through;">a<span style="text-decoration: underline line-through;">b</span>c</span>'
+PASS content('a<span style="text-decoration: underline;"><span style="text-decoration: line-through;">bc</span></span>');
+select(0, 2); toggle("strikeThrough") is '<span style="text-decoration: line-through;">a</span><span style="text-decoration: underline;"><span style="text-decoration: line-through;">bc</span></span>'
+PASS content('a<span style="text-decoration: line-through;"><b><span style="text-decoration: underline;">bc</span></b></span>');
+select(0, 2); toggle("strikeThrough") is '<span style="text-decoration: line-through;">a<b><span style="text-decoration: underline;"><span style="text-decoration: underline line-through;">b</span>c</span></b></span>'
+
+Toggling underline
+PASS content('<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b</span>');
+select(0, 2); toggle("underline") is '<span style="text-decoration: line-through;">a</span>b'
+PASS content('<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b<span style="text-decoration: line-through;">c</span></span>');
+select(0, 3); toggle("underline") is '<span style="text-decoration: line-through;">a</span>b<span style="text-decoration: line-through;">c</span>'
+PASS content('<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>');
+select(0, 3); toggle("underline") is 'a<span style="text-decoration: line-through;">b</span>c'
+PASS content('<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>');
+select(1, 3); toggle("underline") is '<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;">b</span>c'
+PASS content('<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>');
+select(0, 2); toggle("underline") is 'a<span style="text-decoration: line-through;">b</span><span style="text-decoration: underline;">c</span>'
+PASS content('<span style="text-decoration: underline;"><span style="text-decoration: line-through;">ab</span></span>c');
+select(1, 3); toggle("underline") is '<span style="text-decoration: line-through;"><span style="text-decoration: underline;">a</span>b</span>c'
+PASS content('<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span></span>c');
+select(1, 3); toggle("underline") is '<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;">b</span>c'
+PASS content('a<span style="text-decoration: underline;"><span style="text-decoration: line-through;">b</span>c</span>');
+select(0, 2); toggle("underline") is '<span style="text-decoration: underline;">a<span style="text-decoration: line-through underline;">b</span>c</span>'
+PASS content('a<span style="text-decoration: line-through;"><span style="text-decoration: underline;">bc</span></span>');
+select(0, 2); toggle("underline") is '<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;"><span style="text-decoration: underline;">bc</span></span>'
+PASS content('a<span style="text-decoration: underline;"><b><span style="text-decoration: line-through;">bc</span></b></span>');
+select(0, 2); toggle("underline") is '<span style="text-decoration: underline;">a<b><span style="text-decoration: line-through;"><span style="text-decoration: underline line-through;">b</span>c</span></b></span>'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/execCommand/toggle-mixed-text-decorations.html b/LayoutTests/editing/execCommand/toggle-mixed-text-decorations.html
new file mode 100644 (file)
index 0000000..6787424
--- /dev/null
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+description("Test to make sure we can toggle underline and strike through separately.")
+
+var testContainer = document.createElement("div");
+testContainer.contentEditable = true;
+document.body.appendChild(testContainer);
+
+function testSingleToggle(toggleCommand, initialContents, expectedContents)
+{
+    testContainer.innerHTML = initialContents;
+    getSelection().collapse(testContainer, 0);
+    getSelection().modify('')
+    window.getSelection().selectAllChildren(testContainer);
+    document.execCommand("styleWithCSS", false, false);
+    document.execCommand(toggleCommand, false, null);
+    if (testContainer.innerHTML === expectedContents) {
+        testPassed("one " + toggleCommand + " command converted " + initialContents + " to " + expectedContents);
+    } else {
+        testFailed("one " + toggleCommand + " command converted " + initialContents + " to " + testContainer.innerHTML + ", expected " + expectedContents);
+    }
+}
+
+function content(markup) {
+    testContainer.innerHTML = markup;
+}
+
+function select(offset, extent) {
+    getSelection().collapse(testContainer, 0);
+    for (var i = 0; i < offset; i++)
+        getSelection().modify('move', 'forward', 'character');
+    for (var i = offset; i < extent; i++)
+        getSelection().modify('extend', 'forward', 'character');
+}
+
+function toggle(command) {
+    document.execCommand(command, false, null);
+    return testContainer.innerHTML;
+}
+
+evalAndLog('document.execCommand("styleWithCSS", false, false);');
+
+debug('');
+debug('Toggling strikeThrough');
+shouldBe('content("<s><u>a</u>b</s>"); select(0, 2); toggle("strikeThrough")', '"<u>a</u>b"');
+shouldBe('content("<s><u>a</u>b<u>c</u></s>"); select(0, 3); toggle("strikeThrough")', '"<u>a</u>b<u>c</u>"');
+shouldBe('content("<s>a<u>b</u>c</s>"); select(0, 3); toggle("strikeThrough")', '"a<u>b</u>c"');
+shouldBe('content("<s>a<u>b</u>c</s>"); select(1, 3); toggle("strikeThrough")', '"<strike>a</strike><u>b</u>c"');
+shouldBe('content("<s>a<u>b</u>c</s>"); select(0, 2); toggle("strikeThrough")', '"a<u>b</u><strike>c</strike>"');
+shouldBe('content("<s><u>ab</u></s>c"); select(1, 3); toggle("strikeThrough")', '"<u><strike>a</strike>b</u>c"');
+shouldBe('content("<s>a<u>b</u></s>c"); select(1, 3); toggle("strikeThrough")', '"<strike>a</strike><u>b</u>c"');
+shouldBe('content("a<s><u>b</u>c</s>"); select(0, 2); toggle("strikeThrough")', '"<strike>a</strike><s><u>b</u>c</s>"');
+shouldBe('content("a<strike><u>b</u>c</strike>"); select(0, 2); toggle("strikeThrough")', '"<strike>a<u>b</u>c</strike>"');
+shouldBe('content("a<u><s>bc</s></u>"); select(0, 2); toggle("strikeThrough")', '"<strike>a</strike><u><s>bc</s></u>"');
+shouldBe('content("a<s><b><u>bc</u></b></s>"); select(0, 2); toggle("strikeThrough")', '"<strike>a</strike><s><b><u>bc</u></b></s>"');
+shouldBe('content("a<strike><b><u>bc</u></b></strike>"); select(0, 2); toggle("strikeThrough")', '"<strike>a<b><u>bc</u></b></strike>"');
+
+debug('');
+debug('Toggling underline');
+shouldBe('content("<u><s>a</s>b</u>"); select(0, 2); toggle("underline")', '"<s>a</s>b"');
+shouldBe('content("<u><s>a</s>b<s>c</s></u>"); select(0, 3); toggle("underline")', '"<s>a</s>b<s>c</s>"');
+shouldBe('content("<u>a<s>b</s>c</u>"); select(0, 3); toggle("underline")', '"a<s>b</s>c"');
+shouldBe('content("<u>a<s>b</s>c</u>"); select(1, 3); toggle("underline")', '"<u>a</u><s>b</s>c"');
+shouldBe('content("<u>a<s>b</s>c</u>"); select(0, 2); toggle("underline")', '"a<s>b</s><u>c</u>"');
+shouldBe('content("<u><s>ab</s></u>c"); select(1, 3); toggle("underline")', '"<s><u>a</u>b</s>c"');
+shouldBe('content("<u>a<s>b</s></u>c"); select(1, 3); toggle("underline")', '"<u>a</u><s>b</s>c"');
+shouldBe('content("a<u><s>b</s>c</u>"); select(0, 2); toggle("underline")', '"<u>a<s>b</s>c</u>"');
+shouldBe('content("a<s><u>bc</u></s>"); select(0, 2); toggle("underline")', '"<u>a</u><s><u>bc</u></s>"');
+shouldBe('content("a<u><b><s>bc</s></b></u>"); select(0, 2); toggle("underline")', '"<u>a<b><s>bc</s></b></u>"');
+
+evalAndLog('document.execCommand("styleWithCSS", false, true);');
+
+debug('');
+debug('Toggling strikeThrough');
+shouldBe('content(\'<span style="text-decoration: line-through;"><span style="text-decoration: underline">a</span>b</span>\');\n'
+    + 'select(0, 2); toggle("strikeThrough")',
+    '\'<span style="text-decoration: underline;">a</span>b\'');
+shouldBe('content(\'<span style="text-decoration: line-through;"><span style="text-decoration: underline;">a</span>b<span style="text-decoration: underline">c</span></span>\');\n'
+    + 'select(0, 3); toggle("strikeThrough")',
+    '\'<span style="text-decoration: underline;">a</span>b<span style="text-decoration: underline;">c</span>\'');
+shouldBe('content(\'<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>\');\n'
+    + 'select(0, 3); toggle("strikeThrough")',
+    '\'a<span style="text-decoration: underline;">b</span>c\'');
+shouldBe('content(\'<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>\');\n'
+    + 'select(1, 3); toggle("strikeThrough")',
+    '\'<span style="text-decoration: line-through;">a</span><span style="text-decoration: underline;">b</span>c\'');
+shouldBe('content(\'<span style="text-decoration: line-through;">a<span style="text-decoration: underline;">b</span>c</span>\');\n'
+    + 'select(0, 2); toggle("strikeThrough")',
+    '\'a<span style="text-decoration: underline;">b</span><span style="text-decoration: line-through;">c</span>\'');
+shouldBe('content(\'<span style="text-decoration: line-through;"><span style="text-decoration: underline;">ab</span></span>c\');\n'
+    + 'select(1, 3); toggle("strikeThrough")',
+    '\'<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b</span>c\'');
+shouldBe('content(\'a<span style="text-decoration: line-through;"><span style="text-decoration: underline;">b</span>c</span>\');\n'
+    + 'select(0, 2); toggle("strikeThrough")',
+    '\'<span style="text-decoration: line-through;">a<span style="text-decoration: underline line-through;">b</span>c</span>\'');
+shouldBe('content(\'a<span style="text-decoration: underline;"><span style="text-decoration: line-through;">bc</span></span>\');\n'
+    + 'select(0, 2); toggle("strikeThrough")',
+    '\'<span style="text-decoration: line-through;">a</span><span style="text-decoration: underline;"><span style="text-decoration: line-through;">bc</span></span>\'');
+shouldBe('content(\'a<span style="text-decoration: line-through;"><b><span style="text-decoration: underline;">bc</span></b></span>\');\n'
+    + 'select(0, 2); toggle("strikeThrough")',
+    '\'<span style="text-decoration: line-through;">a<b><span style="text-decoration: underline;"><span style="text-decoration: underline line-through;">b</span>c</span></b></span>\'');
+
+debug('');
+debug('Toggling underline');
+shouldBe('content(\'<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b</span>\');\n'
+    + 'select(0, 2); toggle("underline")',
+    '\'<span style="text-decoration: line-through;">a</span>b\'');
+shouldBe('content(\'<span style="text-decoration: underline;"><span style="text-decoration: line-through;">a</span>b<span style="text-decoration: line-through;">c</span></span>\');\n'
+    + 'select(0, 3); toggle("underline")',
+    '\'<span style="text-decoration: line-through;">a</span>b<span style="text-decoration: line-through;">c</span>\'');
+shouldBe('content(\'<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>\');\n'
+    + 'select(0, 3); toggle("underline")',
+    '\'a<span style="text-decoration: line-through;">b</span>c\'');
+shouldBe('content(\'<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>\');\n'
+    + 'select(1, 3); toggle("underline")',
+    '\'<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;">b</span>c\'');
+shouldBe('content(\'<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span>c</span>\');\n'
+    + 'select(0, 2); toggle("underline")',
+    '\'a<span style="text-decoration: line-through;">b</span><span style="text-decoration: underline;">c</span>\'');
+shouldBe('content(\'<span style="text-decoration: underline;"><span style="text-decoration: line-through;">ab</span></span>c\');\n'
+    + 'select(1, 3); toggle("underline")',
+    '\'<span style="text-decoration: line-through;"><span style="text-decoration: underline;">a</span>b</span>c\'');
+shouldBe('content(\'<span style="text-decoration: underline;">a<span style="text-decoration: line-through;">b</span></span>c\');\n'
+    + 'select(1, 3); toggle("underline")',
+    '\'<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;">b</span>c\'');
+shouldBe('content(\'a<span style="text-decoration: underline;"><span style="text-decoration: line-through;">b</span>c</span>\');\n'
+    + 'select(0, 2); toggle("underline")',
+    '\'<span style="text-decoration: underline;">a<span style="text-decoration: line-through underline;">b</span>c</span>\'');
+shouldBe('content(\'a<span style="text-decoration: line-through;"><span style="text-decoration: underline;">bc</span></span>\');\n'
+    + 'select(0, 2); toggle("underline")',
+    '\'<span style="text-decoration: underline;">a</span><span style="text-decoration: line-through;"><span style="text-decoration: underline;">bc</span></span>\'');
+shouldBe('content(\'a<span style="text-decoration: underline;"><b><span style="text-decoration: line-through;">bc</span></b></span>\');\n'
+    + 'select(0, 2); toggle("underline")',
+    '\'<span style="text-decoration: underline;">a<b><span style="text-decoration: line-through;"><span style="text-decoration: underline line-through;">b</span>c</span></b></span>\'');
+
+document.body.removeChild(testContainer);
+
+var successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index b143a8ff5090c11710ec3879cd063abc9da3e9db..fc3fe9b28331f0b2e9e059be534cdfd3b045a22c 100644 (file)
@@ -10,7 +10,7 @@ PASS one strikethrough command converted test to <strike>test</strike>
 PASS one strikethrough command converted <u><b><strike>test</strike></b></u> to <u><b>test</b></u>
 PASS two strikethrough commands converted test to test
 PASS one strikethrough command converted <u>test</u> to <u><strike>test</strike></u>
-PASS one underline command converted <strike>test</strike> to <u><strike>test</strike></u>
+PASS one underline command converted <strike>test</strike> to <strike><u>test</u></strike>
 PASS one strikethrough command converted <span style="text-decoration: overline;">test</span> to <span style="text-decoration: overline;"><strike>test</strike></span>
 PASS one underline command converted <span style="text-decoration: overline;">test</span> to <span style="text-decoration: overline;"><u>test</u></span>
 PASS successfullyParsed is true
index fa6408f870a6fd8afff7baf2305e5aa3635bcf3e..490a9c74bb9fb744e36c9b32f91cdf3fb5d55b99 100644 (file)
@@ -5,7 +5,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 PASS one underline command converted test to <span style="text-decoration: underline;">test</span>
 PASS one underline command converted <span style="text-decoration: underline;">test</span> to test
-PASS one underline command converted <span style="text-decoration: underline line-through overline;">test</span> to <span style="text-decoration: overline line-through;">test</span>
+PASS one underline command converted <span style="text-decoration: underline line-through overline;">test</span> to <span style="text-decoration: line-through overline;">test</span>
 PASS one strikethrough command converted test to <span style="text-decoration: line-through;">test</span>
 PASS one strikethrough command converted <span style="text-decoration: line-through;">test</span> to test
 PASS one strikethrough command converted <span style="text-decoration: underline line-through overline;">test</span> to <span style="text-decoration: underline overline;">test</span>
index 2c95bd6dcfa01f21178134a545c5b96f8868e880..115355b76be108a6c284c23a2992309b2835a91c 100644 (file)
@@ -59,7 +59,7 @@ Unbolding should remove the font-weight but shouldn't remove the style attribute
 
 Undo should reset the style attribute so that "test" is both bold and blue:
 | <span>
-|   style="color: blue; font-weight: 900;"
+|   style="font-weight: 900; color: blue;"
 |   "<#selection-anchor>test<#selection-focus>"
 
 Redo should only remove font-weight and leave "test" blue:
index d080389936fc940d229a81c64d5f7f24da190ba2..30caf9a03b6d28ec8324af9b270a701df702f10b 100644 (file)
@@ -1,3 +1,105 @@
+2015-05-04  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Toggling underline or strike through affects each other
+        https://bugs.webkit.org/show_bug.cgi?id=27818
+
+        Reviewed by Darin Adler.
+
+        This patch introduces a new mechanism to apply and remove text decorations. This is necessary because text
+        decorations are always additive and we can't differentiate whether we're adding or removing a text decoration.
+        Conceptually, we need four values for text decorations: adding underline, removing underline, adding
+        line-through, and removing line-through but we have only three: underline, line-through, none.
+
+        After this patch, there are three mechanism by which text decorations states are kept tracked. While applying
+        or removing text decorations, we use newly added m_underlineChange and m_strikeThroughChange in EditingStyle.
+        For the typing style, we use -webkit-text-decorations-in-effect to store the state since we need to preserve
+        every type of text decorations such as overline in addition to underline and line-through. Once applied, all
+        text decorations should be expressed in terms of the standard text-decoration property.
+
+        Test: editing/execCommand/toggle-mixed-text-decorations.html
+
+        * editing/ApplyStyleCommand.cpp:
+        (WebCore::ApplyStyleCommand::applyBlockStyle):
+        (WebCore::ApplyStyleCommand::removeCSSStyle): conflictsWithInlineStyleOfElement now creates a new inline style
+        instead of a list of properties to remove.
+        (WebCore::ApplyStyleCommand::addBlockStyle):
+        (WebCore::ApplyStyleCommand::applyInlineStyleChange): Merge inline styles instead of adding as string.
+        Otherwise it would generate style content attribute with multiple text-decoration properties.
+
+        * editing/EditingStyle.cpp:
+        (WebCore::HTMLElementEquivalent::matches):
+        (WebCore::HTMLElementEquivalent::propertyExistsInStyle): Takes an EditingStyle instead of StyleProperties.
+        (WebCore::HTMLElementEquivalent::valueIsPresentInStyle):
+        (WebCore::HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent): 
+        (WebCore::HTMLTextDecorationEquivalent::propertyExistsInStyle): Respect newly added m_strikeThroughChange and
+        m_underlineChange in EditingStyle.
+        (WebCore::HTMLTextDecorationEquivalent::valueIsPresentInStyle): Ditto.
+        (WebCore::HTMLTextDecorationEquivalent::changeInStyle): Added. Retrieves the change enum for the associated
+        type of text-decoration (underline or strike through).
+        (WebCore::HTMLAttributeEquivalent::matches):
+        (WebCore::HTMLAttributeEquivalent::valueIsPresentInStyle):
+        (WebCore::EditingStyle::EditingStyle): Initialize m_underlineChange and m_strikeThroughChange. Also use the
+        delegating constructor elsewhere. Also added the missing call to extractFontSizeDelta() in the variant that
+        takes CSSPropertyID and String, and added a variant that takes CSSPropertyID and CSSValueID.
+        (WebCore::EditingStyle::isEmpty): Return false when m_underlineChange and m_strikeThroughChange are not "none".
+        (WebCore::applyTextDecorationChangeToValueList): Added.
+        (WebCore::EditingStyle::overrideTypingStyleAt): Added. Used by Editor::computeAndSetTypingStyle to set a new
+        typing style. Resolve m_underlineChange and m_strikeThroughChange into -webkit-text-decorations-in-effect.
+        (WebCore::EditingStyle::clear): Clear m_underlineChange and m_strikeThroughChange.
+        (WebCore::EditingStyle::copy): Copy m_underlineChange and m_strikeThroughChange.
+        (WebCore::textDecorationValueList): Added.
+        (WebCore::EditingStyle::conflictsWithInlineStyleOfElement): Now takes a pointer to MutableStyleProperties
+        instead of a vector. This was necessary we can't simply remove text-decoration property in ApplyStyleCommand's
+        removeCSSStyle as that would result in unrelated text decorations also getting removed. Also added the code
+        for m_underlineChange and m_strikeThroughChange. Only removing text decoration changes can cause a conflict
+        since text decorations are always additive.
+        (WebCore::EditingStyle::conflictsWithImplicitStyleOfElement): Check isEmpty() instead of the nullity of
+        m_mutableStyle to respect m_underlineChange and m_strikeThroughChange.
+        (WebCore::EditingStyle::conflictsWithImplicitStyleOfAttributes):
+        (WebCore::EditingStyle::extractConflictingImplicitStyleOfAttributes):
+        (WebCore::EditingStyle::styleIsPresentInComputedStyleOfNode): Respect the values of m_underlineChange and
+        m_strikeThroughChange. Here, the style is considered present if it has text decorations that are being added.
+        (WebCore::EditingStyle::elementIsStyledSpanOrHTMLEquivalent):
+        (WebCore::elementMatchesAndPropertyIsNotInInlineStyleDecl): Takes EditingStyle instead of StyleProperties to
+        respect m_underlineChange and m_strikeThroughChange.
+        (WebCore::EditingStyle::mergeInlineAndImplicitStyleOfElement):
+        (WebCore::mergeTextDecorationValues):
+        (WebCore::EditingStyle::mergeStyle): Make a copy of CSSValueList before modifying it since CSSValueList's are
+        shared with other immutable StyleProperties.
+        (WebCore::StyleChange::StyleChange): Set m_applyUnderline, m_applyLineThrough, and m_cssStyle if either
+        m_underlineChange or m_strikeThroughChange are TextDecorationChange::Add in EditingStyle if the current position
+        doesn't already have the matching style.
+        (WebCore::StyleChange::operator==): Moved from the header file. Also added the logic to compare m_cssStyle now
+        that it's a StyleProperties instead of String.
+
+        * editing/EditingStyle.h: Added TextDecorationChange.
+        (WebCore::EditingStyle::create): Added a variant that takes CSSPropertyID and CSSValueID.
+        (WebCore::EditingStyle::conflictsWithInlineStyleOfElement):
+        (WebCore::EditingStyle::setUnderlineChange): Added.
+        (WebCore::EditingStyle::underlineChange): Added.
+        (WebCore::EditingStyle::setStrikeThroughChange): Added.
+        (WebCore::EditingStyle::strikeThroughChange): Added.
+        (WebCore::StyleChange::cssStyle): Now returns StyleProperties* instead of String so that ApplyStyleCommand's
+        applyInlineStyleChange could merge inline styles instead of just appending it to the end.
+        (WebCore::StyleChange::operator==): Moved into the cpp file.
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::applyStyle): Added. This variant takes EditingStyle instead of StyleProperties.
+        (WebCore::Editor::applyStyleToSelection): Ditto.
+        (WebCore::Editor::computeAndSetTypingStyle): Added a variant for EditingStyle. Also use overrideTypingStyleAt
+        to set -webkit-text-decorations-in-effect based on m_underlineChange and m_strikeThroughChange 
+
+        * editing/Editor.h:
+        * editing/EditorCommand.cpp:
+        (WebCore::applyCommandToFrame):
+        (WebCore::isStylePresent): Extracted from executeToggleStyle.
+        (WebCore::executeApplyStyle):
+        (WebCore::executeToggleStyle):
+        (WebCore::executeToggleStyleInList): Deleted.
+        (WebCore::textDecorationChangeForToggling): Added. Used in executeStrikethrough and executeUnderline.
+        (WebCore::executeStrikethrough):
+        (WebCore::executeUnderline):
+
 2015-05-04  Eric Carlson  <eric.carlson@apple.com>
 
         [Mac] Fix build breakage caused by API deprecation
index cab882889c5e9944870e221ceb843d91cb2845f3..990552b7158c81f068c1cdcee176d1edfd58d25b 100644 (file)
@@ -263,7 +263,7 @@ void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
     VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
     while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
         StyleChange styleChange(style, paragraphStart.deepEquivalent());
-        if (styleChange.cssStyle().length() || m_removeOnly) {
+        if (styleChange.cssStyle() || m_removeOnly) {
             RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
             if (!m_removeOnly) {
                 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
@@ -945,17 +945,14 @@ bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element
     if (mode == RemoveNone)
         return style->conflictsWithInlineStyleOfElement(element);
 
-    Vector<CSSPropertyID> properties;
-    if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
+    RefPtr<MutableStyleProperties> newInlineStyle;
+    if (!style->conflictsWithInlineStyleOfElement(element, newInlineStyle, extractedStyle))
         return false;
 
-    // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
-    for (size_t i = 0; i < properties.size(); i++)
-        removeCSSProperty(element, properties[i]);
-
-    // No need to serialize <foo style=""> if we just removed the last css property
-    if (element->inlineStyle()->isEmpty())
+    if (newInlineStyle->isEmpty())
         removeNodeAttribute(element, styleAttr);
+    else
+        setNodeAttribute(element, styleAttr, newInlineStyle->asText());
 
     if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
         removeNodePreservingChildren(element);
@@ -1370,12 +1367,13 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar
 
 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
 {
+    ASSERT(styleChange.cssStyle());
     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
     // inline content.
     if (!block)
         return;
         
-    String cssStyle = styleChange.cssStyle();
+    String cssStyle = styleChange.cssStyle()->asText();
     StringBuilder cssText;
     cssText.append(cssStyle);
     if (const StyleProperties* decl = block->inlineStyle()) {
@@ -1456,21 +1454,17 @@ void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, Pas
         }
     }
 
-    if (styleChange.cssStyle().length()) {
+    if (auto styleToMerge = styleChange.cssStyle()) {
         if (styleContainer) {
-            if (const StyleProperties* existingStyle = styleContainer->inlineStyle()) {
-                String existingText = existingStyle->asText();
-                StringBuilder cssText;
-                cssText.append(existingText);
-                if (!existingText.isEmpty())
-                    cssText.append(' ');
-                cssText.append(styleChange.cssStyle());
-                setNodeAttribute(styleContainer, styleAttr, cssText.toString());
+            if (auto existingStyle = styleContainer->inlineStyle()) {
+                auto inlineStyle = EditingStyle::create(existingStyle);
+                inlineStyle->overrideWithStyle(styleToMerge);
+                setNodeAttribute(styleContainer, styleAttr, inlineStyle->style()->asText());
             } else
-                setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle());
+                setNodeAttribute(styleContainer, styleAttr, styleToMerge->asText());
         } else {
             RefPtr<Element> styleElement = createStyleSpanElement(document());
-            styleElement->setAttribute(styleAttr, styleChange.cssStyle());
+            styleElement->setAttribute(styleAttr, styleToMerge->asText());
             surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
         }
     }
index 8a7120bdfbb5360c162e90eeb8479612049852f1..68b141cf651c90569e78044358a33d4a80f3f14b 100644 (file)
@@ -160,10 +160,10 @@ public:
     HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName);
 
     virtual ~HTMLElementEquivalent() { }
-    virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
+    virtual bool matches(const Element& element) const { return !m_tagName || element.hasTagName(*m_tagName); }
     virtual bool hasAttribute() const { return false; }
-    virtual bool propertyExistsInStyle(const StyleProperties* style) const { return style->getPropertyCSSValue(m_propertyID); }
-    virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const;
+    virtual bool propertyExistsInStyle(const EditingStyle& style) const { return style.m_mutableStyle && style.m_mutableStyle->getPropertyCSSValue(m_propertyID); }
+    virtual bool valueIsPresentInStyle(Element&, const EditingStyle&) const;
     virtual void addToStyle(Element*, EditingStyle*) const;
 
 protected:
@@ -194,9 +194,9 @@ HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primit
     ASSERT(primitiveValue != CSSValueInvalid);
 }
 
-bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const
+bool HTMLElementEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const
 {
-    RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
+    RefPtr<CSSValue> value = style.m_mutableStyle->getPropertyCSSValue(m_propertyID);
     return matches(element) && is<CSSPrimitiveValue>(value.get()) && downcast<CSSPrimitiveValue>(*value).getValueID() == m_primitiveValue->getValueID();
 }
 
@@ -207,39 +207,55 @@ void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
 
 class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
 public:
-    HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName);
+    HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName)
+        : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
+        , m_isUnderline(primitiveValue == CSSValueUnderline)
+    {
+    }
 
-    virtual bool propertyExistsInStyle(const StyleProperties*) const;
-    virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const;
-};
+    bool propertyExistsInStyle(const EditingStyle& style) const override
+    {
+        if (changeInStyle(style) != TextDecorationChange::None)
+            return true;
 
-HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName)
-    : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
-    // m_propertyID is used in HTMLElementEquivalent::addToStyle
-{
-}
+        if (!style.m_mutableStyle)
+            return false;
 
-bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StyleProperties* style) const
-{
-    return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) || style->getPropertyCSSValue(CSSPropertyTextDecoration);
-}
+        auto& mutableStyle = *style.m_mutableStyle;
+        return mutableStyle.getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)
+            || mutableStyle.getPropertyCSSValue(CSSPropertyTextDecoration);
+    }
 
-bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const
-{
-    RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
-    if (!styleValue)
-        styleValue = style->getPropertyCSSValue(CSSPropertyTextDecoration);
-    return matches(element) && is<CSSValueList>(styleValue.get()) && downcast<CSSValueList>(*styleValue).hasValue(m_primitiveValue.get());
-}
+    bool valueIsPresentInStyle(Element& element, const EditingStyle& style) const override
+    {
+        if (!matches(element))
+            return false;
+        auto change = changeInStyle(style);
+        if (change != TextDecorationChange::None)
+            return change == TextDecorationChange::Add;
+        RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
+        if (!styleValue)
+            styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyTextDecoration);
+        return is<CSSValueList>(styleValue.get()) && downcast<CSSValueList>(*styleValue).hasValue(m_primitiveValue.get());
+    }
+
+private:
+    TextDecorationChange changeInStyle(const EditingStyle& style) const
+    {
+        return m_isUnderline ? style.underlineChange() : style.strikeThroughChange();
+    }
+
+    bool m_isUnderline;
+};
 
 class HTMLAttributeEquivalent : public HTMLElementEquivalent {
 public:
     HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
     HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
 
-    bool matches(const Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
+    bool matches(const Element& element) const { return HTMLElementEquivalent::matches(element) && element.hasAttribute(m_attrName); }
     virtual bool hasAttribute() const { return true; }
-    virtual bool valueIsPresentInStyle(Element*, StyleProperties*) const;
+    virtual bool valueIsPresentInStyle(Element&, const EditingStyle&) const;
     virtual void addToStyle(Element*, EditingStyle*) const;
     virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
     inline const QualifiedName& attributeName() const { return m_attrName; }
@@ -260,10 +276,10 @@ HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const Qualifi
 {
 }
 
-bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, StyleProperties* style) const
+bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const
 {
-    RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
-    RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
+    RefPtr<CSSValue> value = attributeValueAsCSSValue(&element);
+    RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(m_propertyID);
     
     return compareCSSValuePtr(value, styleValue);
 }
@@ -315,27 +331,25 @@ float EditingStyle::NoFontDelta = 0.0f;
 
 EditingStyle::EditingStyle()
     : m_shouldUseFixedDefaultFontSize(false)
-    , m_fontSizeDelta(NoFontDelta)
+    , m_underlineChange(static_cast<unsigned>(TextDecorationChange::None))
+    , m_strikeThroughChange(static_cast<unsigned>(TextDecorationChange::None))
 {
 }
 
 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
-    : m_shouldUseFixedDefaultFontSize(false)
-    , m_fontSizeDelta(NoFontDelta)
+    : EditingStyle()
 {
     init(node, propertiesToInclude);
 }
 
 EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
-    : m_shouldUseFixedDefaultFontSize(false)
-    , m_fontSizeDelta(NoFontDelta)
+    : EditingStyle()
 {
     init(position.deprecatedNode(), propertiesToInclude);
 }
 
 EditingStyle::EditingStyle(const StyleProperties* style)
-    : m_shouldUseFixedDefaultFontSize(false)
-    , m_fontSizeDelta(NoFontDelta)
+    : EditingStyle()
 {
     if (style)
         m_mutableStyle = style->mutableCopy();
@@ -343,11 +357,18 @@ EditingStyle::EditingStyle(const StyleProperties* style)
 }
 
 EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
-    : m_mutableStyle(0)
-    , m_shouldUseFixedDefaultFontSize(false)
-    , m_fontSizeDelta(NoFontDelta)
+    : EditingStyle()
 {
     setProperty(propertyID, value);
+    extractFontSizeDelta();
+}
+
+EditingStyle::EditingStyle(CSSPropertyID propertyID, CSSValueID value)
+    : EditingStyle()
+{
+    m_mutableStyle = MutableStyleProperties::create();
+    m_mutableStyle->setProperty(propertyID, value);
+    extractFontSizeDelta();
 }
 
 EditingStyle::~EditingStyle()
@@ -492,7 +513,8 @@ void EditingStyle::extractFontSizeDelta()
 
 bool EditingStyle::isEmpty() const
 {
-    return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
+    return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta
+        && underlineChange() == TextDecorationChange::None && strikeThroughChange() == TextDecorationChange::None;
 }
 
 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
@@ -537,11 +559,61 @@ void EditingStyle::overrideWithStyle(const StyleProperties* style)
     return mergeStyle(style, OverrideValues);
 }
 
+static void applyTextDecorationChangeToValueList(CSSValueList& valueList, TextDecorationChange change, Ref<CSSPrimitiveValue>&& value)
+{
+    switch (change) {
+    case TextDecorationChange::None:
+        break;
+    case TextDecorationChange::Add:
+        valueList.append(WTF::move(value));
+        break;
+    case TextDecorationChange::Remove:
+        valueList.removeAll(&value.get());
+        break;
+    }
+}
+
+void EditingStyle::overrideTypingStyleAt(const EditingStyle& style, const Position& position)
+{
+    mergeStyle(style.m_mutableStyle.get(), OverrideValues);
+
+    m_fontSizeDelta += style.m_fontSizeDelta;
+
+    prepareToApplyAt(position, EditingStyle::PreserveWritingDirection);
+
+    auto underlineChange = style.underlineChange();
+    auto strikeThroughChange = style.strikeThroughChange();
+    if (underlineChange == TextDecorationChange::None && strikeThroughChange == TextDecorationChange::None)
+        return;
+
+    if (!m_mutableStyle)
+        m_mutableStyle = MutableStyleProperties::create();
+
+    Ref<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline);
+    Ref<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough);
+    RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
+    RefPtr<CSSValueList> valueList;
+    if (value && value->isValueList()) {
+        valueList = downcast<CSSValueList>(*value).copy();
+        applyTextDecorationChangeToValueList(*valueList, underlineChange, WTF::move(underline));
+        applyTextDecorationChangeToValueList(*valueList, strikeThroughChange, WTF::move(lineThrough));
+    } else {
+        valueList = CSSValueList::createSpaceSeparated();
+        if (underlineChange == TextDecorationChange::Add)
+            valueList->append(WTF::move(underline));
+        if (strikeThroughChange == TextDecorationChange::Add)
+            valueList->append(WTF::move(lineThrough));
+    }
+    m_mutableStyle->setProperty(CSSPropertyWebkitTextDecorationsInEffect, valueList.get());
+}
+
 void EditingStyle::clear()
 {
     m_mutableStyle.clear();
     m_shouldUseFixedDefaultFontSize = false;
     m_fontSizeDelta = NoFontDelta;
+    setUnderlineChange(TextDecorationChange::None);
+    setStrikeThroughChange(TextDecorationChange::None);
 }
 
 PassRefPtr<EditingStyle> EditingStyle::copy() const
@@ -550,6 +622,8 @@ PassRefPtr<EditingStyle> EditingStyle::copy() const
     if (m_mutableStyle)
         copy->m_mutableStyle = m_mutableStyle->mutableCopy();
     copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
+    copy->m_underlineChange = m_underlineChange;
+    copy->m_strikeThroughChange = m_strikeThroughChange;
     copy->m_fontSizeDelta = m_fontSizeDelta;
     return copy;
 }
@@ -694,16 +768,67 @@ TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
     return state;
 }
 
-bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
+static RefPtr<CSSValueList> textDecorationValueList(const StyleProperties& properties)
+{
+    RefPtr<CSSValue> value = properties.getPropertyCSSValue(CSSPropertyTextDecoration);
+    if (!is<CSSValueList>(value.get()))
+        return nullptr;
+    return downcast<CSSValueList>(value.get());
+}
+
+bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr<MutableStyleProperties>* newInlineStylePtr, EditingStyle* extractedStyle) const
 {
     ASSERT(element);
-    ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
 
     const StyleProperties* inlineStyle = element->inlineStyle();
-    if (!m_mutableStyle || !inlineStyle)
+    if (!inlineStyle)
         return false;
+    bool conflicts = false;
+    RefPtr<MutableStyleProperties> newInlineStyle;
+    if (newInlineStylePtr) {
+        newInlineStyle = inlineStyle->mutableCopy();
+        *newInlineStylePtr = newInlineStyle;
+    }
 
-    unsigned propertyCount = m_mutableStyle->propertyCount();
+    bool shouldRemoveUnderline = underlineChange() == TextDecorationChange::Remove;
+    bool shouldRemoveStrikeThrough = strikeThroughChange() == TextDecorationChange::Remove;
+    if (shouldRemoveUnderline || shouldRemoveStrikeThrough) {
+        if (RefPtr<CSSValueList> valueList = textDecorationValueList(*inlineStyle)) {
+            RefPtr<CSSValueList> newValueList = valueList->copy();
+            RefPtr<CSSValueList> extractedValueList = CSSValueList::createSpaceSeparated();
+
+            Ref<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline);
+            if (shouldRemoveUnderline && valueList->hasValue(underline.ptr())) {
+                if (!newInlineStyle)
+                    return true;
+                newValueList->removeAll(underline.ptr());
+                extractedValueList->append(WTF::move(underline));
+            }
+
+            Ref<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough);
+            if (shouldRemoveStrikeThrough && valueList->hasValue(lineThrough.ptr())) {
+                if (!newInlineStyle)
+                    return true;
+                newValueList->removeAll(lineThrough.ptr());
+                extractedValueList->append(WTF::move(lineThrough));
+            }
+
+            if (extractedValueList->length()) {
+                conflicts = true;
+                if (newValueList->length())
+                    newInlineStyle->setProperty(CSSPropertyTextDecoration, newValueList);
+                else
+                    newInlineStyle->removeProperty(CSSPropertyTextDecoration);
+
+                if (extractedStyle) {
+                    bool isImportant = inlineStyle->propertyIsImportant(CSSPropertyTextDecoration);
+                    extractedStyle->setProperty(CSSPropertyTextDecoration, extractedValueList->cssText(), isImportant);
+                }
+            }
+        }
+    }
+
+    unsigned propertyCount = m_mutableStyle ? m_mutableStyle->propertyCount() : 0;
     for (unsigned i = 0; i < propertyCount; ++i) {
         CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
 
@@ -712,35 +837,36 @@ bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, Edi
             continue;
 
         if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) {
-            if (!conflictingProperties)
+            if (!newInlineStyle)
                 return true;
-            conflictingProperties->append(CSSPropertyTextDecoration);
+            conflicts = true;
+            newInlineStyle->removeProperty(CSSPropertyTextDecoration);
             if (extractedStyle)
                 extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->propertyIsImportant(CSSPropertyTextDecoration));
-            continue;
         }
 
         if (!inlineStyle->getPropertyCSSValue(propertyID))
             continue;
 
         if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
-            if (!conflictingProperties)
+            if (!newInlineStyle)
                 return true;
-            conflictingProperties->append(CSSPropertyDirection);
+            conflicts = true;
+            newInlineStyle->removeProperty(CSSPropertyDirection);
             if (extractedStyle)
                 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
         }
 
-        if (!conflictingProperties)
+        if (!newInlineStyle)
             return true;
 
-        conflictingProperties->append(propertyID);
-
+        conflicts = true;
+        newInlineStyle->removeProperty(propertyID);
         if (extractedStyle)
             extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
     }
 
-    return conflictingProperties && !conflictingProperties->isEmpty();
+    return conflicts;
 }
 
 static const Vector<std::unique_ptr<HTMLElementEquivalent>>& htmlElementEquivalents()
@@ -766,14 +892,13 @@ static const Vector<std::unique_ptr<HTMLElementEquivalent>>& htmlElementEquivale
 
 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
 {
-    if (!m_mutableStyle)
+    if (isEmpty())
         return false;
 
     const Vector<std::unique_ptr<HTMLElementEquivalent>>& HTMLElementEquivalents = htmlElementEquivalents();
-    for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
-        const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
-        if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
-            && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
+    for (auto& equivalent : HTMLElementEquivalents) {
+        if (equivalent->matches(*element) && equivalent->propertyExistsInStyle(*this)
+            && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(*element, *this))) {
             if (extractedStyle)
                 equivalent->addToStyle(element, extractedStyle);
             return true;
@@ -803,13 +928,12 @@ static const Vector<std::unique_ptr<HTMLAttributeEquivalent>>& htmlAttributeEqui
 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
 {
     ASSERT(element);
-    if (!m_mutableStyle)
+    if (isEmpty())
         return false;
 
     const Vector<std::unique_ptr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents();
-    for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
-        if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
-            && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
+    for (auto& equivalent : HTMLAttributeEquivalents) {
+        if (equivalent->matches(*element) && equivalent->propertyExistsInStyle(*this) && !equivalent->valueIsPresentInStyle(*element, *this))
             return true;
     }
 
@@ -834,8 +958,8 @@ bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* elem
         if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
             continue;
 
-        if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
-            || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
+        if (!equivalent->matches(*element) || !equivalent->propertyExistsInStyle(*this)
+            || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(*element, *this)))
             continue;
 
         if (extractedStyle)
@@ -849,10 +973,27 @@ bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* elem
 
 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
 {
-    if (!m_mutableStyle)
+    if (isEmpty())
         return true;
     ComputedStyleExtractor computedStyle(node);
-    return getPropertiesNotIn(*m_mutableStyle, computedStyle)->isEmpty();
+
+    bool shouldAddUnderline = underlineChange() == TextDecorationChange::Add;
+    bool shouldAddLineThrough = strikeThroughChange() == TextDecorationChange::Add;
+    if (shouldAddUnderline || shouldAddLineThrough) {
+        bool hasUnderline = false;
+        bool hasLineThrough = false;
+        if (RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyTextDecoration)) {
+            if (value->isValueList()) {
+                const CSSValueList& valueList = downcast<CSSValueList>(*value);
+                hasUnderline = valueList.hasValue(&cssValuePool().createIdentifierValue(CSSValueUnderline).get());
+                hasLineThrough = valueList.hasValue(&cssValuePool().createIdentifierValue(CSSValueLineThrough).get());
+            }
+        }
+        if ((shouldAddUnderline && !hasUnderline) || (shouldAddLineThrough && !hasLineThrough))
+            return false;
+    }
+
+    return !m_mutableStyle || getPropertiesNotIn(*m_mutableStyle, computedStyle)->isEmpty();
 }
 
 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
@@ -864,7 +1005,7 @@ bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* elemen
         const Vector<std::unique_ptr<HTMLElementEquivalent>>& HTMLElementEquivalents = htmlElementEquivalents();
         size_t i;
         for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
-            if (HTMLElementEquivalents[i]->matches(element)) {
+            if (HTMLElementEquivalents[i]->matches(*element)) {
                 elementIsSpanOrElementEquivalent = true;
                 break;
             }
@@ -877,7 +1018,7 @@ bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* elemen
     unsigned matchedAttributes = 0;
     const Vector<std::unique_ptr<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents = htmlAttributeEquivalents();
     for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
-        if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
+        if (HTMLAttributeEquivalents[i]->matches(*element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
             matchedAttributes++;
     }
 
@@ -969,10 +1110,14 @@ void EditingStyle::mergeInlineStyleOfElement(StyledElement* element, CSSProperty
 }
 
 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const StyledElement* element,
-    EditingStyle::CSSPropertyOverrideMode mode, StyleProperties* style)
+    EditingStyle::CSSPropertyOverrideMode mode, EditingStyle& style)
 {
-    return equivalent->matches(element) && (!element->inlineStyle() || !equivalent->propertyExistsInStyle(element->inlineStyle()))
-        && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
+    if (!equivalent->matches(*element))
+        return false;
+    if (mode != EditingStyle::OverrideValues && equivalent->propertyExistsInStyle(style))
+        return false;
+
+    return !element->inlineStyle() || !equivalent->propertyExistsInStyle(EditingStyle::create(element->inlineStyle()).get());
 }
 
 static PassRefPtr<MutableStyleProperties> extractEditingProperties(const StyleProperties* style, EditingStyle::PropertiesToInclude propertiesToInclude)
@@ -1005,7 +1150,7 @@ void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element,
 
     const Vector<std::unique_ptr<HTMLElementEquivalent>>& elementEquivalents = htmlElementEquivalents();
     for (size_t i = 0; i < elementEquivalents.size(); ++i) {
-        if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
+        if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, *this))
             elementEquivalents[i]->addToStyle(element, this);
     }
 
@@ -1013,7 +1158,7 @@ void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element,
     for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
         if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
             continue; // We don't want to include directionality
-        if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
+        if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, *this))
             attributeEquivalents[i]->addToStyle(element, this);
     }
 }
@@ -1049,16 +1194,16 @@ PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* conte
 }
 
 
-static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
+static void mergeTextDecorationValues(CSSValueList& mergedValue, const CSSValueList& valueToMerge)
 {
-    RefPtr<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline);
-    RefPtr<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough);
+    Ref<CSSPrimitiveValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline);
+    Ref<CSSPrimitiveValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough);
 
-    if (valueToMerge->hasValue(underline.get()) && !mergedValue->hasValue(underline.get()))
-        mergedValue->append(underline.releaseNonNull());
+    if (valueToMerge.hasValue(underline.ptr()) && !mergedValue.hasValue(underline.ptr()))
+        mergedValue.append(WTF::move(underline));
 
-    if (valueToMerge->hasValue(lineThrough.get()) && !mergedValue->hasValue(lineThrough.get()))
-        mergedValue->append(lineThrough.releaseNonNull());
+    if (valueToMerge.hasValue(lineThrough.ptr()) && !mergedValue.hasValue(lineThrough.ptr()))
+        mergedValue.append(WTF::move(lineThrough));
 }
 
 void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideMode mode)
@@ -1077,16 +1222,19 @@ void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideM
         RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
 
         // text decorations never override values.
-        if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
+        if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect)
+            && is<CSSValueList>(*property.value()) && value) {
             if (is<CSSValueList>(*value)) {
-                mergeTextDecorationValues(downcast<CSSValueList>(value.get()), downcast<CSSValueList>(property.value()));
+                RefPtr<CSSValueList> newValue = downcast<CSSValueList>(*value).copy();
+                mergeTextDecorationValues(*newValue, downcast<CSSValueList>(*property.value()));
+                m_mutableStyle->setProperty(property.id(), newValue.release(), property.isImportant());
                 continue;
             }
             value = nullptr; // text-decoration: none is equivalent to not having the property.
         }
 
         if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
-            m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant());
+            m_mutableStyle->setProperty(property.id(), property.value(), property.isImportant());
     }
 
     int oldFontSizeDelta = m_fontSizeDelta;
@@ -1397,8 +1545,8 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position)
     , m_applySubscript(false)
     , m_applySuperscript(false)
 {
-    Document* document = position.anchorNode() ? &position.anchorNode()->document() : 0;
-    if (!style || !style->style() || !document || !document->frame())
+    Document* document = position.deprecatedNode() ? &position.deprecatedNode()->document() : 0;
+    if (!style || style->isEmpty() || !document || !document->frame())
         return;
 
     Node* node = position.containerNode();
@@ -1408,12 +1556,44 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position)
     ComputedStyleExtractor computedStyle(node);
 
     // FIXME: take care of background-color in effect
-    RefPtr<MutableStyleProperties> mutableStyle = getPropertiesNotIn(*style->style(), computedStyle);
+    RefPtr<MutableStyleProperties> mutableStyle = style->style() ?
+        getPropertiesNotIn(*style->style(), computedStyle) : MutableStyleProperties::create();
 
     reconcileTextDecorationProperties(mutableStyle.get());
-    if (!document->frame()->editor().shouldStyleWithCSS())
+    bool shouldStyleWithCSS = document->frame()->editor().shouldStyleWithCSS();
+    if (!shouldStyleWithCSS)
         extractTextStyles(document, *mutableStyle, computedStyle.useFixedFontDefaultSize());
 
+    bool shouldAddUnderline = style->underlineChange() == TextDecorationChange::Add;
+    bool shouldAddStrikeThrough = style->strikeThroughChange() == TextDecorationChange::Add;
+    if (shouldAddUnderline || shouldAddStrikeThrough) {
+        RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyWebkitTextDecorationsInEffect);
+        if (!is<CSSValueList>(value.get()))
+            value = computedStyle.propertyValue(CSSPropertyTextDecoration);
+
+        RefPtr<CSSValueList> valueList;
+        if (is<CSSValueList>(value.get()))
+            valueList = downcast<CSSValueList>(value.get());
+
+        Ref<CSSValue> underline = cssValuePool().createIdentifierValue(CSSValueUnderline);
+        bool hasUnderline = valueList && valueList->hasValue(underline.ptr());
+
+        Ref<CSSValue> lineThrough = cssValuePool().createIdentifierValue(CSSValueLineThrough);
+        bool hasLineThrough = valueList && valueList->hasValue(lineThrough.ptr());
+
+        if (shouldStyleWithCSS) {
+            valueList = valueList ? valueList->copy() : CSSValueList::createSpaceSeparated();
+            if (shouldAddUnderline && !hasUnderline)
+                valueList->append(WTF::move(underline));
+            if (shouldAddStrikeThrough && !hasLineThrough)
+                valueList->append(WTF::move(lineThrough));
+            mutableStyle->setProperty(CSSPropertyTextDecoration, valueList.get());
+        } else {
+            m_applyUnderline = shouldAddUnderline && !hasUnderline;
+            m_applyLineThrough = shouldAddStrikeThrough && !hasLineThrough;
+        }
+    }
+
     // 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);
@@ -1423,8 +1603,25 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position)
     if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
         mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
 
-    // Save the result for later
-    m_cssStyle = mutableStyle->asText().stripWhiteSpace();
+    if (!mutableStyle->isEmpty())
+        m_cssStyle = mutableStyle;
+}
+
+bool StyleChange::operator==(const StyleChange& other)
+{
+    if (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)
+        return false;
+
+    return (!m_cssStyle && !other.m_cssStyle)
+        || (m_cssStyle && other.m_cssStyle && m_cssStyle->asText() == other.m_cssStyle->asText());
 }
 
 static void setTextDecorationProperty(MutableStyleProperties& style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
index 9541caac1b1d2543b6d5afbd283dcfae74c7e436..9b35f309ad9cfe05a6cf2c71a171cc89f9eb2241 100644 (file)
 
 #include "CSSPropertyNames.h"
 #include "CSSValueKeywords.h"
+#include "StyleProperties.h"
 #include "WritingDirection.h"
 #include <wtf/Forward.h>
+#include <wtf/HashMap.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
 #include <wtf/TriState.h>
@@ -61,6 +63,8 @@ class StyleProperties;
 class StyledElement;
 class VisibleSelection;
 
+enum class TextDecorationChange { None, Add, Remove };
+
 class EditingStyle : public RefCounted<EditingStyle> {
 public:
 
@@ -95,6 +99,11 @@ public:
         return adoptRef(*new EditingStyle(propertyID, value));
     }
 
+    static Ref<EditingStyle> create(CSSPropertyID propertyID, CSSValueID value)
+    {
+        return adoptRef(*new EditingStyle(propertyID, value));
+    }
+
     WEBCORE_EXPORT ~EditingStyle();
 
     MutableStyleProperties* style() { return m_mutableStyle.get(); }
@@ -102,6 +111,7 @@ public:
     bool isEmpty() const;
     void setStyle(PassRefPtr<MutableStyleProperties>);
     void overrideWithStyle(const StyleProperties*);
+    void overrideTypingStyleAt(const EditingStyle&, const Position&);
     void clear();
     PassRefPtr<EditingStyle> copy() const;
     PassRefPtr<EditingStyle> extractAndRemoveBlockProperties();
@@ -115,9 +125,10 @@ public:
     TriState triStateOfStyle(EditingStyle*) const;
     TriState triStateOfStyle(const VisibleSelection&) const;
     bool conflictsWithInlineStyleOfElement(StyledElement* element) const { return conflictsWithInlineStyleOfElement(element, 0, 0); }
-    bool conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>& conflictingProperties) const
+    bool conflictsWithInlineStyleOfElement(StyledElement* element, RefPtr<MutableStyleProperties>& newInlineStyle,
+        EditingStyle* extractedStyle) const
     {
-        return conflictsWithInlineStyleOfElement(element, extractedStyle, &conflictingProperties);
+        return conflictsWithInlineStyleOfElement(element, &newInlineStyle, extractedStyle);
     }
     bool conflictsWithImplicitStyleOfElement(HTMLElement*, EditingStyle* extractedStyle = 0, ShouldExtractMatchingStyle = DoNotExtractMatchingStyle) const;
     bool conflictsWithImplicitStyleOfAttributes(HTMLElement*) const;
@@ -144,6 +155,11 @@ public:
     float fontSizeDelta() const { return m_fontSizeDelta; }
     bool hasFontSizeDelta() const { return m_fontSizeDelta != NoFontDelta; }
     bool shouldUseFixedDefaultFontSize() const { return m_shouldUseFixedDefaultFontSize; }
+    
+    void setUnderlineChange(TextDecorationChange change) { m_underlineChange = static_cast<unsigned>(change); }
+    TextDecorationChange underlineChange() const { return static_cast<TextDecorationChange>(m_underlineChange); }
+    void setStrikeThroughChange(TextDecorationChange change) { m_strikeThroughChange = static_cast<unsigned>(change); }
+    TextDecorationChange strikeThroughChange() const { return static_cast<TextDecorationChange>(m_strikeThroughChange); }
 
     static PassRefPtr<EditingStyle> styleAtSelectionStart(const VisibleSelection&, bool shouldUseBackgroundColorInEffect = false);
     static WritingDirection textDirectionForSelection(const VisibleSelection&, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings);
@@ -153,37 +169,33 @@ private:
     EditingStyle(const Position&, PropertiesToInclude);
     explicit EditingStyle(const StyleProperties*);
     EditingStyle(CSSPropertyID, const String& value);
+    EditingStyle(CSSPropertyID, CSSValueID);
     void init(Node*, PropertiesToInclude);
     void removeTextFillAndStrokeColorsIfNeeded(RenderStyle*);
     void setProperty(CSSPropertyID, const String& value, bool important = false);
     void extractFontSizeDelta();
     template<typename T> TriState triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties) const;
-    bool conflictsWithInlineStyleOfElement(StyledElement*, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const;
+    bool conflictsWithInlineStyleOfElement(StyledElement*, RefPtr<MutableStyleProperties>* newInlineStyle, EditingStyle* extractedStyle) const;
     void mergeInlineAndImplicitStyleOfElement(StyledElement*, CSSPropertyOverrideMode, PropertiesToInclude);
     void mergeStyle(const StyleProperties*, CSSPropertyOverrideMode);
 
     RefPtr<MutableStyleProperties> m_mutableStyle;
-    bool m_shouldUseFixedDefaultFontSize;
-    float m_fontSizeDelta;
+    unsigned m_shouldUseFixedDefaultFontSize : 1;
+    unsigned m_underlineChange : 2;
+    unsigned m_strikeThroughChange : 2;
+    float m_fontSizeDelta = NoFontDelta;
 
     friend class HTMLElementEquivalent;
     friend class HTMLAttributeEquivalent;
+    friend class HTMLTextDecorationEquivalent;
 };
 
 class StyleChange {
 public:
-    StyleChange()
-        : m_applyBold(false)
-        , m_applyItalic(false)
-        , m_applyUnderline(false)
-        , m_applyLineThrough(false)
-        , m_applySubscript(false)
-        , m_applySuperscript(false)
-    { }
-
+    StyleChange() { }
     StyleChange(EditingStyle*, const Position&);
 
-    String cssStyle() const { return m_cssStyle; }
+    const StyleProperties* cssStyle() const { return m_cssStyle.get(); }
     bool applyBold() const { return m_applyBold; }
     bool applyItalic() const { return m_applyItalic; }
     bool applyUnderline() const { return m_applyUnderline; }
@@ -198,19 +210,7 @@ public:
     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&);
     bool operator!=(const StyleChange& other)
     {
         return !(*this == other);
@@ -218,13 +218,13 @@ public:
 private:
     void extractTextStyles(Document*, MutableStyleProperties&, bool shouldUseFixedFontDefaultSize);
 
-    String m_cssStyle;
-    bool m_applyBold;
-    bool m_applyItalic;
-    bool m_applyUnderline;
-    bool m_applyLineThrough;
-    bool m_applySubscript;
-    bool m_applySuperscript;
+    RefPtr<MutableStyleProperties> m_cssStyle;
+    bool m_applyBold = false;
+    bool m_applyItalic = false;
+    bool m_applyUnderline = false;
+    bool m_applyLineThrough = false;
+    bool m_applySubscript = false;
+    bool m_applySuperscript = false;
     String m_applyFontColor;
     String m_applyFontFace;
     String m_applyFontSize;
index a77286afb6028027c86e538b84d5fb0e0c47b241..3c6104c20bb2105aa9cf311d28ad66161c3444a4 100644 (file)
@@ -887,7 +887,7 @@ void Editor::applyStyle(StyleProperties* style, EditAction editingAction)
         // do nothing
         break;
     case VisibleSelection::CaretSelection:
-        computeAndSetTypingStyle(style, editingAction);
+        computeAndSetTypingStyle(EditingStyle::create(style), editingAction);
         break;
     case VisibleSelection::RangeSelection:
         if (style)
@@ -895,6 +895,22 @@ void Editor::applyStyle(StyleProperties* style, EditAction editingAction)
         break;
     }
 }
+
+void Editor::applyStyle(RefPtr<EditingStyle>&& style, EditAction editingAction)
+{
+    switch (m_frame.selection().selection().selectionType()) {
+    case VisibleSelection::NoSelection:
+        // do nothing
+        break;
+    case VisibleSelection::CaretSelection:
+        computeAndSetTypingStyle(*style, editingAction);
+        break;
+    case VisibleSelection::RangeSelection:
+        if (style)
+            applyCommand(ApplyStyleCommand::create(document(), style.get(), editingAction));
+        break;
+    }
+}
     
 bool Editor::shouldApplyStyle(StyleProperties* style, Range* range)
 {   
@@ -920,8 +936,21 @@ void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAct
     if (!style || style->isEmpty() || !canEditRichly())
         return;
 
-    if (client() && client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get()))
-        applyStyle(style, editingAction);
+    if (!client() || !client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get()))
+        return;
+    applyStyle(style, editingAction);
+}
+
+void Editor::applyStyleToSelection(RefPtr<EditingStyle>&& style, EditAction editingAction)
+{
+    if (!style || style->isEmpty() || !canEditRichly())
+        return;
+
+    // FIXME: This is wrong for text decorations since m_mutableStyle is empty.
+    if (!client() || !client()->shouldApplyStyle(style->style(), m_frame.selection().toNormalizedRange().get()))
+        return;
+
+    applyStyle(WTF::move(style), editingAction);
 }
 
 void Editor::applyParagraphStyleToSelection(StyleProperties* style, EditAction editingAction)
@@ -2952,22 +2981,20 @@ bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const V
     return client() && client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(), affinity, stillSelecting);
 }
 
-void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editingAction)
+void Editor::computeAndSetTypingStyle(EditingStyle& style, EditAction editingAction)
 {
-    if (!style || style->isEmpty()) {
+    if (style.isEmpty()) {
         m_frame.selection().clearTypingStyle();
         return;
     }
 
     // Calculate the current typing style.
     RefPtr<EditingStyle> typingStyle;
-    if (m_frame.selection().typingStyle()) {
-        typingStyle = m_frame.selection().typingStyle()->copy();
-        typingStyle->overrideWithStyle(style);
-    } else
-        typingStyle = EditingStyle::create(style);
-
-    typingStyle->prepareToApplyAt(m_frame.selection().selection().visibleStart().deepEquivalent(), EditingStyle::PreserveWritingDirection);
+    if (auto existingTypingStyle = m_frame.selection().typingStyle())
+        typingStyle = existingTypingStyle->copy();
+    else
+        typingStyle = EditingStyle::create();
+    typingStyle->overrideTypingStyleAt(style, m_frame.selection().selection().visibleStart().deepEquivalent());
 
     // Handle block styles, substracting these from the typing style.
     RefPtr<EditingStyle> blockStyle = typingStyle->extractAndRemoveBlockProperties();
@@ -2978,6 +3005,11 @@ void Editor::computeAndSetTypingStyle(StyleProperties* style, EditAction editing
     m_frame.selection().setTypingStyle(typingStyle);
 }
 
+void Editor::computeAndSetTypingStyle(StyleProperties& properties, EditAction editingAction)
+{
+    return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction);
+}
+
 void Editor::textFieldDidBeginEditing(Element* e)
 {
     if (client())
index 1313db3dd01d951bde98ffadac8005ffd94fd078..90976fa8bab779f30f623c5e65979f8215e21e0f 100644 (file)
@@ -183,8 +183,10 @@ public:
     bool dispatchCPPEvent(const AtomicString&, DataTransferAccessPolicy);
     
     WEBCORE_EXPORT void applyStyle(StyleProperties*, EditAction = EditActionUnspecified);
+    void applyStyle(RefPtr<EditingStyle>&&, EditAction);
     void applyParagraphStyle(StyleProperties*, EditAction = EditActionUnspecified);
     WEBCORE_EXPORT void applyStyleToSelection(StyleProperties*, EditAction);
+    void applyStyleToSelection(RefPtr<EditingStyle>&&, EditAction);
     void applyParagraphStyleToSelection(StyleProperties*, EditAction);
 
     void appliedEditing(PassRefPtr<CompositeEditCommand>);
@@ -361,7 +363,8 @@ public:
     const VisibleSelection& mark() const; // Mark, to be used as emacs uses it.
     void setMark(const VisibleSelection&);
 
-    WEBCORE_EXPORT void computeAndSetTypingStyle(StyleProperties* , EditAction = EditActionUnspecified);
+    void computeAndSetTypingStyle(EditingStyle& , EditAction = EditActionUnspecified);
+    WEBCORE_EXPORT void computeAndSetTypingStyle(StyleProperties& , EditAction = EditActionUnspecified);
     WEBCORE_EXPORT void applyEditingStyleToBodyElement() const;
     void applyEditingStyleToElement(Element*) const;
 
index 7a81668355b7ee32b056a60e3dc78f6d4b236d99..330622d54d195e7d3f7f6d2e0ece87a209e18114 100644 (file)
@@ -98,77 +98,46 @@ static Frame* targetFrame(Frame& frame, Event* event)
     return node->document().frame();
 }
 
-static bool applyCommandToFrame(Frame& frame, EditorCommandSource source, EditAction action, StyleProperties* style)
+static bool applyCommandToFrame(Frame& frame, EditorCommandSource source, EditAction action, RefPtr<EditingStyle>&& style)
 {
     // FIXME: We don't call shouldApplyStyle when the source is DOM; is there a good reason for that?
     switch (source) {
     case CommandFromMenuOrKeyBinding:
-        frame.editor().applyStyleToSelection(style, action);
+        frame.editor().applyStyleToSelection(WTF::move(style), action);
         return true;
     case CommandFromDOM:
     case CommandFromDOMWithUserInterface:
-        frame.editor().applyStyle(style);
+        frame.editor().applyStyle(WTF::move(style), EditActionUnspecified);
         return true;
     }
     ASSERT_NOT_REACHED();
     return false;
 }
 
-static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue)
+static bool isStylePresent(Editor& editor, CSSPropertyID propertyID, const char* onValue)
 {
-    RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
-    style->setProperty(propertyID, propertyValue);
-    return applyCommandToFrame(frame, source, action, style.get());
+    // Style is considered present when
+    // Mac: present at the beginning of selection
+    // Windows: present throughout the selection
+    if (editor.behavior().shouldToggleStyleBasedOnStartOfSelection())
+        return editor.selectionStartHasStyle(propertyID, onValue);
+    return editor.selectionHasStyle(propertyID, onValue) == TrueTriState;
 }
 
-static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValueID propertyValue)
+static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue)
 {
-    RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
-    style->setProperty(propertyID, propertyValue);
-    return applyCommandToFrame(frame, source, action, style.get());
+    return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, propertyValue));
 }
 
-// FIXME: executeToggleStyleInList does not handle complicated cases such as <b><u>hello</u>world</b> properly.
-//        This function must use Editor::selectionHasStyle to determine the current style but we cannot fix this
-//        until https://bugs.webkit.org/show_bug.cgi?id=27818 is resolved.
-static bool executeToggleStyleInList(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValue* value)
+static bool executeApplyStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValueID propertyValue)
 {
-    RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(frame.selection().selection());
-    if (!selectionStyle || !selectionStyle->style())
-        return false;
-
-    RefPtr<CSSValue> selectedCSSValue = selectionStyle->style()->getPropertyCSSValue(propertyID);
-    String newStyle = ASCIILiteral("none");
-    if (is<CSSValueList>(*selectedCSSValue)) {
-        RefPtr<CSSValueList> selectedCSSValueList = downcast<CSSValueList>(selectedCSSValue.get());
-        if (!selectedCSSValueList->removeAll(value))
-            selectedCSSValueList->append(*value);
-        if (selectedCSSValueList->length())
-            newStyle = selectedCSSValueList->cssText();
-
-    } else if (selectedCSSValue->cssText() == "none")
-        newStyle = value->cssText();
-
-    // FIXME: We shouldn't be having to convert new style into text.  We should have setPropertyCSSValue.
-    RefPtr<MutableStyleProperties> newMutableStyle = MutableStyleProperties::create();
-    newMutableStyle->setProperty(propertyID, newStyle);
-    return applyCommandToFrame(frame, source, action, newMutableStyle.get());
+    return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, propertyValue));
 }
 
 static bool executeToggleStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const char* offValue, const char* onValue)
 {
-    // Style is considered present when
-    // Mac: present at the beginning of selection
-    // other: present throughout the selection
-
-    bool styleIsPresent;
-    if (frame.editor().behavior().shouldToggleStyleBasedOnStartOfSelection())
-        styleIsPresent = frame.editor().selectionStartHasStyle(propertyID, onValue);
-    else
-        styleIsPresent = frame.editor().selectionHasStyle(propertyID, onValue) == TrueTriState;
-
-    RefPtr<EditingStyle> style = EditingStyle::create(propertyID, styleIsPresent ? offValue : onValue);
-    return applyCommandToFrame(frame, source, action, style->style());
+    bool styleIsPresent = isStylePresent(frame.editor(), propertyID, onValue);
+    return applyCommandToFrame(frame, source, action, EditingStyle::create(propertyID, styleIsPresent ? offValue : onValue));
 }
 
 static bool executeApplyParagraphStyle(Frame& frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue)
@@ -1058,10 +1027,17 @@ static bool executeSetMark(Frame& frame, Event*, EditorCommandSource, const Stri
     return true;
 }
 
+static TextDecorationChange textDecorationChangeForToggling(Editor& editor, CSSPropertyID propertyID, const char* onValue)
+{
+    return isStylePresent(editor, propertyID, onValue) ? TextDecorationChange::Remove : TextDecorationChange::Add;
+}
+
 static bool executeStrikethrough(Frame& frame, Event*, EditorCommandSource source, const String&)
 {
-    RefPtr<CSSPrimitiveValue> lineThrough = CSSPrimitiveValue::createIdentifier(CSSValueLineThrough);
-    return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, lineThrough.get());
+    RefPtr<EditingStyle> style = EditingStyle::create();
+    style->setStrikeThroughChange(textDecorationChangeForToggling(frame.editor(), CSSPropertyWebkitTextDecorationsInEffect, "line-through"));
+    // FIXME: Needs a new EditAction!
+    return applyCommandToFrame(frame, source, EditActionUnderline, WTF::move(style));
 }
 
 static bool executeStyleWithCSS(Frame& frame, Event*, EditorCommandSource, const String& value)
@@ -1125,8 +1101,10 @@ static bool executeTranspose(Frame& frame, Event*, EditorCommandSource, const St
 
 static bool executeUnderline(Frame& frame, Event*, EditorCommandSource source, const String&)
 {
-    RefPtr<CSSPrimitiveValue> underline = CSSPrimitiveValue::createIdentifier(CSSValueUnderline);
-    return executeToggleStyleInList(frame, source, EditActionUnderline, CSSPropertyWebkitTextDecorationsInEffect, underline.get());
+    RefPtr<EditingStyle> style = EditingStyle::create();
+    TextDecorationChange change = textDecorationChangeForToggling(frame.editor(), CSSPropertyWebkitTextDecorationsInEffect, "underline");
+    style->setUnderlineChange(change);
+    return applyCommandToFrame(frame, source, EditActionUnderline, WTF::move(style));
 }
 
 static bool executeUndo(Frame& frame, Event*, EditorCommandSource, const String&)
index fffa8c33482908ec6d96e3541266450c27c53e87..529ff9a8839ee9355a420c8ff92eaf097d46b6aa 100644 (file)
@@ -1,3 +1,13 @@
+2015-05-04  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Toggling underline or strike through affects each other
+        https://bugs.webkit.org/show_bug.cgi?id=27818
+
+        Reviewed by Darin Adler.
+
+        * WebView/WebFrame.mm:
+        (-[WebFrame _setTypingStyle:withUndoAction:]):
+
 2015-05-01  Martin Robinson  <mrobinson@igalia.com>
 
         USE(...) macro should expect unprefixed variables
index ec15ac35330996832b4f7415e66a3fd1aca274f5..b38e4b83127df69abc9dfbb8fcad1d652a2fb77b 100644 (file)
@@ -941,7 +941,7 @@ static inline WebDataSource *dataSource(DocumentLoader* loader)
         return;
     // FIXME: We shouldn't have to create a copy here.
     Ref<MutableStyleProperties> properties(core(style)->copyProperties());
-    _private->coreFrame->editor().computeAndSetTypingStyle(properties.ptr(), undoAction);
+    _private->coreFrame->editor().computeAndSetTypingStyle(properties.get(), undoAction);
 }
 
 #if ENABLE(DRAG_SUPPORT)