2010-10-21 Ryosuke Niwa <rniwa@webkit.org>
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Oct 2010 01:30:04 +0000 (01:30 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 Oct 2010 01:30:04 +0000 (01:30 +0000)
        Reviewed by Tony Chang.

        removeFormat needs to be reimplemented
        https://bugs.webkit.org/show_bug.cgi?id=43017

        Reimplemented execCommand('RemoveFormat', false, null). New implementation removes
        the same elements removed by Internet Explorer. Because WebKit supports StyleWithCSS
        we also reset any editing styles to match that of the root editable element
        while Internet Explorer does not remove any CSS styles.

        New implementation uses ApplyStyleCommand to remove appropriate elements and reset the style.
        Added new constructor and member variable to ApplyStyleCommand to support mass-removal of elements
        since it's inefficient to call ApplyStyleCommand on each element we're removing.

        To avoid an infinite loop in pushDownInlineStyleAroundNode when mass-removing, WebKit no longer
        push down element one level at a time. Instead, we keep a stack of styled elements to be applied,
        and apply wrap siblings of targetNode's ancestors by all of them at once.

        Tests: editing/execCommand/remove-format-elements.html
               editing/execCommand/remove-format-multiple-elements.html

        * editing/ApplyStyleCommand.cpp:
        (WebCore::ApplyStyleCommand::ApplyStyleCommand): Added; this version takes style and a function pointer
        to a boolean function that determines which element needs to removed, and set m_removeOnly to true.
        (WebCore::ApplyStyleCommand::doApply): Added support for m_isInlineElementToRemoveFunction.
        (WebCore::ApplyStyleCommand::applyBlockStyle): Ditto.
        (WebCore::ApplyStyleCommand::applyInlineStyleToNodeRange): Exits early if m_removeOnly is true.
        (WebCore::ApplyStyleCommand::isStyledInlineElementToRemove): Added.
        (WebCore::ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle): Calls isStyledInlineElementToRemove.
        (WebCore::ApplyStyleCommand::removeInlineStyleFromElement): Ditto.
        (WebCore::ApplyStyleCommand::removeInlineStyle): Ditto.
        (WebCore::ApplyStyleCommand::pushDownInlineStyleAroundNode): See above.
        * editing/ApplyStyleCommand.h:
        (WebCore::ApplyStyleCommand::create): Added.
        * editing/RemoveFormatCommand.cpp:
        (WebCore::isElementForRemoveFormatCommand): Added.
        (WebCore::RemoveFormatCommand::doApply): Rewritten.
2010-10-21  Ryosuke Niwa  <rniwa@webkit.org>

        Reviewed by Tony Chang.

        removeFormat needs to be reimplemented
        https://bugs.webkit.org/show_bug.cgi?id=43017

        * editing/execCommand/19403-expected.txt: hr element is no longer removed erroneously.
        * editing/execCommand/19403.html: Updated the test description.
        * editing/execCommand/4786404-1-expected.txt: Change in text nodes but identical rendering.
        * editing/execCommand/4786404-2-expected.txt: Ditto.
        * editing/execCommand/4920488-expected.txt: Preserves anchor element on RemoveFormat.
        * editing/execCommand/4920488.html: Updated the test description.
        * editing/execCommand/4920742-1-expected.txt: Preserves div elements.
        * editing/execCommand/5049671.html: Updated the test to dump twice before and after RemoveFormat.
        This was a test to ensure WebKit removes anchor elements but we no longer removes anchor elements.
        * editing/execCommand/5049671-expected.txt:
        * editing/execCommand/5573879.html: Updated the test description because WebKit no longer removes
        lists on RemoveFormat.
        * editing/execCommand/5573879-expected.txt:
        * editing/execCommand/5770834-1-expected.txt: Removed redundant text-align property value.
        * editing/execCommand/remove-format-elements-expected.txt: Added.
        * editing/execCommand/remove-format-elements.html: Added.
        * editing/execCommand/remove-format-multiple-elements-expected.txt: Added.
        * editing/execCommand/remove-format-multiple-elements.html: Added.
        * editing/execCommand/remove-formatting-2-expected.txt: Change in text nodes but identical rendering.
        * editing/execCommand/remove-formatting-expected.txt: WebKit no longer removes anchor, table,
        tbody, tr, and td elements.
        * editing/execCommand/script-tests/remove-format-multiple-elements.js: Added.
        (testRemoveFormat):
        (selectAll):
        (selectSecondWord):
        (selectFirstTwoWords):
        (selectLastTwoWords):
        (selectLastWord):
        * editing/execCommand/script-tests/toggle-link.js: Anchor wraps div instead of div wrapping anchor.
        * editing/execCommand/script-tests/toggle-unlink.js: Ditto.
        * editing/execCommand/toggle-link-expected.txt: Ditto.
        * editing/execCommand/toggle-unlink-expected.txt: Ditto.
        * editing/execCommand/unlink-expected.txt: i wraps div instead of i wrapping anchor.
        * editing/inserting/space-after-removeformat-expected.txt: Editing delegate change.

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

30 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/execCommand/19403-expected.txt
LayoutTests/editing/execCommand/19403.html
LayoutTests/editing/execCommand/4786404-1-expected.txt
LayoutTests/editing/execCommand/4786404-2-expected.txt
LayoutTests/editing/execCommand/4920488-expected.txt
LayoutTests/editing/execCommand/4920488.html
LayoutTests/editing/execCommand/4920742-1-expected.txt
LayoutTests/editing/execCommand/5049671-expected.txt
LayoutTests/editing/execCommand/5049671.html
LayoutTests/editing/execCommand/5573879-expected.txt
LayoutTests/editing/execCommand/5573879.html
LayoutTests/editing/execCommand/5770834-1-expected.txt
LayoutTests/editing/execCommand/remove-format-elements-expected.txt [new file with mode: 0644]
LayoutTests/editing/execCommand/remove-format-elements.html [new file with mode: 0644]
LayoutTests/editing/execCommand/remove-format-multiple-elements-expected.txt [new file with mode: 0644]
LayoutTests/editing/execCommand/remove-format-multiple-elements.html [new file with mode: 0644]
LayoutTests/editing/execCommand/remove-formatting-2-expected.txt
LayoutTests/editing/execCommand/remove-formatting-expected.txt
LayoutTests/editing/execCommand/script-tests/remove-format-multiple-elements.js [new file with mode: 0644]
LayoutTests/editing/execCommand/script-tests/toggle-link.js
LayoutTests/editing/execCommand/script-tests/toggle-unlink.js
LayoutTests/editing/execCommand/toggle-link-expected.txt
LayoutTests/editing/execCommand/toggle-unlink-expected.txt
LayoutTests/editing/execCommand/unlink-expected.txt
LayoutTests/editing/inserting/space-after-removeformat-expected.txt
WebCore/ChangeLog
WebCore/editing/ApplyStyleCommand.cpp
WebCore/editing/ApplyStyleCommand.h
WebCore/editing/RemoveFormatCommand.cpp

index af7b8f5..af4c2fc 100644 (file)
@@ -1,3 +1,45 @@
+2010-10-21  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Tony Chang.
+
+        removeFormat needs to be reimplemented
+        https://bugs.webkit.org/show_bug.cgi?id=43017
+
+        * editing/execCommand/19403-expected.txt: hr element is no longer removed erroneously.
+        * editing/execCommand/19403.html: Updated the test description.
+        * editing/execCommand/4786404-1-expected.txt: Change in text nodes but identical rendering.
+        * editing/execCommand/4786404-2-expected.txt: Ditto.
+        * editing/execCommand/4920488-expected.txt: Preserves anchor element on RemoveFormat.
+        * editing/execCommand/4920488.html: Updated the test description.
+        * editing/execCommand/4920742-1-expected.txt: Preserves div elements.
+        * editing/execCommand/5049671.html: Updated the test to dump twice before and after RemoveFormat.
+        This was a test to ensure WebKit removes anchor elements but we no longer removes anchor elements.
+        * editing/execCommand/5049671-expected.txt:
+        * editing/execCommand/5573879.html: Updated the test description because WebKit no longer removes
+        lists on RemoveFormat.
+        * editing/execCommand/5573879-expected.txt:
+        * editing/execCommand/5770834-1-expected.txt: Removed redundant text-align property value.
+        * editing/execCommand/remove-format-elements-expected.txt: Added.
+        * editing/execCommand/remove-format-elements.html: Added.
+        * editing/execCommand/remove-format-multiple-elements-expected.txt: Added.
+        * editing/execCommand/remove-format-multiple-elements.html: Added.
+        * editing/execCommand/remove-formatting-2-expected.txt: Change in text nodes but identical rendering.
+        * editing/execCommand/remove-formatting-expected.txt: WebKit no longer removes anchor, table,
+        tbody, tr, and td elements.
+        * editing/execCommand/script-tests/remove-format-multiple-elements.js: Added.
+        (testRemoveFormat):
+        (selectAll):
+        (selectSecondWord):
+        (selectFirstTwoWords):
+        (selectLastTwoWords):
+        (selectLastWord):
+        * editing/execCommand/script-tests/toggle-link.js: Anchor wraps div instead of div wrapping anchor.
+        * editing/execCommand/script-tests/toggle-unlink.js: Ditto.
+        * editing/execCommand/toggle-link-expected.txt: Ditto.
+        * editing/execCommand/toggle-unlink-expected.txt: Ditto.
+        * editing/execCommand/unlink-expected.txt: i wraps div instead of i wrapping anchor.
+        * editing/inserting/space-after-removeformat-expected.txt: Editing delegate change.
+
 2010-10-21  Tony Gentilcore  <tonyg@chromium.org>
 
         Reviewed by Adam Barth.
index 0e58379..3a776ca 100644 (file)
@@ -1,2 +1,2 @@
-This tests for an ASSERT during a RemoveFormat call when it's called on a selection containing only a horizontal rule. It should not ASSERT. Bug: It should also not remove the horizontal rule.
-<br>
+This tests for an ASSERT during a RemoveFormat call when it's called on a selection containing only a horizontal rule. It should not ASSERT.
+<hr>
index 2bffb30..430249d 100644 (file)
@@ -1,4 +1,4 @@
-<div id="description">This tests for an ASSERT during a RemoveFormat call when it's called on a selection containing only a horizontal rule.  It should not ASSERT.  Bug: It should also not remove the horizontal rule.</div>
+<div id="description">This tests for an ASSERT during a RemoveFormat call when it's called on a selection containing only a horizontal rule.  It should not ASSERT.</div>
 <div id="edit" contentEditable="true"><hr></div>
 
 <script>
index 829d1de..46ef7e6 100644 (file)
@@ -3,4 +3,6 @@ This tests for a bug where performing the Remove Format operation on content wou
 |   contenteditable="true"
 |   id="div"
 |   style="font-weight: normal; color: black;"
-|   "<#selection-anchor>foo bar baz<#selection-focus>"
+|   "<#selection-anchor>foo "
+|   "bar"
+|   " baz<#selection-focus>"
index 5dd8a2a..7f0b4e5 100644 (file)
@@ -2,4 +2,5 @@ This tests for a bug where performing the Remove Format operation on a selection
 | <div>
 |   contenteditable="true"
 |   id="div"
-|   "<#selection-anchor>foo bar baz<#selection-focus>"
+|   "<#selection-anchor>foo"
+|   " bar baz<#selection-focus>"
index 98cd5e0..8285b0d 100644 (file)
@@ -1,7 +1,12 @@
-This tests for a bug in GMail's Editor, they try to extract the contents of a range that has had it's contents removed from the document by an editing command.  You should see 'dogfood' unstyled below.
+This tests for a bug in GMail's Editor, they try to extract the contents of a range that has had it's contents removed from the document by an editing command.  Since the bug 43017 requires WebKit does not remove anchor elements, div should be empty after the extraction. We currently leave anchor element in the div due to the Bug 47916.
 
 After removeFormat:
-| "<#selection-anchor>dogfood<#selection-focus>"
+| "<#selection-anchor>dog"
+| <a>
+|   href="http://www.google.com/"
+|   "food<#selection-focus>"
 
 After extractContents():
-| "<#selection-anchor>dogfood<#selection-focus>"
+| ""
+| <a>
+|   href="http://www.google.com/"
index f884b64..33619a5 100644 (file)
@@ -5,7 +5,7 @@
 <div id="div" contenteditable="true">dog<a href="http://www.google.com/">food</a></div>
 <script>
 
-Markup.description("This tests for a bug in GMail's Editor, they try to extract the contents of a range that has had it's contents removed from the document by an editing command.  You should see 'dogfood' unstyled below.");
+Markup.description("This tests for a bug in GMail's Editor, they try to extract the contents of a range that has had it's contents removed from the document by an editing command.  Since the bug 43017 requires WebKit does not remove anchor elements, div should be empty after the extraction. We currently leave anchor element in the div due to the Bug 47916.");
 
 var div = document.getElementById("div");
 var text = div.firstChild;
index 953f64a..ae64f84 100644 (file)
@@ -1,7 +1,7 @@
 This tests for a bug where RemoveFormat would reverse the order of paragraphs. Bug: the caret is not on the last line but it should be.
+| "<#selection-anchor>foo"
 | <div>
-|   "<#selection-anchor>foo"
-|   <br>
 |   <br>
+| <div>
 |   <#selection-focus>
 |   <br>
index d095f0a..cd0fcf2 100644 (file)
@@ -1,2 +1,11 @@
-This tests for a bug where Remove Format would fail to remove links that were fully selected.  You should see plain text only in the editable region below.
-| "<#selection-anchor>This shouldn't be a link or underlined.<#selection-focus>"
+This tests was added for a bug where Remove Format would fail to remove links that were fully selected. However, because the bug 43017 requires WebKit does not remove anchor elements, RemoveFormat should NOT remove anchor elements.
+
+Before RemoveFormat:
+| <a>
+|   href="http://www.google.com/"
+|   "<#selection-anchor>This shouldn't be a link or underlined.<#selection-focus>"
+
+After RemoveFormat:
+| <a>
+|   href="http://www.google.com/"
+|   "<#selection-anchor>This shouldn't be a link or underlined.<#selection-focus>"
index 527fa3e..b7347dc 100644 (file)
@@ -5,13 +5,18 @@
 <div id="div" contenteditable="true"><a href="http://www.google.com/">This shouldn't be a link or underlined.</a></div>
 <script>
 
+Markup.description('This tests was added for a bug where Remove Format would fail to remove links that were fully selected.'
+ + ' However, because the bug 43017 requires WebKit does not remove anchor elements, RemoveFormat should NOT remove anchor elements.');
+
 var div = document.getElementById("div");
 div.focus();
 document.execCommand("SelectAll");
+
+Markup.dump(div, 'Before RemoveFormat');
+
 document.execCommand("RemoveFormat");
 
-Markup.description('This tests for a bug where Remove Format would fail to remove links that were fully selected.  You should see plain text only in the editable region below.');
-Markup.dump(div);
+Markup.dump(div, 'After RemoveFormat');
 
 </script>
 </body>
index 9e9af7f..b40fc8f 100644 (file)
@@ -1,5 +1,6 @@
-This tests to make sure that RemoveFormat destroys lists if they are fully selected.  You should see foo<br>bar below.
-| <div>
-|   "<#selection-anchor>foo"
-|   <br>
-|   "bar<#selection-focus>"
+This tests to make sure that RemoveFormat destroys lists if they are fully selected. However, because the bug 43017 requires WebKit does not destroy lists, "foo" and "bar" should be in a separate list item.
+| <ul>
+|   <li>
+|     "<#selection-anchor>foo"
+|   <li>
+|     "bar<#selection-focus>"
index f8cac9b..a1a4c82 100644 (file)
@@ -11,7 +11,8 @@ div.focus();
 document.execCommand("SelectAll");
 document.execCommand("RemoveFormat");
 
-Markup.description('This tests to make sure that RemoveFormat destroys lists if they are fully selected.  You should see foo<br>bar below.');
+Markup.description('This tests to make sure that RemoveFormat destroys lists if they are fully selected.'
+ + ' However, because the bug 43017 requires WebKit does not destroy lists, "foo" and "bar" should be in a separate list item.');
 Markup.dump(div);
 
 </script>
index c1172fd..32c9d32 100644 (file)
@@ -2,6 +2,6 @@ This tests for a crash when removing format from two paragraphs that are inside
 
 
 <div style="text-align: right;">
-<div style="text-align: -webkit-auto;">foo<br>bar</div>
+<div>foo<br>bar</div>
 </div>
 
diff --git a/LayoutTests/editing/execCommand/remove-format-elements-expected.txt b/LayoutTests/editing/execCommand/remove-format-elements-expected.txt
new file mode 100644 (file)
index 0000000..204c9d9
--- /dev/null
@@ -0,0 +1,7 @@
+Removed
+acronym, b, bdo, big, cite, code, dfn, em, font, i, ins, kbd, nobr, q, s, samp, small, strike, strong, sub, sup, tt, u, var
+Preserved
+noscript, a, abbr, address, applet, area, article, aside, audio, base, basefont, bgsound, blockquote, body, br, button, canvas, caption, center, col, colgroup, command, datagrid, datalist, dcell, dcol, dd, del, details, dir, div, dl, drow, dt, embed, fieldset, figcaption, figure, footer, form, frame, frameset, h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html, iframe, image, img, input, isindex, label, layer, legend, li, link, listing, map, mark, marquee, menu, meta, meter, nav, noembed, noframes, nolayer, noscript, object, ol, optgroup, option, p, param, plaintext, pre, progress, rp, rt, ruby, script, section, select, source, span, style, summary, table, tbody, td, textarea, tfoot, th, thead, title, tr, track, ul, video, wbr, xmp
+Exceptions
+keygen (<keygen><option>2048 (High Grade)</option><option>1024 (Medium Grade)</option><option>512 (Low Grade)</option>hello=1;</keygen> webkit=2;)
+
diff --git a/LayoutTests/editing/execCommand/remove-format-elements.html b/LayoutTests/editing/execCommand/remove-format-elements.html
new file mode 100644 (file)
index 0000000..0f7b60d
--- /dev/null
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test" contenteditable></div>
+<dl>
+<dt>Removed</dt>
+<dd id="removed"></dd>
+<dt>Preserved</dt>
+<dd id="preserved"></dd>
+<dt>Exceptions</dt>
+<dd id="exceptions"></dd>
+</dl>
+<script type="text/javascript">
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var elements = [ "noscript", "a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio",
+"b", "base", "basefont", "bdo", "bgsound", "big", "blockquote", "body", "br", "button",
+"canvas", ["table", "caption"], "center", "cite", "code", ["table", "col"], ["table", "colgroup"], "command",
+"datagrid", "datalist", "dcell", "dcol", "dd", "del", "details", "dfn", "dir", "div", "dl", "drow", "dt",
+"em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset",
+"h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html",
+"i", "iframe", "image", "img", "input", "ins", "isindex", "kbd", "keygen", "label", "layer", "legend", ["ul", "li"], "link", "listing",
+"map", "mark", "marquee", "menu", "meta", "meter", "nav", "nobr", "noembed", "noframes", "nolayer", "noscript",
+"object", "ol", "optgroup", "option", "p", "param", "plaintext", "pre", "progress", "q", "rp", "rt", "ruby",
+"s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup",
+"table", ["table", "tbody"], ["table", "td"], "textarea", ["table", "tfoot"], ["table", "th"], ["table", "thead"],
+"title", ["table", "tr"], "track", "tt", "u", "ul", "var", "video", "wbr", "xmp" ];
+
+function addResult(container, name, annotation, description)
+{
+    if (container.innerHTML.length && !annotation)
+        container.innerHTML += ', ';
+    container.innerHTML += name;
+    if (annotation)
+        container.appendChild(document.createTextNode(" (" + annotation + ")"));
+    if (description)
+        container.appendChild(document.createTextNode(description));
+    if (annotation || description)
+        container.appendChild(document.createElement('br'));
+}
+
+function contains(node, descendant)
+{
+    while (descendant && descendant != node && descendant.parentNode)
+        descendant = descendant.parentNode;
+    return descendant == node;
+}
+
+var test = document.getElementById('test');
+for (var i = 0; i < elements.length; i++) {
+    var elementName;
+    var instance = null;
+    var container = null;
+    if (typeof elements[i] == 'string') {
+        elementName = elements[i];
+        instance = document.createElement(elementName)
+        container = instance;
+    } else {
+        var elementList = elements[i];
+        for (var j = 0; j < elementList.length; j++) {
+            var node = document.createElement(elementList[j]);
+            if (!container)
+                container = node;
+            if (instance)
+                instance.appendChild(node);
+            instance = node;
+        }
+        elementName = elementList[elementList.length - 1];
+    }
+    var dummy_text = document.createTextNode('hello=1;');
+    if (instance === undefined) {
+        addResult(document.getElementById('exceptions'), elementName, test.innerHTML);
+        continue;
+    }
+    test.innerHTML = "";
+    try {
+        instance.appendChild(dummy_text); // this may fail for non-container elements.
+    } catch (error) { }
+    test.appendChild(container);
+    test.appendChild(document.createTextNode(' webkit=2;'));
+    var ec = ''
+    if (window.getSelection) {
+        window.getSelection().selectAllChildren(test);
+    } else {
+        var range = document.body.createTextRange();
+        range.moveToElementText(test.firstChild);
+        try { range.select(); }
+        catch (error) { ec += ' failed to select'; }
+    }
+    document.execCommand('removeFormat', false, null);
+
+    if (ec) {
+        addResult(document.getElementById('exceptions'), elementName, test.innerHTML, ec);
+        continue;
+    }
+
+    if (!test.firstChild || test.firstChild.nodeType == 3 /*TextNode*/)
+        addResult(document.getElementById('removed'), elementName)
+    else if (contains(test, instance) && (instance.innerHTML == "" || instance.innerHTML == "hello=1;"))
+        addResult(document.getElementById('preserved'), elementName);
+    else
+        addResult(document.getElementById('exceptions'), elementName, test.innerHTML);
+}
+document.body.removeChild(test);
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/editing/execCommand/remove-format-multiple-elements-expected.txt b/LayoutTests/editing/execCommand/remove-format-multiple-elements-expected.txt
new file mode 100644 (file)
index 0000000..196b69f
--- /dev/null
@@ -0,0 +1,26 @@
+This tests removing multiple elements by RemoveFormat command.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS RemoveFormat on all of "hello" yields "hello"
+PASS RemoveFormat on all of "<i>hello</i> <u>world</u>" yields "hello world"
+PASS RemoveFormat on all of "<b><u>hello</u> world</b> <a href="http://webkit.org/"><em>WebKit</em></a>" yields "hello world <a href="http://webkit.org/">WebKit</a>"
+PASS RemoveFormat on second word of "<b><u>hello</u> world</b> <a href="http://webkit.org/"><em>WebKit</em></a>" yields "<b><u>hello</u> </b>world <a href="http://webkit.org/"><em>WebKit</em></a>"
+PASS RemoveFormat on second word of "<sub><tt>hello world WebKit</tt></sub>" yields "<sub><tt>hello </tt></sub>world<sub><tt> WebKit</tt></sub>"
+PASS RemoveFormat on second word of "<q><ins><var>hello wor</var>ld</ins> WebKit</q>" yields "<q><ins><var>hello </var></ins></q>world<q> WebKit</q>"
+PASS RemoveFormat on last word of "<b>hello <dfn>world <kbd>WebKit</kbd></dfn></b>" yields "<b>hello <dfn>world </dfn></b>WebKit"
+PASS RemoveFormat on second word of "<b>hello <dfn>world <kbd>WebKit</kbd></dfn></b>" yields "<b>hello </b>world<b><dfn> <kbd>WebKit</kbd></dfn></b>"
+PASS RemoveFormat on first two words of "<code>hello <strong>world WebKit</storng></code>" yields "hello world<code><strong> WebKit</strong></code>"
+PASS RemoveFormat on first two words of "<acronym><tt><mark><samp>hello</samp></mark> world <sub>WebKit</sub></tt></acronym>" yields "<mark>hello</mark> world<acronym><tt> <sub>WebKit</sub></tt></acronym>"
+PASS RemoveFormat on last two words of "<b><div>hello world</div></b><div>WebKit</div>" yields "<div><b>hello </b>world</div><div>WebKit</div>"
+PASS RemoveFormat on last two words of "<q><b><div>hello world</div></b>WebKit</q>" yields "<div><q><b>hello </b></q>world</div>WebKit"
+PASS RemoveFormat on second word of "<q><b><div>hello world</div></b>WebKit</q>" yields "<div><q><b>hello </b></q>world</div><q>WebKit</q>"
+PASS RemoveFormat on all of "<i style="font-weight:bold;">hello</i> <u>world</u>" yields "hello world"
+PASS RemoveFormat on second word of "<font color="red"><b style="font-size: large;"><u>hello</u> world</b> WebKit</font>" yields "<font color="red"><b style="font-size: large;"><u>hello</u> </b></font>world<font color="red"> WebKit</font>"
+PASS RemoveFormat on second word of "<font size="5"><i><u style="font-size: small;">hello</u> world</i><font size="3"> WebKit</font></font>" yields "<font size="5"><i><u style="font-size: small;">hello</u> </i></font>world<font size="5"><font size="3"> WebKit</font></font>"
+PASS RemoveFormat on second word of "<sup><div style="text-decoration: underline; font-size: large;">hello <dfn style="font-size: normal;">world</dfn></div> WebKit</sup>" yields "<div><sup><font class="Apple-style-span" size="4"><u>hello </u></font></sup>world</div><sup> WebKit</sup>"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/execCommand/remove-format-multiple-elements.html b/LayoutTests/editing/execCommand/remove-format-multiple-elements.html
new file mode 100644 (file)
index 0000000..90841bd
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../../fast/js/resources/js-test-style.css">
+<script src="../../fast/js/resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="script-tests/remove-format-multiple-elements.js"></script>
+<script src="../../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 8dc2740..b20896c 100644 (file)
@@ -9,4 +9,5 @@ EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
 EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification
 This tests that RemoveFormat not only removes style from the selected part of the DOM, but that it also applies the document default style to the selection if that's necessary in order to leave the selected text unstyled.
-| "<#selection-anchor>This<#selection-focus> text should look the same as the text above."
+| "<#selection-anchor>This<#selection-focus>"
+| " text should look the same as the text above."
index d07a075..c7473e7 100644 (file)
@@ -4,7 +4,7 @@ EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotificatio
 EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 0 of DIV > BODY > HTML > #document to 0 of DIV > BODY > HTML > #document toDOMRange:range from 0 of #text > B > DIV > BODY > HTML > #document to 3 of #text > SPAN > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
-EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 0 of #text > DIV > BODY > HTML > #document to 9 of #text > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 0 of #text > DIV > BODY > HTML > #document to 3 of #text > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
 EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
@@ -12,11 +12,32 @@ EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification
 This is a test for execCommand("RemoveFormat"). It demonstrates a bug: everything in the editable region below should be selected, as everything was selected before Remove Format was performed.
 
 markup:
-| "<#selection-anchor>foobarbaz"
+| "
+"
+| "<#selection-anchor>foo"
+| <a>
+|   href="http://www.google.com/"
+|   "bar"
+| "baz"
 | <br>
-| "foo bar     baz"
+| "
+"
+| <table>
+|   border="1"
+|   <tbody>
+|     <tr>
+|       <td>
+|         "foo"
+|       <td>
+|         "bar"
+|       <td>
+|         "baz"
+| "
+"
+| "foo"
+| "bar"
+| "baz<#selection-focus>"
 | <br>
-| "foobarbaz<#selection-focus>"
 | "
 "
 
diff --git a/LayoutTests/editing/execCommand/script-tests/remove-format-multiple-elements.js b/LayoutTests/editing/execCommand/script-tests/remove-format-multiple-elements.js
new file mode 100644 (file)
index 0000000..456443d
--- /dev/null
@@ -0,0 +1,78 @@
+description("This tests removing multiple elements by RemoveFormat command.")
+
+var testContainer = document.createElement("div");
+testContainer.contentEditable = true;
+document.body.appendChild(testContainer);
+
+function testRemoveFormat(initialContents, selector, expected)
+{
+    testContainer.innerHTML = initialContents;
+    var selected = selector(testContainer);
+    document.execCommand('RemoveFormat', false, null);
+    var action = 'RemoveFormat on ' + selected + ' of "' + initialContents + '" yields "' + testContainer.innerHTML + '"';
+    if (testContainer.innerHTML === expected)
+        testPassed(action);
+    else
+        testFailed(action + ', expected "' + expected + '"');
+}
+
+function selectAll(container) {
+    window.getSelection().selectAllChildren(container);
+    return 'all';
+}
+
+function selectSecondWord(container) {
+    window.getSelection().setPosition(container, 0);
+    window.getSelection().modify('move', 'forward', 'word');
+    window.getSelection().modify('move', 'forward', 'word');
+    window.getSelection().modify('extend', 'backward', 'word');
+    return 'second word';
+}
+
+function selectFirstTwoWords(container) {
+    window.getSelection().setPosition(container, 0);
+    window.getSelection().modify('extend', 'forward', 'word');
+    window.getSelection().modify('extend', 'forward', 'word');
+    return 'first two words';
+}
+
+function selectLastTwoWords(container) {
+    window.getSelection().setPosition(container, container.childNodes.length);
+    window.getSelection().modify('extend', 'backward', 'word');
+    window.getSelection().modify('extend', 'backward', 'word');
+    return 'last two words';
+}
+
+function selectLastWord(container) {
+    window.getSelection().setPosition(container, container.childNodes.length);
+    window.getSelection().modify('extend', 'backward', 'word');
+    return 'last word';
+}
+
+testRemoveFormat('hello', selectAll, 'hello');
+testRemoveFormat('<i>hello</i> <u>world</u>', selectAll, 'hello world');
+testRemoveFormat('<b><u>hello</u> world</b> <a href="http://webkit.org/"><em>WebKit</em></a>', selectAll, 'hello world <a href="http://webkit.org/">WebKit</a>');
+testRemoveFormat('<b><u>hello</u> world</b> <a href="http://webkit.org/"><em>WebKit</em></a>',
+    selectSecondWord, '<b><u>hello</u> </b>world <a href="http://webkit.org/"><em>WebKit</em></a>');
+testRemoveFormat('<sub><tt>hello world WebKit</tt></sub>', selectSecondWord, '<sub><tt>hello </tt></sub>world<sub><tt> WebKit</tt></sub>');
+testRemoveFormat('<q><ins><var>hello wor</var>ld</ins> WebKit</q>', selectSecondWord, '<q><ins><var>hello </var></ins></q>world<q> WebKit</q>');    
+testRemoveFormat('<b>hello <dfn>world <kbd>WebKit</kbd></dfn></b>', selectLastWord, '<b>hello <dfn>world </dfn></b>WebKit');
+testRemoveFormat('<b>hello <dfn>world <kbd>WebKit</kbd></dfn></b>', selectSecondWord, '<b>hello </b>world<b><dfn> <kbd>WebKit</kbd></dfn></b>');
+testRemoveFormat('<code>hello <strong>world WebKit</storng></code>', selectFirstTwoWords, 'hello world<code><strong> WebKit</strong></code>');
+testRemoveFormat('<acronym><tt><mark><samp>hello</samp></mark> world <sub>WebKit</sub></tt></acronym>',
+    selectFirstTwoWords, '<mark>hello</mark> world<acronym><tt> <sub>WebKit</sub></tt></acronym>');
+testRemoveFormat('<b><div>hello world</div></b><div>WebKit</div>', selectLastTwoWords, '<div><b>hello </b>world</div><div>WebKit</div>');
+testRemoveFormat('<q><b><div>hello world</div></b>WebKit</q>', selectLastTwoWords, '<div><q><b>hello </b></q>world</div>WebKit');
+testRemoveFormat('<q><b><div>hello world</div></b>WebKit</q>', selectSecondWord, '<div><q><b>hello </b></q>world</div><q>WebKit</q>');
+
+testRemoveFormat('<i style="font-weight:bold;">hello</i> <u>world</u>', selectAll, 'hello world');
+testRemoveFormat('<font color="red"><b style="font-size: large;"><u>hello</u> world</b> WebKit</font>',
+    selectSecondWord, '<font color="red"><b style="font-size: large;"><u>hello</u> </b></font>world<font color="red"> WebKit</font>');
+testRemoveFormat('<font size="5"><i><u style="font-size: small;">hello</u> world</i><font size="3"> WebKit</font></font>',
+    selectSecondWord, '<font size="5"><i><u style="font-size: small;">hello</u> </i></font>world<font size="5"><font size="3"> WebKit</font></font>');
+testRemoveFormat('<sup><div style="text-decoration: underline; font-size: large;">hello <dfn style="font-size: normal;">world</dfn></div> WebKit</sup>',
+    selectSecondWord, '<div><sup><font class="Apple-style-span" size="4"><u>hello </u></font></sup>world</div><sup> WebKit</sup>');
+
+document.body.removeChild(testContainer);
+
+var successfullyParsed = true;
index 43c924c..39955d0 100644 (file)
@@ -42,12 +42,16 @@ testSingleToggle("createLink", 'hello <b>world</b> WebKit', selectFirstTwoWords,
 testSingleToggle("createLink", '<a href="http://trac.webkit.org/">hello <b>world</b></a> WebKit', selectFirstTwoWords, '<a href="http://webkit.org/">hello <b>world</b></a> WebKit');
 testSingleToggle("createLink", '<a href="http://trac.webkit.org/" style="font-style: italic;">hello world</a> WebKit', selectFirstTwoWords, '<i><a href="http://webkit.org/">hello world</a></i> WebKit');
 testSingleToggle("createLink", 'hello <a href="http://trac.webkit.org/"><b>world</b> WebKit</a>', selectFirstTwoWords, '<a href="http://webkit.org/">hello <b>world</b></a><a href="http://trac.webkit.org/"> WebKit</a>');
-testSingleToggle("createLink", 'hello <a href="http://trac.webkit.org/" style="font-style: italic;"><b>world</b> WebKit</a>', selectFirstTwoWords, '<a href="http://webkit.org/">hello <b style="font-style: italic; ">world</b></a><a href="http://trac.webkit.org/"><i> WebKit</i></a>');
+testSingleToggle("createLink", 'hello <a href="http://trac.webkit.org/" style="font-style: italic;"><b>world</b> WebKit</a>', selectFirstTwoWords,
+    '<a href="http://webkit.org/">hello <b style="font-style: italic; ">world</b></a><a href="http://trac.webkit.org/"><i> WebKit</i></a>');
 testSingleToggle("createLink", 'hello <b>world</b> WebKit', selectLastWord, 'hello <b>world</b> <a href="http://webkit.org/">WebKit</a>');
 testSingleToggle("createLink", '<u>hello <b>world</b> WebKit</u>', selectLastWord, '<u>hello <b>world</b> <a href="http://webkit.org/">WebKit</a></u>');
-testSingleToggle("createLink", '<a href="http://trac.webkit.org/"><div>hello</div><div>world</div></a>', selectLastWord, '<div><a href="http://trac.webkit.org/">hello</a></div><div><a href="http://webkit.org/">world</a></div>');
-testSingleToggle("createLink", '<a href="http://trac.webkit.org/" style="font-weight: bold;"><div>hello</div><div>world</div></a>', selectLastWord, '<div style="font-weight: bold; "><a href="http://trac.webkit.org/">hello</a></div><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>');
-testSingleToggle("createLink", '<a href="http://trac.webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>', selectLastWord, '<div style="font-weight: normal; "><a href="http://trac.webkit.org/">hello</a></div><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>');
+testSingleToggle("createLink", '<a href="http://trac.webkit.org/"><div>hello</div><div>world</div></a>', selectLastWord,
+    '<a href="http://trac.webkit.org/"><div>hello</div></a><div><a href="http://webkit.org/">world</a></div>');
+testSingleToggle("createLink", '<a href="http://trac.webkit.org/" style="font-weight: bold;"><div>hello</div><div>world</div></a>', selectLastWord,
+    '<a href="http://trac.webkit.org/"><div style="font-weight: bold; ">hello</div></a><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>');
+testSingleToggle("createLink", '<a href="http://trac.webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>', selectLastWord,
+    '<a href="http://trac.webkit.org/"><div style="font-weight: normal; ">hello</div></a><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>');
 
 document.body.removeChild(testContainer);
 
index bcf133d..1d2340e 100644 (file)
@@ -59,9 +59,9 @@ testSingleToggle("unlink", '<a href="http://webkit.org/" style="background-color
 testSingleToggle("unlink", 'hello<a href="http://webkit.org/" style="background-color: yellow;"><div>world</div></a>WebKit',
     selectAll, 'hello<div style="background-color: yellow; ">world</div><span class="Apple-style-span" style="background-color: yellow;">WebKit</span>');
 testSingleToggle("unlink", '<a href="http://webkit.org/" style="font-weight: bold;"><div>hello</div><div>world WebKit</div></a>',
-    selectLastTwoWords, '<div style="font-weight: bold; "><a href="http://webkit.org/">hello</a></div><div style="font-weight: bold; ">world WebKit</div>');
+    selectLastTwoWords, '<a href="http://webkit.org/"><div style="font-weight: bold; ">hello</div></a><div style="font-weight: bold; ">world WebKit</div>');
 testSingleToggle("unlink", '<a href="http://webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>',
-    selectLastWord, '<div style="font-weight: normal; "><a href="http://webkit.org/">hello</a></div><div style="font-weight: bold; ">world</div>');
+    selectLastWord, '<a href="http://webkit.org/"><div style="font-weight: normal; ">hello</div></a><div style="font-weight: bold; ">world</div>');
 
 document.body.removeChild(testContainer);
 
index f88935f..43afa7e 100644 (file)
@@ -14,9 +14,9 @@ PASS select first two words of "hello <a href="http://trac.webkit.org/"><b>world
 PASS select first two words of "hello <a href="http://trac.webkit.org/" style="font-style: italic;"><b>world</b> WebKit</a>" and createLink (http://webkit.org/) yields "<a href="http://webkit.org/">hello <b style="font-style: italic; ">world</b></a><a href="http://trac.webkit.org/"><i> WebKit</i></a>"
 PASS select last word of "hello <b>world</b> WebKit" and createLink (http://webkit.org/) yields "hello <b>world</b> <a href="http://webkit.org/">WebKit</a>"
 PASS select last word of "<u>hello <b>world</b> WebKit</u>" and createLink (http://webkit.org/) yields "<u>hello <b>world</b> <a href="http://webkit.org/">WebKit</a></u>"
-PASS select last word of "<a href="http://trac.webkit.org/"><div>hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<div><a href="http://trac.webkit.org/">hello</a></div><div><a href="http://webkit.org/">world</a></div>"
-PASS select last word of "<a href="http://trac.webkit.org/" style="font-weight: bold;"><div>hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<div style="font-weight: bold; "><a href="http://trac.webkit.org/">hello</a></div><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>"
-PASS select last word of "<a href="http://trac.webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<div style="font-weight: normal; "><a href="http://trac.webkit.org/">hello</a></div><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>"
+PASS select last word of "<a href="http://trac.webkit.org/"><div>hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<a href="http://trac.webkit.org/"><div>hello</div></a><div><a href="http://webkit.org/">world</a></div>"
+PASS select last word of "<a href="http://trac.webkit.org/" style="font-weight: bold;"><div>hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<a href="http://trac.webkit.org/"><div style="font-weight: bold; ">hello</div></a><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>"
+PASS select last word of "<a href="http://trac.webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>" and createLink (http://webkit.org/) yields "<a href="http://trac.webkit.org/"><div style="font-weight: normal; ">hello</div></a><div style="font-weight: bold; "><a href="http://webkit.org/">world</a></div>"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 688ee70..64912f3 100644 (file)
@@ -14,8 +14,8 @@ PASS unlink on last two words of "hello <a href="http://webkit.org/" style="font
 PASS unlink on first two words of "hello <a href="http://webkit.org/" style="font-style: italic;"><b>world</b> WebKit</a>" yields "hello <b style="font-style: italic; ">world</b><a href="http://webkit.org/"><i> WebKit</i></a>"
 PASS unlink on all of "<a href="http://webkit.org/" style="background-color: yellow;"><div>hello</div><div>world</div></a>" yields "<div style="background-color: yellow; ">hello</div><div style="background-color: yellow; ">world</div>"
 PASS unlink on all of "hello<a href="http://webkit.org/" style="background-color: yellow;"><div>world</div></a>WebKit" yields "hello<div style="background-color: yellow; ">world</div><span class="Apple-style-span" style="background-color: yellow;">WebKit</span>"
-PASS unlink on last two words of "<a href="http://webkit.org/" style="font-weight: bold;"><div>hello</div><div>world WebKit</div></a>" yields "<div style="font-weight: bold; "><a href="http://webkit.org/">hello</a></div><div style="font-weight: bold; ">world WebKit</div>"
-PASS unlink on last word of "<a href="http://webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>" yields "<div style="font-weight: normal; "><a href="http://webkit.org/">hello</a></div><div style="font-weight: bold; ">world</div>"
+PASS unlink on last two words of "<a href="http://webkit.org/" style="font-weight: bold;"><div>hello</div><div>world WebKit</div></a>" yields "<a href="http://webkit.org/"><div style="font-weight: bold; ">hello</div></a><div style="font-weight: bold; ">world WebKit</div>"
+PASS unlink on last word of "<a href="http://webkit.org/" style="font-weight: bold;"><div style="font-weight: normal;">hello</div><div>world</div></a>" yields "<a href="http://webkit.org/"><div style="font-weight: normal; ">hello</div></a><div style="font-weight: bold; ">world</div>"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 5e04322..26d4cb7 100644 (file)
@@ -64,5 +64,5 @@ The innerHTML of editable regions after the test:
 
 This paragraph should should end up unlinked.
 <a href="http://www.apple.com/">The</a> second<a href="http://www.apple.com/"> word in this paragraph should end up being unlinked, everything else should be a link.</a>
-This paragraph starts with <i><a href="http://www.google.com">a</a></i><span id="test3start"> link</span> in the middle. Only the 'a' in the previous sentence should be linked after the test.
+This paragraph starts with <a href="http://www.google.com"><i>a</i></a><span id="test3start"> link</span> in the middle. Only the 'a' in the previous sentence should be linked after the test.
 <p>This <i>editable region</i> contains lists, tables, styled text, and images. Everything in this region that is not selected should be a link, nothing that is selected should be a link.</p> <ul> <li>Item 1</li> <li>Item 2</li> </ul> <table border="1"><tbody><tr><td>1</td><td>2</td><td><span id="test4end"><a href="http://www.google.com/">3</a></span></td></tr></tbody></table> <a href="http://www.google.com/"><br> This <b>line</b> contains <img src="../resources/abe.png"> an image. </a>
index 2c85269..3bdacda 100644 (file)
@@ -1,9 +1,5 @@
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
 EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
-EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 0 of #text > DIV > BODY > HTML > #document to 10 of #text > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
-EDITING DELEGATE: shouldBeginEditingInDOMRange:range from 0 of BODY > HTML > #document to 9 of BODY > HTML > #document
-EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification
-EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
 EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
 This is a test for execCommand("RemoveFormat"). You should see Some%20text before and after RemoveFormat is executed. This test case is due to the WebKit bug 14062, regression (r21212).
 
index faf1119..4506f84 100644 (file)
@@ -1,3 +1,43 @@
+2010-10-21  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Reviewed by Tony Chang.
+
+        removeFormat needs to be reimplemented
+        https://bugs.webkit.org/show_bug.cgi?id=43017
+
+        Reimplemented execCommand('RemoveFormat', false, null). New implementation removes
+        the same elements removed by Internet Explorer. Because WebKit supports StyleWithCSS
+        we also reset any editing styles to match that of the root editable element
+        while Internet Explorer does not remove any CSS styles.
+
+        New implementation uses ApplyStyleCommand to remove appropriate elements and reset the style.
+        Added new constructor and member variable to ApplyStyleCommand to support mass-removal of elements
+        since it's inefficient to call ApplyStyleCommand on each element we're removing.
+
+        To avoid an infinite loop in pushDownInlineStyleAroundNode when mass-removing, WebKit no longer
+        push down element one level at a time. Instead, we keep a stack of styled elements to be applied,
+        and apply wrap siblings of targetNode's ancestors by all of them at once.
+
+        Tests: editing/execCommand/remove-format-elements.html
+               editing/execCommand/remove-format-multiple-elements.html
+
+        * editing/ApplyStyleCommand.cpp:
+        (WebCore::ApplyStyleCommand::ApplyStyleCommand): Added; this version takes style and a function pointer
+        to a boolean function that determines which element needs to removed, and set m_removeOnly to true.
+        (WebCore::ApplyStyleCommand::doApply): Added support for m_isInlineElementToRemoveFunction.
+        (WebCore::ApplyStyleCommand::applyBlockStyle): Ditto.
+        (WebCore::ApplyStyleCommand::applyInlineStyleToNodeRange): Exits early if m_removeOnly is true.
+        (WebCore::ApplyStyleCommand::isStyledInlineElementToRemove): Added.
+        (WebCore::ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle): Calls isStyledInlineElementToRemove.
+        (WebCore::ApplyStyleCommand::removeInlineStyleFromElement): Ditto.
+        (WebCore::ApplyStyleCommand::removeInlineStyle): Ditto.
+        (WebCore::ApplyStyleCommand::pushDownInlineStyleAroundNode): See above.
+        * editing/ApplyStyleCommand.h:
+        (WebCore::ApplyStyleCommand::create): Added.
+        * editing/RemoveFormatCommand.cpp:
+        (WebCore::isElementForRemoveFormatCommand): Added.
+        (WebCore::RemoveFormatCommand::doApply): Rewritten.
+
 2010-10-21  Tony Gentilcore  <tonyg@chromium.org>
 
         Reviewed by Adam Barth.
index 3f60a8b..273edb4 100644 (file)
@@ -538,6 +538,7 @@ ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* st
     , m_useEndingSelection(true)
     , m_styledInlineElement(0)
     , m_removeOnly(false)
+    , m_isInlineElementToRemoveFunction(0)
 {
 }
 
@@ -551,6 +552,7 @@ ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* st
     , m_useEndingSelection(false)
     , m_styledInlineElement(0)
     , m_removeOnly(false)
+    , m_isInlineElementToRemoveFunction(0)
 {
 }
 
@@ -564,6 +566,21 @@ ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnl
     , m_useEndingSelection(true)
     , m_styledInlineElement(element)
     , m_removeOnly(removeOnly)
+    , m_isInlineElementToRemoveFunction(0)
+{
+}
+
+ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
+    : CompositeEditCommand(document)
+    , m_style(style->makeMutable())
+    , m_editingAction(editingAction)
+    , m_propertyLevel(PropertyDefault)
+    , m_start(endingSelection().start().downstream())
+    , m_end(endingSelection().end().upstream())
+    , m_useEndingSelection(true)
+    , m_styledInlineElement(0)
+    , m_removeOnly(true)
+    , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
 {
 }
 
@@ -605,7 +622,7 @@ void ApplyStyleCommand::doApply()
                 applyBlockStyle(blockStyle.get());
             // apply any remaining styles to the inline elements
             // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
-            if (blockStyle->length() < m_style->length() || m_styledInlineElement) {
+            if (blockStyle->length() < m_style->length() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
                 RefPtr<CSSMutableStyleDeclaration> inlineStyle = m_style->copy();
                 applyRelativeFontStyleChange(inlineStyle.get());
                 blockStyle->diff(inlineStyle.get());
@@ -664,9 +681,11 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style)
         StyleChange styleChange(style, paragraphStart.deepEquivalent());
         if (styleChange.cssStyle().length() || m_removeOnly) {
             RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node());
-            RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
-            if (newBlock)
-                block = newBlock;
+            if (!m_removeOnly) {
+                RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
+                if (newBlock)
+                    block = newBlock;
+            }
             ASSERT(block->isHTMLElement());
             if (block->isHTMLElement()) {
                 removeCSSStyle(style, static_cast<HTMLElement*>(block.get()));
@@ -1126,6 +1145,9 @@ static bool containsNonEditableRegion(Node* node)
 
 void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* style, Node* node, Node* pastEndNode)
 {
+    if (m_removeOnly)
+        return;
+
     for (Node* next; node && node != pastEndNode; node = next) {
         next = node->traverseNextNode();
         
@@ -1171,10 +1193,16 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration*
 
         if (!removeStyleFromRunBeforeApplyingStyle(style, node, runEnd))
             continue;
-        addInlineStyleIfNeeded(style, node, runEnd, m_removeOnly ? DoNotAddStyledElement : AddStyledElement);
+        addInlineStyleIfNeeded(style, node, runEnd, AddStyledElement);
     }
 }
 
+bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
+{
+    return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
+        || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
+}
+
 bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd)
 {
     ASSERT(runStart && runEnd && runStart->parentNode() == runEnd->parentNode());
@@ -1183,6 +1211,7 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec
     for (Node* node = runStart; node && node != pastEndNode; node = node->traverseNextNode()) {
         if (node->childNodeCount())
             continue;
+        // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
         if (getPropertiesNotIn(style, computedStyle(node).get())->length()
             || (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))) {
             needToApplyStyle = true;
@@ -1222,7 +1251,7 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration*
     if (!element->parentNode() || !element->parentNode()->isContentEditable())
         return false;
 
-    if (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) {
+    if (isStyledInlineElementToRemove(element)) {
         if (mode == RemoveNone)
             return true;
         ASSERT(extractedStyle);
@@ -1504,6 +1533,9 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration
 
     // The outer loop is traversing the tree vertically from highestAncestor to targetNode
     Node* current = highestAncestor;
+    // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
+    // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
+    Vector<RefPtr<Element> > elementsToPushDown;
     while (current != targetNode) {
         ASSERT(current);
         ASSERT(current->isHTMLElement());
@@ -1511,8 +1543,10 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration
         Node* child = current->firstChild();
         Node* lastChild = current->lastChild();
         RefPtr<StyledElement> styledElement;
-        if (current->isStyledElement() && m_styledInlineElement && current->hasTagName(m_styledInlineElement->tagQName()))
+        if (current->isStyledElement() && isStyledInlineElementToRemove(static_cast<Element*>(current))) {
             styledElement = static_cast<StyledElement*>(current);
+            elementsToPushDown.append(styledElement);
+        }
         RefPtr<CSSMutableStyleDeclaration> styleToPushDown = CSSMutableStyleDeclaration::create();
         removeInlineStyleFromElement(style, static_cast<HTMLElement*>(current), RemoveIfNeeded, styleToPushDown.get());
 
@@ -1521,17 +1555,14 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(CSSMutableStyleDeclaration
         while (child) {
             Node* nextChild = child->nextSibling();
 
-            if (child != targetNode && styledElement) {
-                // If child has children, wrap children of child by a clone of the styled element to avoid infinite loop.
-                // Otherwise, wrap the child by the styled element, and we won't fall into an infinite loop.
-                RefPtr<Element> wrapper = styledElement->cloneElementWithoutChildren();
-                ExceptionCode ec = 0;
-                wrapper->removeAttribute(styleAttr, ec);
-                ASSERT(!ec);
-                if (child->firstChild())
-                    surroundNodeRangeWithElement(child->firstChild(), child->lastChild(), wrapper);
-                else
+            if (!child->contains(targetNode) && elementsToPushDown.size()) {
+                for (size_t i = 0; i < elementsToPushDown.size(); i++) {
+                    RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
+                    ExceptionCode ec = 0;
+                    wrapper->removeAttribute(styleAttr, ec);
+                    ASSERT(!ec);
                     surroundNodeRangeWithElement(child, child, wrapper);
+                }
             }
 
             // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode
@@ -1594,7 +1625,7 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration>
             RefPtr<Node> next = elem->traverseNextNode();
             RefPtr<CSSMutableStyleDeclaration> styleToPushDown;
             PassRefPtr<Node> childNode = 0;
-            if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName())) {
+            if (isStyledInlineElementToRemove(elem.get())) {
                 styleToPushDown = CSSMutableStyleDeclaration::create();
                 childNode = elem->firstChild();
             }
index eb6a2bc..16c5b68 100644 (file)
@@ -44,6 +44,7 @@ public:
     enum EPropertyLevel { PropertyDefault, ForceBlockProperties };
     enum InlineStyleRemovalMode { RemoveIfNeeded, RemoveAlways, RemoveNone };
     enum EAddStyledElement { AddStyledElement, DoNotAddStyledElement };
+    typedef bool (*IsInlineElementToRemoveFunction)(const Element*);
 
     static PassRefPtr<ApplyStyleCommand> create(Document* document, CSSStyleDeclaration* style, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault)
     {
@@ -57,6 +58,10 @@ public:
     {
         return adoptRef(new ApplyStyleCommand(element, removeOnly, action));
     }
+    static PassRefPtr<ApplyStyleCommand> create(Document* document, CSSStyleDeclaration* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction action = EditActionChangeAttributes)
+    {
+        return adoptRef(new ApplyStyleCommand(document, style, isInlineElementToRemoveFunction, action));
+    }
 
     static RefPtr<CSSMutableStyleDeclaration> removeNonEditingProperties(CSSStyleDeclaration* style);
     static PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle = IgnoreTypingStyle);
@@ -65,6 +70,7 @@ private:
     ApplyStyleCommand(Document*, CSSStyleDeclaration*, EditAction, EPropertyLevel);
     ApplyStyleCommand(Document*, CSSStyleDeclaration*, const Position& start, const Position& end, EditAction, EPropertyLevel);
     ApplyStyleCommand(PassRefPtr<Element>, bool removeOnly, EditAction);
+    ApplyStyleCommand(Document*, CSSStyleDeclaration*, bool (*isInlineElementToRemove)(const Element*), EditAction);
 
     virtual void doApply();
     virtual EditAction editingAction() const;
@@ -72,6 +78,7 @@ private:
     CSSMutableStyleDeclaration* style() const { return m_style.get(); }
 
     // style-removal helpers
+    bool isStyledInlineElementToRemove(Element*) const;
     bool removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd);
     bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded, CSSMutableStyleDeclaration* extractedStyle = 0);
     inline bool shouldRemoveInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element) {return removeInlineStyleFromElement(style, element, RemoveNone);}
@@ -122,6 +129,7 @@ private:
     bool m_useEndingSelection;
     RefPtr<Element> m_styledInlineElement;
     bool m_removeOnly;
+    IsInlineElementToRemoveFunction m_isInlineElementToRemoveFunction;
 };
 
 bool isStyleSpan(const Node*);
index 65f6008..f8807e2 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2007 Apple Computer, Inc.  All rights reserved.
+ * Copyright (C) 2010 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "config.h"
 #include "RemoveFormatCommand.h"
 
+#include "ApplyStyleCommand.h"
 #include "CSSComputedStyleDeclaration.h"
 #include "CSSMutableStyleDeclaration.h"
+#include "CSSValueKeywords.h"
 #include "Editor.h"
 #include "Frame.h"
+#include "HTMLElement.h"
 #include "HTMLNames.h"
 #include "VisibleSelection.h"
 #include "SelectionController.h"
 #include "TextIterator.h"
 #include "TypingCommand.h"
-#include "ApplyStyleCommand.h"
+#include "htmlediting.h"
 
 namespace WebCore {
 
@@ -46,6 +50,38 @@ RemoveFormatCommand::RemoveFormatCommand(Document* document)
 {
 }
 
+static bool isElementForRemoveFormatCommand(const Element* element)
+{
+    DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, elements, ());
+    if (elements.isEmpty()) {
+        elements.add(acronymTag);
+        elements.add(bTag);
+        elements.add(bdoTag);
+        elements.add(bigTag);
+        elements.add(citeTag);
+        elements.add(codeTag);
+        elements.add(dfnTag);
+        elements.add(emTag);
+        elements.add(fontTag);
+        elements.add(iTag);
+        elements.add(insTag);
+        elements.add(kbdTag);
+        elements.add(nobrTag);
+        elements.add(qTag);
+        elements.add(sTag);
+        elements.add(sampTag);
+        elements.add(smallTag);
+        elements.add(strikeTag);
+        elements.add(strongTag);
+        elements.add(subTag);
+        elements.add(supTag);
+        elements.add(ttTag);
+        elements.add(uTag);
+        elements.add(varTag);
+    }
+    return elements.contains(element->tagQName());
+}
+
 void RemoveFormatCommand::doApply()
 {
     Frame* frame = document()->frame();
@@ -53,34 +89,12 @@ void RemoveFormatCommand::doApply()
     if (!frame->selection()->selection().isNonOrphanedCaretOrRange())
         return;
 
-    // Make a plain text string from the selection to remove formatting like tables and lists.
-    String string = plainText(frame->selection()->selection().toNormalizedRange().get());
-
     // Get the default style for this editable root, it's the style that we'll give the
     // content that we're operating on.
     Node* root = frame->selection()->rootEditableElement();
     RefPtr<CSSMutableStyleDeclaration> defaultStyle = ApplyStyleCommand::editingStyleAtPosition(Position(root, 0));
 
-    // Delete the selected content.
-    // FIXME: We should be able to leave this to insertText, but its delete operation
-    // doesn't preserve the style we're about to set.
-    deleteSelection();
-    
-    // Delete doesn't remove fully selected lists.
-    while (breakOutOfEmptyListItem())
-        ;
-    
-    // If the selection was all formatting (like an empty list) the format-less text will 
-    // be empty. Early return since we don't need to do any of the work that follows and
-    // to avoid the ASSERT that fires if input(...) is called with an empty String.
-    if (string.isEmpty())
-        return;
-    
-    // Insert the content with the default style.
-    // See <rdar://problem/5794382> RemoveFormat doesn't always reset text alignment
-    frame->selection()->setTypingStyle(defaultStyle.release());
-    
-    inputText(string, true);
+    applyCommandToComposite(ApplyStyleCommand::create(document(), defaultStyle.get(), isElementForRemoveFormatCommand, editingAction()));
 }
 
 }