--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+ RenderInline {SPAN} at (0,0) size 140x28
+ RenderText {TEXT} at (14,14) size 69x28
+ text run at (14,14) width 69: "foo bar"
+ RenderInline {SPAN} at (0,0) size 31x28
+ RenderText {TEXT} at (83,14) size 31x28
+ text run at (83,14) width 31: "bar"
+ RenderText {TEXT} at (114,14) size 40x28
+ text run at (114,14) width 40: " baz"
+ RenderText {TEXT} at (0,0) size 0x0
+selection is CARET:
+start: position 3 of child 1 {TEXT} of child 2 {SPAN} of child 2 {SPAN} of root {DIV}
+upstream: position 3 of child 1 {TEXT} of child 2 {SPAN} of child 2 {SPAN} of root {DIV}
+downstream: position 0 of child 3 {TEXT} of child 2 {SPAN} of root {DIV}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 4; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 3; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ moveSelectionForwardByCharacterCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body>
+<div contenteditable id="root" class="editing">
+<span id="test">foo bar baz</span>
+</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 63x28
+ text run at (14,14) width 63: "There "
+ RenderText {TEXT} at (77,14) size 285x28
+ text run at (77,14) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 124x28
+ text run at (14,14) width 124: "Which taken"
+ RenderText {TEXT} at (138,14) size 285x28
+ text run at (138,14) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 124x28
+ text run at (14,14) width 124: "Which taken"
+ RenderText {TEXT} at (138,14) size 310x28
+ text run at (138,14) width 310: " at the flood leads on to fortune."
+ RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 351x28
+ text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 11 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream: position 11 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 6; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 44; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x364 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 756x28
+ RenderText {TEXT} at (0,0) size 63x28
+ text run at (0,0) width 63: "There "
+ RenderText {TEXT} at (63,0) size 285x28
+ text run at (63,0) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 434x28
+ text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (14,98) size 756x252 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 728x0
+ RenderBlock {DIV} at (14,14) size 728x112 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 700x28
+ RenderText {TEXT} at (0,0) size 80x28
+ text run at (0,0) width 80: "Omitted"
+ RenderText {TEXT} at (80,0) size 285x28
+ text run at (80,0) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 434x28
+ text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (14,126) size 728x112 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 700x28
+ RenderText {TEXT} at (0,0) size 80x28
+ text run at (0,0) width 80: "Omitted"
+ RenderText {TEXT} at (80,0) size 271x28
+ text run at (80,0) width 271: ", all the voyage of their life,"
+ RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 7 of child 1 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
+upstream: position 7 of child 1 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 6; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 86; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,
+<div class="editing">Which taken at the flood leads on to fortune.
+<div class="editing">Omitted, all the voyage of their life,
+<div class="editing">Is bound in shallows and in miseries.
+</div>
+</div>
+</div>
+</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 63x28
+ text run at (14,14) width 63: "There "
+ RenderText {TEXT} at (77,14) size 285x28
+ text run at (77,14) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 434x28
+ text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 351x28
+ text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 0 of child 1 {TEXT} of child 2 {DIV} of root {BODY}
+upstream: position 0 of child 2 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 2 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 6; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 33; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 63x28
+ text run at (14,14) width 63: "There "
+ RenderText {TEXT} at (77,14) size 285x28
+ text run at (77,14) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 285x28
+ text run at (14,14) width 285: "is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 434x28
+ text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 351x28
+ text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream: position 0 of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 6; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 33; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 348x28
+ text run at (14,14) width 348: "There is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 348x28
+ text run at (14,14) width 348: "There is a tide in the affairs of men,"
+ RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 434x28
+ text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 351x28
+ text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream: position 0 of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 39; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x280 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 756x28
+ RenderText {TEXT} at (0,0) size 348x28
+ text run at (0,0) width 348: "There is a tide in the affairs of men,"
+ RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 348x28
+ text run at (14,14) width 348: "There is a tide in the affairs of men,"
+ RenderBlock {DIV} at (14,98) size 756x168 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 728x28
+ RenderText {TEXT} at (0,0) size 434x28
+ text run at (0,0) width 434: "Which taken at the flood leads on to fortune."
+ RenderBlock {DIV} at (14,42) size 728x112 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 700x28
+ RenderText {TEXT} at (0,0) size 351x28
+ text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start: position 0 of child 1 {TEXT} of child 3 {DIV} of child 2 {DIV} of root {BODY}
+upstream: position 0 of child 3 {DIV} of child 2 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of child 2 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 39; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+
+<div id="test" class="editing">There is a tide in the affairs of men,<div class="editing">Which taken at the flood leads on to fortune.<div class="editing">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div></div></div></div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x140 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 756x28
+ RenderText {TEXT} at (0,0) size 351x28
+ text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 114x28
+ text run at (14,14) width 114: "Is bound in "
+ RenderText {TEXT} at (128,14) size 243x28
+ text run at (128,14) width 243: "shallows and in miseries."
+ RenderBlock (anonymous) at (14,98) size 756x28
+ RenderText {TEXT} at (0,0) size 197x28
+ text run at (0,0) width 197: "Upon such a full sea"
+ RenderText {TEXT} at (197,0) size 243x28
+ text run at (197,0) width 243: "shallows and in miseries."
+ RenderBlock {DIV} at (0,140) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 197x28
+ text run at (14,14) width 197: "Upon such a full sea"
+ RenderText {TEXT} at (211,14) size 185x28
+ text run at (211,14) width 185: " are we now afloat,"
+selection is CARET:
+start: position 20 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream: position 20 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 51; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 46; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+
+<div class="editing" id="test">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div>Upon such a full sea are we now afloat,</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
--- /dev/null
+layer at (0,0) size 800x600
+ RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+ RenderBlock {HTML} at (0,0) size 800x600
+ RenderBody {BODY} at (8,8) size 784x584
+ RenderBlock {DIV} at (0,0) size 784x140 [border: (2px solid #FF0000)]
+ RenderBlock (anonymous) at (14,14) size 756x28
+ RenderText {TEXT} at (0,0) size 351x28
+ text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+ RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 357x28
+ text run at (14,14) width 357: "Is bound in shallows and in miseries."
+ RenderBlock (anonymous) at (14,98) size 756x28
+ RenderText {TEXT} at (0,0) size 17x28
+ text run at (0,0) width 17: "U"
+ RenderBlock {DIV} at (0,140) size 784x56 [border: (2px solid #FF0000)]
+ RenderText {TEXT} at (14,14) size 17x28
+ text run at (14,14) width 17: "U"
+ RenderText {TEXT} at (31,14) size 365x28
+ text run at (31,14) width 365: "pon such a full sea are we now afloat,"
+selection is CARET:
+start: position 1 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream: position 1 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
--- /dev/null
+<html>
+<head>
+
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 12px;
+ font-size: 24px;
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+ for (i = 0; i < 76; i++)
+ moveSelectionForwardByCharacterCommand();
+ for (i = 0; i < 2; i++)
+ extendSelectionForwardByCharacterCommand();
+ copyCommand();
+ pasteCommand();
+ pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title>
+</head>
+<body contenteditable id="root">
+
+<div class="editing" id="test">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div>Upon such a full sea are we now afloat,</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
+2004-11-29 Ken Kocienda <kocienda@apple.com>
+
+ Reviewed by Chris
+
+ Rewrite of paste code (specifically the ReplaceSelectionCommand class). Many more cases
+ are handled correctly now, including selections that span multiple blocks, and cases
+ where content on the pasteboard ends in newlines (or what appear to be newlines to a
+ user, really block ends or BRs). I also made one small, but important change in the
+ copy code to annotate the markup written to the pasteboard to support these selections
+ ending in newlines.
+
+ New header that defines a couple of constants used in copying and pasting.
+
+ * ForwardingHeaders/editing/html_interchange.h: Added.
+ * khtml/editing/html_interchange.h: Added.
+
+ Rewrite of the ReplaceSelectionCommand. There are several new helper functions, as well
+ as a new helper class, ReplacementFragment, which encapsulates information and functions
+ pertaining to a document fragment that is being inserted into a document.
+
+ * khtml/editing/htmlediting.cpp:
+ (khtml::ReplacementFragment::ReplacementFragment):
+ (khtml::ReplacementFragment::~ReplacementFragment):
+ (khtml::ReplacementFragment::firstChild): Simple accessor.
+ (khtml::ReplacementFragment::lastChild): Ditto.
+ (khtml::ReplacementFragment::mergeStartNode): Looks at the nodes in a fragment and determines
+ the starting node to use for merging into the block containing the start of the selection.
+ (khtml::ReplacementFragment::mergeEndNode): Same as above, but for the end of the selection.
+ (khtml::ReplacementFragment::pruneEmptyNodes): Simple helper.
+ (khtml::ReplacementFragment::isInterchangeNewlineComment): Determines if a node is the
+ special annotation comment added in by the copy code.
+ (khtml::ReplacementFragment::removeNode): Simple helper.
+ (khtml::isComment): Simple helper.
+ (khtml::isProbablyBlock): Determines if a node is of a type that is usually rendered as a block.
+ I would like to do better than this some day, but this check will hold us until I can do better.
+ (khtml::ReplaceSelectionCommand::ReplaceSelectionCommand):
+ (khtml::ReplaceSelectionCommand::~ReplaceSelectionCommand):
+ (khtml::ReplaceSelectionCommand::doApply):
+ (khtml::ReplaceSelectionCommand::completeHTMLReplacement): Figures out the right ending selection.
+ * khtml/editing/htmlediting.h: Declarations for the new ReplacementFragment class.
+ (khtml::ReplacementFragment::root):
+ (khtml::ReplacementFragment::type):
+ (khtml::ReplacementFragment::isEmpty):
+ (khtml::ReplacementFragment::isSingleTextNode):
+ (khtml::ReplacementFragment::isTreeFragment):
+ (khtml::ReplacementFragment::hasMoreThanOneBlock):
+ (khtml::ReplacementFragment::hasLogicalNewlineAtEnd):
+
+ This smaller set of changes markup generation to add the newline annotation described in the
+ comment at the start of this entry.
+
+ * khtml/xml/dom2_rangeimpl.cpp:
+ (DOM::RangeImpl::addCommentToHTMLMarkup): Simple helper.
+ (DOM::RangeImpl::toHTML): Added new EAnnotateForInterchange default argument to control whether
+ comment annotations are added to the markup generated.
+ * khtml/xml/dom2_rangeimpl.h: Add some new declarations.
+ * kwq/WebCoreBridge.mm:
+ (-[WebCoreBridge markupStringFromRange:nodes:]): Request that markup resulting from call to
+ DOM::RangeImpl::toHTML uses annotations when generating.
+
+ New tests.
+
+ * layout-tests/editing/pasteboard/paste-text-001-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-001.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-002-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-002.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-003-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-003.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-004-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-004.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-005-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-005.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-006-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-006.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-007-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-007.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-008-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-008.html: Added.
+ * layout-tests/editing/pasteboard/paste-text-009-expected.txt: Added.
+ * layout-tests/editing/pasteboard/paste-text-009.html: Added.
+
2004-11-29 Ken Kocienda <kocienda@apple.com>
Reviewed by Harrison
--- /dev/null
+#import <html_interchange.h>
--- /dev/null
+/*
+ * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef KHTML_EDITING_HTML_INTERCHANGE_H
+#define KHTML_EDITING_HTML_INTERCHANGE_H
+
+#define KHTMLInterchangeNewline "KHTMLInterchangeNewline"
+
+enum EAnnotateForInterchange { DoNotAnnotateForInterchange, AnnotateForInterchange };
+
+#endif
#include "dom2_rangeimpl.h"
#include "html_elementimpl.h"
#include "html_imageimpl.h"
+#include "html_interchange.h"
#include "htmlattrs.h"
#include "htmltags.h"
#include "khtml_part.h"
using DOM::DocumentImpl;
using DOM::DOMString;
using DOM::DOMStringImpl;
+using DOM::DoNotStayInBlock;
using DOM::DoNotUpdateLayout;
using DOM::EditingTextImpl;
using DOM::ElementImpl;
+using DOM::EStayInBlock;
using DOM::HTMLElementImpl;
using DOM::HTMLImageElementImpl;
using DOM::NamedAttrMapImpl;
//------------------------------------------------------------------------------------------
// ReplaceSelectionCommand
-ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace)
- : CompositeEditCommand(document), m_fragment(fragment), m_selectReplacement(selectReplacement), m_smartReplace(smartReplace)
+ReplacementFragment::ReplacementFragment(DocumentFragmentImpl *fragment)
+ : m_fragment(fragment), m_hasInterchangeNewlineComment(false), m_hasMoreThanOneBlock(false)
{
- ASSERT(m_fragment);
+ if (!m_fragment) {
+ m_type = EmptyFragment;
+ return;
+ }
+
m_fragment->ref();
+
+ NodeImpl *firstChild = m_fragment->firstChild();
+ NodeImpl *lastChild = m_fragment->lastChild();
+
+ if (!firstChild) {
+ m_type = EmptyFragment;
+ return;
+ }
+
+ if (firstChild == lastChild && firstChild->isTextNode()) {
+ m_type = SingleTextNodeFragment;
+ return;
+ }
+
+ m_type = TreeFragment;
+
+ NodeImpl *node = firstChild;
+ int blockCount = 0;
+ NodeImpl *commentToDelete = 0;
+ while (node) {
+ NodeImpl *next = node->traverseNextNode();
+ if (isInterchangeNewlineComment(node)) {
+ m_hasInterchangeNewlineComment = true;
+ commentToDelete = node;
+ }
+ else if (isProbablyBlock(node))
+ blockCount++;
+ node = next;
+ }
+
+ if (commentToDelete)
+ removeNode(commentToDelete);
+
+ firstChild = m_fragment->firstChild();
+ lastChild = m_fragment->lastChild();
+ if (!isProbablyBlock(firstChild))
+ blockCount++;
+ if (!isProbablyBlock(lastChild) && firstChild != lastChild)
+ blockCount++;
+
+ if (blockCount > 1)
+ m_hasMoreThanOneBlock = true;
+}
+
+ReplacementFragment::~ReplacementFragment()
+{
+ if (m_fragment)
+ m_fragment->deref();
+}
+
+NodeImpl *ReplacementFragment::firstChild() const
+{
+ return m_fragment->firstChild();
+}
+
+NodeImpl *ReplacementFragment::lastChild() const
+{
+ return m_fragment->lastChild();
+}
+
+NodeImpl *ReplacementFragment::mergeStartNode() const
+{
+ NodeImpl *node = m_fragment->firstChild();
+ while (node) {
+ NodeImpl *next = node->traverseNextNode();
+ if (!isProbablyBlock(node))
+ return node;
+ node = next;
+ }
+ return 0;
+}
+
+NodeImpl *ReplacementFragment::mergeEndNode() const
+{
+ NodeImpl *node = m_fragment->lastChild();
+ while (node && node->lastChild())
+ node = node->lastChild();
+ while (node) {
+ NodeImpl *prev = node->traversePreviousNode();
+ if (!isProbablyBlock(node)) {
+ NodeImpl *previousSibling = node->previousSibling();
+ while (1) {
+ if (!previousSibling || isProbablyBlock(previousSibling))
+ return node;
+ node = previousSibling;
+ }
+ }
+ node = prev;
+ }
+ return 0;
+}
+
+void ReplacementFragment::pruneEmptyNodes()
+{
+ bool run = true;
+ while (run) {
+ run = false;
+ NodeImpl *node = m_fragment->firstChild();
+ while (node) {
+ if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
+ (isProbablyBlock(node) && node->childNodeCount() == 0)) {
+ NodeImpl *next = node->traverseNextSibling();
+ removeNode(node);
+ node = next;
+ run = true;
+ }
+ else {
+ node = node->traverseNextNode();
+ }
+ }
+ }
+}
+
+bool ReplacementFragment::isInterchangeNewlineComment(const NodeImpl *node)
+{
+ return isComment(node) && node->nodeValue() == KHTMLInterchangeNewline;
+}
+
+void ReplacementFragment::removeNode(NodeImpl *node)
+{
+ if (!node)
+ return;
+
+ NodeImpl *parent = node->parentNode();
+ if (!parent)
+ return;
+
+ int exceptionCode = 0;
+ parent->removeChild(node, exceptionCode);
+ ASSERT(exceptionCode == 0);
+ }
+
+bool isComment(const NodeImpl *node)
+{
+ return node && node->nodeType() == Node::COMMENT_NODE;
+}
+
+bool isProbablyBlock(const NodeImpl *node)
+{
+ if (!node)
+ return false;
+
+ switch (node->id()) {
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_P:
+ case ID_PRE:
+ case ID_TD:
+ case ID_TH:
+ case ID_TR:
+ case ID_UL:
+ return true;
+ }
+
+ return false;
+}
+
+ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace)
+ : CompositeEditCommand(document),
+ m_fragment(fragment),
+ m_selectReplacement(selectReplacement),
+ m_smartReplace(smartReplace)
+{
}
ReplaceSelectionCommand::~ReplaceSelectionCommand()
{
- ASSERT(m_fragment);
- m_fragment->deref();
}
void ReplaceSelectionCommand::doApply()
{
- NodeImpl *firstChild = m_fragment->firstChild();
- NodeImpl *lastChild = m_fragment->lastChild();
-
Selection selection = endingSelection();
+ VisiblePosition visibleStart(selection.start());
+ VisiblePosition visibleEnd(selection.end());
+ bool startAtStartOfLine = isFirstVisiblePositionOnLine(visibleStart);
+ bool startAtStartOfBlock = isFirstVisiblePositionInBlock(visibleStart);
+ bool startAtEndOfBlock = isLastVisiblePositionInBlock(visibleStart);
+ bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
+ NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
+ NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
+
+ bool mergeStart = !(startAtStartOfLine && (m_fragment.hasInterchangeNewlineComment() || m_fragment.hasMoreThanOneBlock()));
+ bool mergeEnd = !m_fragment.hasInterchangeNewlineComment() && m_fragment.hasMoreThanOneBlock();
+ Position startPos = Position(selection.start().node()->enclosingBlockFlowElement(), 0);
+ Position endPos;
+ EStayInBlock upstreamStayInBlock = StayInBlock;
// Delete the current selection, or collapse whitespace, as needed
- if (selection.isRange())
- deleteSelection();
+ if (selection.isRange()) {
+ deleteSelection(false, !(m_fragment.hasInterchangeNewlineComment() || m_fragment.hasMoreThanOneBlock()));
+ }
+ else if (selection.isCaret() && mergeEnd && !startAtBlockBoundary) {
+ // The start and the end need to wind up in separate blocks.
+ // Insert a paragraph separator to make that happen.
+ insertParagraphSeparator();
+ upstreamStayInBlock = DoNotStayInBlock;
+ }
- KHTMLPart *part = document()->part();
- ASSERT(part);
+ selection = endingSelection();
+ if (!startAtBlockBoundary || !startPos.node()->inDocument())
+ startPos = selection.start().upstream(upstreamStayInBlock);
+ endPos = selection.end().downstream();
// This command does not use any typing style that is set as a residual effect of
// a delete.
// FIXME: Improve typing style.
// See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+ KHTMLPart *part = document()->part();
part->clearTypingStyle();
setTypingStyle(0);
-
- selection = endingSelection();
- ASSERT(selection.isCaret());
+ if (!m_fragment.firstChild())
+ return;
+
// Now that we are about to add content, check to see if a placeholder element
// can be removed.
- Position pos = selection.start();
- NodeImpl *block = pos.node()->enclosingBlockFlowElement();
+ NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
if (removeBlockPlaceholderIfNeeded(block)) {
- pos = Position(block, 0);
+ startPos = Position(block, 0);
}
bool addLeadingSpace = false;
bool addTrailingSpace = false;
if (m_smartReplace) {
- addLeadingSpace = pos.leadingWhitespacePosition().isNull();
- addTrailingSpace = pos.trailingWhitespacePosition().isNull();
+ addLeadingSpace = startPos.leadingWhitespacePosition().isNull();
+ addTrailingSpace = endPos.trailingWhitespacePosition().isNull();
}
#if APPLE_CHANGES
if (addLeadingSpace) {
- QChar previousChar = VisiblePosition(pos).previous().character();
+ QChar previousChar = VisiblePosition(startPos).previous().character();
if (!previousChar.isNull()) {
addLeadingSpace = !KWQ(part)->isCharacterSmartReplaceExempt(previousChar, true);
}
}
if (addTrailingSpace) {
- QChar thisChar = VisiblePosition(pos).character();
+ QChar thisChar = VisiblePosition(endPos).character();
if (!thisChar.isNull()) {
addTrailingSpace = !KWQ(part)->isCharacterSmartReplaceExempt(thisChar, false);
}
}
#endif
+
+ document()->updateLayout();
+
+ NodeImpl *refBlock = startPos.node()->enclosingBlockFlowElement();
+ Position insertionPos = startPos;
+ bool insertBlocksBefore = true;
+
+ NodeImpl *firstNodeInserted = 0;
+ NodeImpl *lastNodeInserted = 0;
+ bool lastNodeInsertedInMergeEnd = false;
+
+ // prune empty nodes from fragment
+ m_fragment.pruneEmptyNodes();
+
+ // Merge content into the end block, if necessary.
+ if (mergeEnd) {
+ NodeImpl *node = m_fragment.mergeEndNode();
+ if (node) {
+ NodeImpl *refNode = node;
+ NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+ insertNodeAt(refNode, endPos.node(), endPos.offset());
+ firstNodeInserted = refNode;
+ lastNodeInserted = refNode;
+ while (node && !isProbablyBlock(node)) {
+ NodeImpl *next = node->nextSibling();
+ insertNodeAfter(node, refNode);
+ lastNodeInserted = node;
+ refNode = node;
+ node = next;
+ }
+ lastNodeInsertedInMergeEnd = true;
+ }
+ }
- if (!firstChild) {
- // Pasting something that didn't parse or was empty.
- ASSERT(!lastChild);
- } else if (firstChild == lastChild && firstChild->isTextNode()) {
- // FIXME: HTML fragment case needs to be improved to the point
- // where we can remove this separate case.
-
- // Simple text paste. Treat as if the text were typed.
- Position upstreamStart(pos.upstream(StayInBlock));
- DOMString text = static_cast<TextImpl *>(firstChild)->data();
- if (addLeadingSpace) {
- text = " " + text;
+ // prune empty nodes from fragment
+ m_fragment.pruneEmptyNodes();
+
+ // Merge content into the start block, if necessary.
+ if (mergeStart) {
+ NodeImpl *node = m_fragment.mergeStartNode();
+ NodeImpl *insertionNode = 0;
+ if (node) {
+ NodeImpl *refNode = node;
+ NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+ insertNodeAt(refNode, startPos.node(), startPos.offset());
+ firstNodeInserted = refNode;
+ if (!lastNodeInsertedInMergeEnd)
+ lastNodeInserted = refNode;
+ insertionNode = refNode;
+ while (node && !isProbablyBlock(node)) {
+ NodeImpl *next = node->nextSibling();
+ insertNodeAfter(node, refNode);
+ if (!lastNodeInsertedInMergeEnd)
+ lastNodeInserted = node;
+ insertionNode = node;
+ refNode = node;
+ node = next;
+ }
}
- if (addTrailingSpace) {
- text += " ";
+ if (insertionNode)
+ insertionPos = Position(insertionNode, insertionNode->caretMaxOffset());
+ insertBlocksBefore = false;
+ }
+
+ // prune empty nodes from fragment
+ m_fragment.pruneEmptyNodes();
+
+ // Merge everything remaining.
+ NodeImpl *node = m_fragment.firstChild();
+ if (node) {
+ NodeImpl *refNode = node;
+ NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+ if (isProbablyBlock(refNode) && (insertBlocksBefore || startAtStartOfBlock)) {
+ insertNodeBefore(refNode, refBlock);
}
- inputText(text, m_selectReplacement);
- }
- else {
- // HTML fragment paste.
-
- // FIXME: Add leading and trailing spaces to the fragment?
- // Or just insert them as we insert it?
-
- NodeImpl *beforeNode = firstChild;
- NodeImpl *node = firstChild->nextSibling();
-
- insertNodeAt(firstChild, pos.node(), pos.offset());
-
- // Insert the nodes from the fragment
+ else if (isProbablyBlock(refNode) && startAtEndOfBlock) {
+ insertNodeAfter(refNode, refBlock);
+ }
+ else {
+ insertNodeAt(refNode, insertionPos.node(), insertionPos.offset());
+ }
+ if (!firstNodeInserted)
+ firstNodeInserted = refNode;
+ if (!lastNodeInsertedInMergeEnd)
+ lastNodeInserted = refNode;
while (node) {
NodeImpl *next = node->nextSibling();
- insertNodeAfter(node, beforeNode);
- beforeNode = node;
+ insertNodeAfter(node, refNode);
+ if (!lastNodeInsertedInMergeEnd)
+ lastNodeInserted = node;
+ refNode = node;
node = next;
}
- ASSERT(beforeNode);
-
- // Find the last leaf.
- NodeImpl *lastLeaf = lastChild;
- while (1) {
- NodeImpl *nextChild = lastLeaf->lastChild();
- if (!nextChild)
- break;
- lastLeaf = nextChild;
- }
+ insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+ }
- // Find the first leaf.
- NodeImpl *firstLeaf = firstChild;
- while (1) {
- NodeImpl *nextChild = firstLeaf->firstChild();
- if (!nextChild)
- break;
- firstLeaf = nextChild;
- }
-
- Selection replacementSelection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()));
- if (m_selectReplacement) {
- // Select what was inserted.
- setEndingSelection(replacementSelection);
- }
- else {
- // Place the cursor after what was inserted, and mark misspellings in the inserted content.
- selection = Selection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
- setEndingSelection(selection);
+ if (m_fragment.hasInterchangeNewlineComment()) {
+ if (startBlock == endBlock && !isProbablyBlock(lastNodeInserted)) {
+ setEndingSelection(insertionPos);
+ insertParagraphSeparator();
+ endPos = endingSelection().end().downstream();
}
+ completeHTMLReplacement(startPos, endPos);
+ }
+ else {
+ completeHTMLReplacement(firstNodeInserted, lastNodeInserted);
+ }
+}
+
+void ReplaceSelectionCommand::completeHTMLReplacement(const Position &start, const Position &end)
+ {
+ if (start.isNull() || !start.node()->inDocument() || end.isNull() || !end.node()->inDocument())
+ return;
+ m_selectReplacement ? setEndingSelection(Selection(start, end)) : setEndingSelection(end);
+}
+
+void ReplaceSelectionCommand::completeHTMLReplacement(NodeImpl *firstNodeInserted, NodeImpl *lastNodeInserted)
+{
+ if (!firstNodeInserted || !firstNodeInserted->inDocument() ||
+ !lastNodeInserted || !lastNodeInserted->inDocument())
+ return;
+
+ // Find the last leaf.
+ NodeImpl *lastLeaf = lastNodeInserted;
+ while (1) {
+ NodeImpl *nextChild = lastLeaf->lastChild();
+ if (!nextChild)
+ break;
+ lastLeaf = nextChild;
+ }
+
+ // Find the first leaf.
+ NodeImpl *firstLeaf = firstNodeInserted;
+ while (1) {
+ NodeImpl *nextChild = firstLeaf->firstChild();
+ if (!nextChild)
+ break;
+ firstLeaf = nextChild;
+ }
+
+ Position start(firstLeaf, firstLeaf->caretMinOffset());
+ Position end(lastLeaf, lastLeaf->caretMaxOffset());
+ Selection replacementSelection(start, end);
+ if (m_selectReplacement) {
+ // Select what was inserted.
+ setEndingSelection(replacementSelection);
+ }
+ else {
+ // Place the cursor after what was inserted, and mark misspellings in the inserted content.
+ setEndingSelection(end);
}
}
//------------------------------------------------------------------------------------------
// ReplaceSelectionCommand
+// --- ReplacementFragment helper class
+
+class ReplacementFragment
+{
+public:
+ ReplacementFragment(DOM::DocumentFragmentImpl *fragment);
+ ~ReplacementFragment();
+
+ enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
+
+ DOM::DocumentFragmentImpl *root() const { return m_fragment; }
+ DOM::NodeImpl *firstChild() const;
+ DOM::NodeImpl *lastChild() const;
+
+ DOM::NodeImpl *mergeStartNode() const;
+ DOM::NodeImpl *mergeEndNode() const;
+
+ void pruneEmptyNodes();
+
+ EFragmentType type() const { return m_type; }
+ bool isEmpty() const { return m_type == EmptyFragment; }
+ bool isSingleTextNode() const { return m_type == SingleTextNodeFragment; }
+ bool isTreeFragment() const { return m_type == TreeFragment; }
+
+ bool hasMoreThanOneBlock() const { return m_hasMoreThanOneBlock; }
+ bool hasInterchangeNewlineComment() const { return m_hasInterchangeNewlineComment; }
+
+private:
+ // no copy construction or assignment
+ ReplacementFragment(const ReplacementFragment &);
+ ReplacementFragment &operator=(const ReplacementFragment &);
+
+ static bool isInterchangeNewlineComment(const DOM::NodeImpl *);
+ void removeNode(DOM::NodeImpl *);
+
+ EFragmentType m_type;
+ DOM::DocumentFragmentImpl *m_fragment;
+ bool m_hasInterchangeNewlineComment;
+ bool m_hasMoreThanOneBlock;
+};
+
+// free-floating helper functions
+bool isProbablyBlock(const DOM::NodeImpl *);
+bool isComment(const DOM::NodeImpl *);
+
class ReplaceSelectionCommand : public CompositeEditCommand
{
public:
virtual void doApply();
private:
- DOM::DocumentFragmentImpl *m_fragment;
+ void completeHTMLReplacement(const DOM::Position &, const DOM::Position &);
+ void completeHTMLReplacement(DOM::NodeImpl *, DOM::NodeImpl *);
+
+ ReplacementFragment m_fragment;
bool m_selectReplacement;
bool m_smartReplace;
};
#include "dom_xmlimpl.h"
#include "html/html_elementimpl.h"
#include "misc/htmltags.h"
+#include "editing/visible_position.h"
#include "editing/visible_text.h"
#include "xml/dom_position.h"
return text;
}
-DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes) const
+void RangeImpl::addCommentToHTMLMarkup(const DOMString &comment, QStringList &markups, EAddToMarkup appendOrPrepend) const
+ {
+ if (!m_ownerDocument)
+ return;
+
+ CommentImpl *n = new CommentImpl(m_ownerDocument, comment);
+ n->ref();
+ switch (appendOrPrepend) {
+ case PrependToMarkup:
+ markups.prepend(n->startMarkup(this));
+ break;
+ case AppendToMarkup:
+ markups.append(n->startMarkup(this));
+ break;
+ }
+ n->deref();
+}
+
+DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes, EAnnotateForInterchange annotate) const
{
int exceptionCode;
NodeImpl *commonAncestor = commonAncestorContainer(exceptionCode);
NodeImpl *next;
for (NodeImpl *n = startNode(); n != pastEnd; n = next) {
next = n->traverseNextNode();
+ if (!n->renderer())
+ continue;
+
+ if (n->isBlockFlow() && next == pastEnd) {
+ // Don't write out an empty block.
+ continue;
+ }
// Add the node to the markup.
markups.append(n->startMarkup(this));
lastClosed = n;
// Check if the node is the last leaf of a tree.
- if (n->nextSibling() == 0) {
+ if (n->nextSibling() == 0 || next == pastEnd) {
if (!ancestorsToClose.isEmpty()) {
// Close up the ancestors.
while (NodeImpl *ancestor = ancestorsToClose.last()) {
}
}
+ if (annotate) {
+ Position pos(m_endContainer, m_endOffset);
+ NodeImpl *block = pos.node()->enclosingBlockFlowElement();
+ NodeImpl *upstreamBlock = pos.upstream().node()->enclosingBlockFlowElement();
+ if (block != upstreamBlock) {
+ addCommentToHTMLMarkup(KHTMLInterchangeNewline, markups, AppendToMarkup);
+ }
+ }
+
return markups.join("");
}
#include <qptrlist.h>
#include "dom/dom2_range.h"
+#include "editing/html_interchange.h"
#include "misc/shared.h"
+class QStringList;
+
namespace DOM {
class DocumentPtr;
DocumentFragmentImpl *cloneContents ( int &exceptioncode );
void insertNode( NodeImpl *newNode, int &exceptioncode );
DOMString toString ( int &exceptioncode ) const;
- DOMString toHTML(QPtrList<NodeImpl> *nodes=NULL) const;
+ DOMString toHTML(QPtrList<NodeImpl> *nodes=NULL, EAnnotateForInterchange annotate=DoNotAnnotateForInterchange) const;
DOMString text() const;
DocumentFragmentImpl *createContextualFragment ( DOMString &html, int &exceptioncode ) const;
void setEndContainer(NodeImpl *_endContainer);
void checkDeleteExtract(int &exceptioncode);
bool containedByReadOnly() const;
+
+ enum EAddToMarkup { PrependToMarkup, AppendToMarkup };
+ void addCommentToHTMLMarkup(const DOMString &, QStringList &, EAddToMarkup) const;
};
} // namespace
if (nodes) {
nodeList = new QPtrList<NodeImpl>;
}
- NSString *markupString = [range _rangeImpl]->toHTML(nodeList).string().getNSString();
+ NSString *markupString = [range _rangeImpl]->toHTML(nodeList, AnnotateForInterchange).string().getNSString();
if (nodes) {
*nodes = [self nodesFromList:nodeList];
delete nodeList;