Bug 384147 - "tabbing out list item doesn't merge with next list item at same depth" [p=mfenniak-moz@mathieu.fenniak.net (Mathieu Fenniak) r=glazou sr+a1.9=roc]
--- a/editor/composer/test/Makefile.in
+++ b/editor/composer/test/Makefile.in
@@ -41,12 +41,13 @@ srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = editor/composer/test
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_bug348497.html \
+ test_bug384147.html \
$(NULL)
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/test_bug384147.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384147
+-->
+<head>
+ <title>Test for Bug 384147</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384147">Mozilla Bug 384147</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+<div contentEditable id="editor"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+/** Test for Bug 384147 **/
+
+SimpleTest.waitForExplicitFinish();
+
+// netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+var editor = document.getElementById("editor");
+
+editor.innerHTML = "<ol><li>Item 1</li><li>Item 2</li><ol><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>";
+editor.focus();
+
+// If executed directly, a race condition exists that will cause execCommand
+// to fail occasionally (but often). Defer test execution to page load.
+addLoadEvent(function() {
+
+ var sel = window.getSelection();
+
+ // Test the affect that the tab key has on list items. Each test is
+ // documented with the initial state of the list on the left, and the
+ // expected state of the list on the right. {\t} indicates the list item
+ // that will be indented. {\st} indicates that a shift-tab will be simulated
+ // on that list item, outdenting it.
+ //
+ // Note: any test failing will likely result in all following tests failing
+ // as well, since each test depends on the document being in a given state.
+ // Unfortunately, due to the problems getting document focus and key events
+ // to fire consistently, it's difficult to reset state between tests.
+ // If there are test failures here, only debug the first test failure.
+
+ // *** test 1 ***
+ // 1. Item 1 1. Item 1
+ // 2. {\t}Item 2 1. Item 2
+ // 1. Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 1");
+
+ // *** test 2 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. {\t}Item 3 1. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><ol><li>Item 3</li></ol></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 2");
+
+ // *** test 3 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 1. {\st}Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ document.execCommand("outdent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 3");
+
+ // *** test 4 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. {\st}Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ document.execCommand("outdent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 4");
+
+ // *** test 5 ***
+ // 1. Item 1 1. Item 1
+ // 1. {\st}Item 2 2. Item 2
+ // 2. Item 3 3. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
+ document.execCommand("outdent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 5");
+
+ // *** test 6 ***
+ // 1. Item 1 1. Item 1
+ // 2. {\t}Item 2 1. Item 2
+ // 3. Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 6");
+
+ // *** test 7 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. {\t}Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 7");
+
+ // That covers the basics of merging lists on indent and outdent.
+ // We also want to check that ul / ol lists won't be merged together,
+ // since they're different types of lists.
+ // *** test 8 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. Item 3 2. Item 3
+ // * {\t}Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li></ul><li>Item 5</li></ul>",
+ "html output doesn't match expected value in test 8");
+
+ // Better test merging with <ul> rather than <ol> too.
+ // *** test 9 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. Item 3 2. Item 3
+ // * Item 4 * Item 4
+ // * {\t}Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[4]);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
+ "html output doesn't match expected value in test 9");
+
+ // Same test as test 8, but with outdent rather than indent.
+ // *** test 10 ***
+ // 1. Item 1 1. Item 1
+ // 1. Item 2 1. Item 2
+ // 2. Item 3 2. Item 3
+ // * {\st}Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
+ document.execCommand("outdent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
+ "html output doesn't match expected value in test 10");
+
+ // Test indenting multiple items at once. Hold down "shift" and select
+ // upwards to get all the <ol> items and the first <ul> item.
+ // *** test 11 ***
+ // 1. Item 1 1. Item 1
+ // 1. {\t}Item 2 1. Item 2
+ // 2. {\t}Item 3 2. Item 3
+ // * {\t}Item 4 * Item 4
+ // * Item 5 * Item 5
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(editor.getElementsByTagName("li")[1], 0);
+ range.setEnd(editor.getElementsByTagName("li")[3], editor.getElementsByTagName("li")[3].childNodes.length);
+ sel.addRange(range);
+ document.execCommand("indent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><ol><li>Item 2</li><li>Item 3</li></ol></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
+ "html output doesn't match expected value in test 11");
+
+ // Test outdenting multiple items at once. Selection is already ready...
+ // *** test 12 ***
+ // 1. Item 1 1. Item 1
+ // 1. {\st}Item 2 1. Item 2
+ // 2. {\st}Item 3 2. Item 3
+ // * {\st}Item 4 * Item 4
+ // * Item 5 * Item 5
+ document.execCommand("outdent", false, null);
+ ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
+ "html output doesn't match expected value in test 12");
+
+ SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/editor/libeditor/html/nsHTMLEditRules.cpp
+++ b/editor/libeditor/html/nsHTMLEditRules.cpp
@@ -3739,24 +3739,59 @@ nsHTMLEditRules::WillHTMLIndent(nsISelec
PRInt32 offset;
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
if (NS_FAILED(res)) return res;
// some logic for putting list items into nested lists...
if (nsHTMLEditUtils::IsList(curParent))
{
+ sibling = nsnull;
+
+ // Check for whether we should join a list that follows curNode.
+ // We do this if the next element is a list, and the list is of the
+ // same type (li/ol) as curNode was a part it.
+ mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
+ if (sibling && nsHTMLEditUtils::IsList(sibling))
+ {
+ nsAutoString curListTag, siblingListTag;
+ nsEditor::GetTagString(curParent, curListTag);
+ nsEditor::GetTagString(sibling, siblingListTag);
+ if (curListTag == siblingListTag)
+ {
+ res = mHTMLEditor->MoveNode(curNode, sibling, 0);
+ if (NS_FAILED(res)) return res;
+ continue;
+ }
+ }
+
+ // Check for whether we should join a list that preceeds curNode.
+ // We do this if the previous element is a list, and the list is of
+ // the same type (li/ol) as curNode was a part of.
+ mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
+ if (sibling && nsHTMLEditUtils::IsList(sibling))
+ {
+ nsAutoString curListTag, siblingListTag;
+ nsEditor::GetTagString(curParent, curListTag);
+ nsEditor::GetTagString(sibling, siblingListTag);
+ if (curListTag == siblingListTag)
+ {
+ res = mHTMLEditor->MoveNode(curNode, sibling, -1);
+ if (NS_FAILED(res)) return res;
+ continue;
+ }
+ }
+
+ sibling = nsnull;
+
// check to see if curList is still appropriate. Which it is if
// curNode is still right after it in the same list.
if (curList)
- {
- sibling = nsnull;
mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
- }
-
+
if (!curList || (sibling && sibling != curList) )
{
nsAutoString listTag;
nsEditor::GetTagString(curParent,listTag);
ToLowerCase(listTag);
// create a new nested list of correct type
res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
if (NS_FAILED(res)) return res;