Title: [236785] trunk
Revision
236785
Author
rn...@webkit.org
Date
2018-10-02 23:28:07 -0700 (Tue, 02 Oct 2018)

Log Message

Copying content with shadow DOM doesn't copy any contents
https://bugs.webkit.org/show_bug.cgi?id=157443

Reviewed by Wenson Hsieh.

Source/WebCore:

This patch adds the support for copying and pasting content across shadow boundaries in HTML and plain text,
which is enabled whenever selection across shadow boundaries is enabled.

To do this, TextIterator now has a constructor which takes two Positions, and the node traversal code in
StyledMarkupAccumulator has been abstracted via helper functions as done for TextIterator.

When serializing a HTMl slot element, serialize it as a span with "display: contents" to make sure when
the content is pasted into a shadow tree, it wouldn't affect the slot assignment of the shadow tree.

Tests: editing/pasteboard/copy-paste-across-shadow-boundaries-1.html
       editing/pasteboard/copy-paste-across-shadow-boundaries-2.html
       editing/pasteboard/copy-paste-across-shadow-boundaries-3.html
       editing/pasteboard/copy-paste-across-shadow-boundaries-4.html
       editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html
       editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html
       editing/pasteboard/copy-paste-with-shadow-content.html

* dom/ComposedTreeIterator.h:
(WebCore::assignedSlotIgnoringUserAgentShadow): Moved from TextIterator.cpp.
(WebCore::shadowRootIgnoringUserAgentShadow): Ditto.
(WebCore::firstChildInComposedTreeIgnoringUserAgentShadow): Ditto.
(WebCore::nextSiblingInComposedTreeIgnoringUserAgentShadow): Ditto.
* dom/Position.h:
(WebCore::Position::treeScope const): Added.
* editing/EditingStyle.cpp:
(WebCore::EditingStyle::addDisplayContents): Added.
* editing/EditingStyle.h:
* editing/Editor.cpp:
(WebCore::Editor::selectedText const): Use the new behavior when selectionAcrossShadowBoundariesEnabled is set.
(WebCore::Editor::selectedTextForDataTransfer const): Ditto.
* editing/MarkupAccumulator.cpp:
(WebCore::MarkupAccumulator::appendEndElement): Renamed from appendEndTag. Now takes StringBuilder.
* editing/MarkupAccumulator.h:
(WebCore::MarkupAccumulator::appendEndTag):
* editing/TextIterator.cpp:
(WebCore::TextIterator::TextIterator): Added a new variant which takes two positions.
(WebCore::TextIterator::init):
(WebCore::firstChild):
(WebCore::nextSibling):
(WebCore::plainText): Ditto.
* editing/TextIterator.h:
* editing/cocoa/EditorCocoa.mm:
(WebCore::Editor::selectionInHTMLFormat): Use the new behavior if selectionAcrossShadowBoundariesEnabled is set.
* editing/gtk/EditorGtk.cpp:
(WebCore::Editor::writeSelectionToPasteboard): Ditto.
* editing/markup.cpp:
(WebCore::StyledMarkupAccumulator::parentNode): Added.
(WebCore::StyledMarkupAccumulator::firstChild): Added.
(WebCore::StyledMarkupAccumulator::nextSibling): Added.
(WebCore::StyledMarkupAccumulator::nextSkippingChildren): Added.
(WebCore::StyledMarkupAccumulator::hasChildNodes): Added.
(WebCore::StyledMarkupAccumulator::isDescendantOf): Added.
(WebCore::StyledMarkupAccumulator::StyledMarkupAccumulator):
(WebCore::StyledMarkupAccumulator::appendElement): Serialize a slot element as a span with display: contents.
(WebCore::StyledMarkupAccumulator::appendEndElement): Added. Ditto.
(WebCore::StyledMarkupAccumulator::serializeNodes):
(WebCore::StyledMarkupAccumulator::traverseNodesForSerialization): Use the newly added helper functions to
traverse the composed tree when m_useComposedTree is set.
(WebCore::commonShadowIncludingAncestor): Added.
(WebCore::serializePreservingVisualAppearanceInternal): Added SerializeComposedTree as an argument. Also use
StyledMarkupAccumulator::parentNode to serialize special common ancestors; e.g. to preserve b, i, etc...
(WebCore::serializePreservingVisualAppearance): Ditto to the variant which takes VisibleSelection.
(WebCore::sanitizedMarkupForFragmentInDocument):
* editing/markup.h:
* editing/wpe/EditorWPE.cpp:
(WebCore::Editor::writeSelectionToPasteboard):
* loader/archive/cf/LegacyWebArchive.cpp:
(WebCore::LegacyWebArchive::createFromSelection):
* page/PageSerializer.cpp:
(WebCore::PageSerializer::SerializerMarkupAccumulator::appendEndElement):
* testing/Internals.cpp:
(WebCore::Internals::setSelectionWithoutValidation): Added. A helper function to create a selection across
shadow boundaries for testing purposes.
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

Added tests for copying and pasting across shadow boundaries with HTML and plain text.

* editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-1.html: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-2.html: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-3.html: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-4.html: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt: Added.
* editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html: Added.
* editing/pasteboard/copy-paste-with-shadow-content-expected.txt: Added.
* editing/pasteboard/copy-paste-with-shadow-content.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (236784 => 236785)


--- trunk/LayoutTests/ChangeLog	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/LayoutTests/ChangeLog	2018-10-03 06:28:07 UTC (rev 236785)
@@ -1,3 +1,27 @@
+2018-10-02  Ryosuke Niwa  <rn...@webkit.org>
+
+        Copying content with shadow DOM doesn't copy any contents
+        https://bugs.webkit.org/show_bug.cgi?id=157443
+
+        Reviewed by Wenson Hsieh.
+
+        Added tests for copying and pasting across shadow boundaries with HTML and plain text.
+
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-1.html: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-2.html: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-3.html: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-4.html: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt: Added.
+        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html: Added.
+        * editing/pasteboard/copy-paste-with-shadow-content-expected.txt: Added.
+        * editing/pasteboard/copy-paste-with-shadow-content.html: Added.
+
 2018-10-01  Ryosuke Niwa  <rn...@webkit.org>
 
         GC can collect JS wrappers of nodes in the mutation records waiting to be delivered

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,14 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| "hello "
+| <span>
+|   id="host"
+|   <span>
+|     style="display: contents;"
+|     "world"
+|   " Web<#selection-caret>"
+
+text/plain:
+| "hello world Web"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-1.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,39 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue">hello <span id="host">world</span> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = '<slot></slot> WebKit';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(source, 0, shadowRoot.lastChild, shadowRoot.lastChild.data.indexOf('Kit'));
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,15 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "world" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <span>
+|   id="host"
+|   <span>
+|     style="display: contents;"
+|     "world"
+|   " "
+|   "WebKit"
+| " rocks<#selection-caret>"
+
+text/plain:
+| "world WebKit rocks"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-2.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,39 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "world" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue">hello <span id="host">world</span> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = '<slot></slot> WebKit';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(host.firstChild, 0, source.lastChild, source.lastChild.data.length);
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,13 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "WebKit" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <span>
+|   id="host"
+|   <span>
+|     style="display: contents;"
+|     "world WebKit"
+| " rocks<#selection-caret>"
+
+text/plain:
+| "world WebKit rocks"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-3.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,39 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "WebKit" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue"><span id="host">world WebKit</span> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = 'hello <slot></slot>';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(host.firstChild, 0, source.lastChild, source.lastChild.data.length);
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,11 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| "hello "
+| <span>
+|   style="display: contents;"
+|   "world Web<#selection-caret>"
+
+text/plain:
+| "hello world Web"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-4.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,39 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue"><span id="host">world WebKit</span> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = 'hello <slot></slot>';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(shadowRoot, 0, host.firstChild, host.firstChild.data.indexOf('Kit'));
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,17 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <b>
+|   <span>
+|     id="host"
+|     "hello "
+|     <span>
+|       id="host"
+|       <span>
+|         style="display: contents; color: blue;"
+|         "world"
+|       " Web<#selection-caret>"
+
+text/plain:
+| "hello world Web"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,39 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue"><b>hello <span id="host">world</span></b> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = '<slot style="color: blue;"></slot> WebKit';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(source, 0, shadowRoot.lastChild, shadowRoot.lastChild.data.indexOf('Kit'));
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,21 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "WebKit", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <div>
+|   id="host1"
+|   style="font-style: italic;"
+|   <span>
+|     style="display: contents; color: blue;"
+|     <b>
+|       "hello"
+|   " "
+|   "world"
+| <div>
+|   id="host2"
+|   <u>
+|     "WebKit <#selection-caret>"
+
+text/plain:
+| "hello world
+WebKit "

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,42 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content across shadow boundaries.<br>
+To test manually, copy text blow starting from "hello" ending with "WebKit", and paste into the green box below. All the text shoul be copied & pasted.</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue"><div style="font-style: italic" id="host1"><b>hello</b></div><div id="host2">rocks</div></div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot1 = host1.attachShadow({mode: 'open'});
+shadowRoot1.innerHTML = '<slot style="color: blue;"></slot> world';
+
+const shadowRoot2 = host2.attachShadow({mode: 'open'});
+shadowRoot2.innerHTML = '<u>WebKit <slot></slot></u>';
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    internals.setSelectionWithoutValidation(source, 0, shadowRoot2.querySelector('u'), 1);
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content-expected.txt (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,31 @@
+This tests copying and pasting content with a shadow tree.
+To test manually, copy paste the content in the blue box to the green box below. All the text shoul be copied & pasted
+
+pasted:
+| <span>
+|   id="host"
+|   "hello "
+|   <span>
+|     id="host"
+|     <span>
+|       style="display: contents;"
+|       "world"
+|     " "
+|     "WebKit"
+|   " rocks<#selection-caret>"
+
+text/html:
+| <span>
+|   id="host"
+|   "hello "
+|   <span>
+|     id="host"
+|     <span>
+|       style="display: contents;"
+|       "world"
+|     " "
+|     "WebKit"
+|   " rocks<#selection-caret>"
+
+text/plain:
+| "hello world WebKit rocks"

Added: trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content.html (0 => 236785)


--- trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/copy-paste-with-shadow-content.html	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,40 @@
+<!DOCTYPE html><!-- webkit-test-runner [ internal:selectionAcrossShadowBoundariesEnabled=true ] -->
+<html>
+<body>
+<p id="description">This tests copying and pasting content with a shadow tree.<br>
+To test manually, copy paste the content in the blue box to the green box below. All the text shoul be copied & pasted</p>
+<style> .box { border: solid 1px; padding: 0.2rem; margin-bottom: 1rem; } </style>
+<div id="source" class="box" style="border-color: blue">hello <span id="host">world</span> rocks</div>
+<div id="destination" class="box" style="border-color: blue" contenteditable></div>
+<pre id="htmlResult"></pre>
+<pre id="textResult"></pre>
+<script src=""
+<script>
+
+Markup.description(description.textContent);
+Markup.waitUntilDone();
+
+const shadowRoot = host.attachShadow({mode: 'open'});
+shadowRoot.innerHTML = '<slot></slot> WebKit';
+getSelection().selectAllChildren(source);
+
+destination.addEventListener('paste', (event) => {
+    htmlResult.textContent = event.clipboardData.getData('text/html');
+    textResult.textContent = event.clipboardData.getData('text/plain');
+    setTimeout(() => {
+        Markup.dump(destination, 'pasted');
+        Markup.dump(destination, 'text/html');
+        Markup.dump(textResult, 'text/plain');
+        Markup.notifyDone();
+    }, 0);
+});
+
+if (window.testRunner) {
+    document.execCommand('copy');
+    destination.focus();
+    document.execCommand('paste');
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt (0 => 236785)


--- trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,18 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "world" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <span>
+|   id="host"
+|   style="-webkit-text-size-adjust: auto;"
+|   <span>
+|     style="display: contents;"
+|     "world"
+|   " "
+|   "WebKit"
+| <span>
+|   style="-webkit-text-size-adjust: auto;"
+|   " rocks<#selection-caret>"
+
+text/plain:
+| "world WebKit rocks"

Added: trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt (0 => 236785)


--- trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,16 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "WebKit" ending with "rocks", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <span>
+|   id="host"
+|   style="-webkit-text-size-adjust: auto;"
+|   <span>
+|     style="display: contents;"
+|     "world WebKit"
+| <span>
+|   style="-webkit-text-size-adjust: auto;"
+|   " rocks<#selection-caret>"
+
+text/plain:
+| "world WebKit rocks"

Added: trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt (0 => 236785)


--- trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,13 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <span>
+|   style="-webkit-text-size-adjust: auto;"
+|   "hello "
+| <span>
+|   style="-webkit-text-size-adjust: auto; display: contents;"
+|   "world Web<#selection-caret>"
+
+text/plain:
+| "hello world Web"

Added: trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt (0 => 236785)


--- trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,18 @@
+This tests copying and pasting content across shadow boundaries.
+To test manually, copy text blow starting from "hello" ending with "Web", and paste into the green box below. All the text shoul be copied & pasted.
+
+pasted html:
+| <b>
+|   style="-webkit-text-size-adjust: auto;"
+|   <span>
+|     id="host"
+|     "hello "
+|     <span>
+|       id="host"
+|       <span>
+|         style="display: contents; color: blue;"
+|         "world"
+|       " Web<#selection-caret>"
+
+text/plain:
+| "hello world Web"

Added: trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-with-shadow-content-expected.txt (0 => 236785)


--- trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-with-shadow-content-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/platform/ios/editing/pasteboard/copy-paste-with-shadow-content-expected.txt	2018-10-03 06:28:07 UTC (rev 236785)
@@ -0,0 +1,33 @@
+This tests copying and pasting content with a shadow tree.
+To test manually, copy paste the content in the blue box to the green box below. All the text shoul be copied & pasted
+
+pasted:
+| <span>
+|   id="host"
+|   style="-webkit-text-size-adjust: auto;"
+|   "hello "
+|   <span>
+|     id="host"
+|     <span>
+|       style="display: contents;"
+|       "world"
+|     " "
+|     "WebKit"
+|   " rocks<#selection-caret>"
+
+text/html:
+| <span>
+|   id="host"
+|   style="-webkit-text-size-adjust: auto;"
+|   "hello "
+|   <span>
+|     id="host"
+|     <span>
+|       style="display: contents;"
+|       "world"
+|     " "
+|     "WebKit"
+|   " rocks<#selection-caret>"
+
+text/plain:
+| "hello world WebKit rocks"

Modified: trunk/Source/WebCore/ChangeLog (236784 => 236785)


--- trunk/Source/WebCore/ChangeLog	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/ChangeLog	2018-10-03 06:28:07 UTC (rev 236785)
@@ -1,3 +1,86 @@
+2018-10-02  Ryosuke Niwa  <rn...@webkit.org>
+
+        Copying content with shadow DOM doesn't copy any contents
+        https://bugs.webkit.org/show_bug.cgi?id=157443
+
+        Reviewed by Wenson Hsieh.
+
+        This patch adds the support for copying and pasting content across shadow boundaries in HTML and plain text,
+        which is enabled whenever selection across shadow boundaries is enabled.
+
+        To do this, TextIterator now has a constructor which takes two Positions, and the node traversal code in
+        StyledMarkupAccumulator has been abstracted via helper functions as done for TextIterator.
+
+        When serializing a HTMl slot element, serialize it as a span with "display: contents" to make sure when
+        the content is pasted into a shadow tree, it wouldn't affect the slot assignment of the shadow tree.
+
+        Tests: editing/pasteboard/copy-paste-across-shadow-boundaries-1.html
+               editing/pasteboard/copy-paste-across-shadow-boundaries-2.html
+               editing/pasteboard/copy-paste-across-shadow-boundaries-3.html
+               editing/pasteboard/copy-paste-across-shadow-boundaries-4.html
+               editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html
+               editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html
+               editing/pasteboard/copy-paste-with-shadow-content.html
+
+        * dom/ComposedTreeIterator.h:
+        (WebCore::assignedSlotIgnoringUserAgentShadow): Moved from TextIterator.cpp.
+        (WebCore::shadowRootIgnoringUserAgentShadow): Ditto.
+        (WebCore::firstChildInComposedTreeIgnoringUserAgentShadow): Ditto.
+        (WebCore::nextSiblingInComposedTreeIgnoringUserAgentShadow): Ditto.
+        * dom/Position.h:
+        (WebCore::Position::treeScope const): Added.
+        * editing/EditingStyle.cpp:
+        (WebCore::EditingStyle::addDisplayContents): Added.
+        * editing/EditingStyle.h:
+        * editing/Editor.cpp:
+        (WebCore::Editor::selectedText const): Use the new behavior when selectionAcrossShadowBoundariesEnabled is set.
+        (WebCore::Editor::selectedTextForDataTransfer const): Ditto.
+        * editing/MarkupAccumulator.cpp:
+        (WebCore::MarkupAccumulator::appendEndElement): Renamed from appendEndTag. Now takes StringBuilder.
+        * editing/MarkupAccumulator.h:
+        (WebCore::MarkupAccumulator::appendEndTag):
+        * editing/TextIterator.cpp:
+        (WebCore::TextIterator::TextIterator): Added a new variant which takes two positions.
+        (WebCore::TextIterator::init):
+        (WebCore::firstChild):
+        (WebCore::nextSibling):
+        (WebCore::plainText): Ditto.
+        * editing/TextIterator.h:
+        * editing/cocoa/EditorCocoa.mm:
+        (WebCore::Editor::selectionInHTMLFormat): Use the new behavior if selectionAcrossShadowBoundariesEnabled is set.
+        * editing/gtk/EditorGtk.cpp:
+        (WebCore::Editor::writeSelectionToPasteboard): Ditto.
+        * editing/markup.cpp:
+        (WebCore::StyledMarkupAccumulator::parentNode): Added.
+        (WebCore::StyledMarkupAccumulator::firstChild): Added.
+        (WebCore::StyledMarkupAccumulator::nextSibling): Added.
+        (WebCore::StyledMarkupAccumulator::nextSkippingChildren): Added.
+        (WebCore::StyledMarkupAccumulator::hasChildNodes): Added.
+        (WebCore::StyledMarkupAccumulator::isDescendantOf): Added.
+        (WebCore::StyledMarkupAccumulator::StyledMarkupAccumulator):
+        (WebCore::StyledMarkupAccumulator::appendElement): Serialize a slot element as a span with display: contents.
+        (WebCore::StyledMarkupAccumulator::appendEndElement): Added. Ditto.
+        (WebCore::StyledMarkupAccumulator::serializeNodes):
+        (WebCore::StyledMarkupAccumulator::traverseNodesForSerialization): Use the newly added helper functions to
+        traverse the composed tree when m_useComposedTree is set.
+        (WebCore::commonShadowIncludingAncestor): Added.
+        (WebCore::serializePreservingVisualAppearanceInternal): Added SerializeComposedTree as an argument. Also use
+        StyledMarkupAccumulator::parentNode to serialize special common ancestors; e.g. to preserve b, i, etc...
+        (WebCore::serializePreservingVisualAppearance): Ditto to the variant which takes VisibleSelection.
+        (WebCore::sanitizedMarkupForFragmentInDocument):
+        * editing/markup.h:
+        * editing/wpe/EditorWPE.cpp:
+        (WebCore::Editor::writeSelectionToPasteboard):
+        * loader/archive/cf/LegacyWebArchive.cpp:
+        (WebCore::LegacyWebArchive::createFromSelection):
+        * page/PageSerializer.cpp:
+        (WebCore::PageSerializer::SerializerMarkupAccumulator::appendEndElement):
+        * testing/Internals.cpp:
+        (WebCore::Internals::setSelectionWithoutValidation): Added. A helper function to create a selection across
+        shadow boundaries for testing purposes.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2018-10-02  Chris Dumez  <cdu...@apple.com>
 
         MessageEvent.ports should return the same object

Modified: trunk/Source/WebCore/dom/ComposedTreeIterator.h (236784 => 236785)


--- trunk/Source/WebCore/dom/ComposedTreeIterator.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/dom/ComposedTreeIterator.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include "ElementAndTextDescendantIterator.h"
+#include "HTMLSlotElement.h"
 #include "ShadowRoot.h"
 
 namespace WebCore {
@@ -199,4 +200,49 @@
 enum class ComposedTreeAsTextMode { Normal, WithPointers };
 WEBCORE_EXPORT String composedTreeAsText(ContainerNode& root, ComposedTreeAsTextMode = ComposedTreeAsTextMode::Normal);
 
+
+// Helper functions for walking the composed tree.
+// FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work.
+
+inline HTMLSlotElement* assignedSlotIgnoringUserAgentShadow(Node& node)
+{
+    auto* slot = node.assignedSlot();
+    if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent)
+        return nullptr;
+    return slot;
+}
+
+inline ShadowRoot* shadowRootIgnoringUserAgentShadow(Node& node)
+{
+    auto* shadowRoot = node.shadowRoot();
+    if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent)
+        return nullptr;
+    return shadowRoot;
+}
+
+inline Node* firstChildInComposedTreeIgnoringUserAgentShadow(Node& node)
+{
+    if (auto* shadowRoot = shadowRootIgnoringUserAgentShadow(node))
+        return shadowRoot->firstChild();
+    if (is<HTMLSlotElement>(node)) {
+        if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes())
+            return assignedNodes->at(0);
+    }
+    return node.firstChild();
+}
+
+inline Node* nextSiblingInComposedTreeIgnoringUserAgentShadow(Node& node)
+{
+    if (auto* slot = assignedSlotIgnoringUserAgentShadow(node)) {
+        auto* assignedNodes = slot->assignedNodes();
+        ASSERT(assignedNodes);
+        auto nodeIndex = assignedNodes->find(&node);
+        ASSERT(nodeIndex != notFound);
+        if (assignedNodes->size() > nodeIndex + 1)
+            return assignedNodes->at(nodeIndex + 1);
+        return nullptr;
+    }
+    return node.nextSibling();
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/dom/Position.h (236784 => 236785)


--- trunk/Source/WebCore/dom/Position.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/dom/Position.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -117,6 +117,7 @@
     Node* deprecatedNode() const { return m_anchorNode.get(); }
 
     Document* document() const { return m_anchorNode ? &m_anchorNode->document() : nullptr; }
+    TreeScope* treeScope() const { return m_anchorNode ? &m_anchorNode->treeScope() : nullptr; }
     Element* rootEditableElement() const
     {
         Node* container = containerNode();

Modified: trunk/Source/WebCore/editing/EditingStyle.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/EditingStyle.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/EditingStyle.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -1369,6 +1369,13 @@
     m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
 }
 
+void EditingStyle::addDisplayContents()
+{
+    if (!m_mutableStyle)
+        m_mutableStyle = MutableStyleProperties::create();
+    m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueContents);
+}
+
 bool EditingStyle::convertPositionStyle()
 {
     if (!m_mutableStyle)

Modified: trunk/Source/WebCore/editing/EditingStyle.h (236784 => 236785)


--- trunk/Source/WebCore/editing/EditingStyle.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/EditingStyle.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -149,6 +149,7 @@
     void removeStyleFromRulesAndContext(StyledElement&, Node* context);
     void removePropertiesInElementDefaultStyle(Element&);
     void forceInline();
+    void addDisplayContents();
     bool convertPositionStyle();
     bool isFloating();
     int legacyFontSize(Document&) const;

Modified: trunk/Source/WebCore/editing/Editor.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/Editor.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/Editor.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -3182,18 +3182,25 @@
 
 String Editor::selectedText() const
 {
-    return selectedText(TextIteratorDefaultBehavior);
+    TextIteratorBehavior behavior = TextIteratorDefaultBehavior;
+    if (m_frame.settings().selectionAcrossShadowBoundariesEnabled())
+        behavior |= TextIteratorTraversesFlatTree;
+    return selectedText(behavior);
 }
 
 String Editor::selectedTextForDataTransfer() const
 {
-    return selectedText(TextIteratorEmitsImageAltText);
+    TextIteratorBehavior behavior = TextIteratorEmitsImageAltText;
+    if (m_frame.settings().selectionAcrossShadowBoundariesEnabled())
+        behavior |= TextIteratorTraversesFlatTree;
+    return selectedText(behavior);
 }
 
 String Editor::selectedText(TextIteratorBehavior behavior) const
 {
     // We remove '\0' characters because they are not visibly rendered to the user.
-    return plainText(m_frame.selection().toNormalizedRange().get(), behavior).replaceWithLiteral('\0', "");
+    auto& selection = m_frame.selection().selection();
+    return plainText(selection.start(), selection.end(), behavior).replaceWithLiteral('\0', "");
 }
 
 static inline void collapseCaretWidth(IntRect& rect)

Modified: trunk/Source/WebCore/editing/MarkupAccumulator.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/MarkupAccumulator.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/MarkupAccumulator.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -192,9 +192,9 @@
         m_nodes->append(const_cast<Node*>(&node));
 }
 
-void MarkupAccumulator::appendEndTag(const Element& element)
+void MarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
 {
-    appendEndMarkup(m_markup, element);
+    appendEndMarkup(out, element);
 }
 
 void MarkupAccumulator::appendTextSubstring(const Text& text, unsigned start, unsigned length)

Modified: trunk/Source/WebCore/editing/MarkupAccumulator.h (236784 => 236785)


--- trunk/Source/WebCore/editing/MarkupAccumulator.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/MarkupAccumulator.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -76,10 +76,10 @@
     void appendEndTag(const Node& node)
     {
         if (is<Element>(node))
-            appendEndTag(downcast<Element>(node));
+            appendEndElement(m_markup, downcast<Element>(node));
     }
 
-    virtual void appendEndTag(const Element&);
+    virtual void appendEndElement(StringBuilder&, const Element&);
     virtual void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*);
     virtual void appendText(StringBuilder&, const Text&);
     virtual void appendElement(StringBuilder&, const Element&, Namespaces*);

Modified: trunk/Source/WebCore/editing/TextIterator.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/TextIterator.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/TextIterator.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -27,6 +27,7 @@
 #include "config.h"
 #include "TextIterator.h"
 
+#include "ComposedTreeIterator.h"
 #include "Document.h"
 #include "Editing.h"
 #include "FontCascade.h"
@@ -339,10 +340,35 @@
 
 // --------
 
+
+TextIterator::TextIterator(Position start, Position end, TextIteratorBehavior behavior)
+    : m_behavior(behavior)
+{
+    if (start.isNull() || end.isNull())
+        return;
+    ASSERT(comparePositions(start, end) <= 0);
+
+    RELEASE_ASSERT(behavior & TextIteratorTraversesFlatTree || start.treeScope() == end.treeScope());
+
+    start.document()->updateLayoutIgnorePendingStylesheets();
+
+    // FIXME: Use Position / PositionIterator instead to avoid offset computation.
+    m_startContainer = start.containerNode();
+    m_startOffset = start.computeOffsetInContainerNode();
+
+    m_endContainer = end.containerNode();
+    m_endOffset = end.computeOffsetInContainerNode();
+
+    m_node = start.firstNode().get();
+    if (!m_node)
+        return;
+
+    init();
+}
+
 TextIterator::TextIterator(const Range* range, TextIteratorBehavior behavior)
     : m_behavior(behavior)
 {
-    // FIXME: Only m_positionNode above needs to be initialized if range is null.
     if (!range)
         return;
 
@@ -358,11 +384,15 @@
     m_endContainer = &range->endContainer();
     m_endOffset = range->endOffset();
 
-    // Set up the current node for processing.
     m_node = range->firstNode();
     if (!m_node)
         return;
 
+    init();
+}
+
+void TextIterator::init()
+{
     if (isClippedByFrameAncestor(m_node->document(), m_behavior))
         return;
 
@@ -379,52 +409,11 @@
 
 TextIterator::~TextIterator() = default;
 
-static HTMLSlotElement* assignedAuthorSlot(Node& node)
-{
-    auto* slot = node.assignedSlot();
-    if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent)
-        return nullptr;
-    return slot;
-}
-
-static ShadowRoot* authorShadowRoot(Node& node)
-{
-    auto* shadowRoot = node.shadowRoot();
-    if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent)
-        return nullptr;
-    return shadowRoot;
-}
-
 // FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work.
-static inline Node* firstChildInFlatTreeIgnoringUserAgentShadow(Node& node)
-{
-    if (auto* shadowRoot = authorShadowRoot(node))
-        return shadowRoot->firstChild();
-    if (is<HTMLSlotElement>(node)) {
-        if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes())
-            return assignedNodes->at(0);
-    }
-    return node.firstChild();
-}
-
-static inline Node* nextSiblingInFlatTreeIgnoringUserAgentShadow(Node& node)
-{
-    if (auto* slot = assignedAuthorSlot(node)) {
-        auto* assignedNodes = slot->assignedNodes();
-        ASSERT(assignedNodes);
-        auto nodeIndex = assignedNodes->find(&node);
-        ASSERT(nodeIndex != notFound);
-        if (assignedNodes->size() > nodeIndex + 1)
-            return assignedNodes->at(nodeIndex + 1);
-        return nullptr;
-    }
-    return node.nextSibling();
-}
-
 static inline Node* firstChild(TextIteratorBehavior options, Node& node)
 {
     if (UNLIKELY(options & TextIteratorTraversesFlatTree))
-        return firstChildInFlatTreeIgnoringUserAgentShadow(node);
+        return firstChildInComposedTreeIgnoringUserAgentShadow(node);
     return node.firstChild();
 }
 
@@ -431,7 +420,7 @@
 static inline Node* nextSibling(TextIteratorBehavior options, Node& node)
 {
     if (UNLIKELY(options & TextIteratorTraversesFlatTree))
-        return nextSiblingInFlatTreeIgnoringUserAgentShadow(node);
+        return nextSiblingInComposedTreeIgnoringUserAgentShadow(node);
     return node.nextSibling();
 }
 
@@ -2654,11 +2643,15 @@
     return false;
 }
 
-String plainText(const Range* r, TextIteratorBehavior defaultBehavior, bool isDisplayString)
+String plainText(Position start, Position end, TextIteratorBehavior defaultBehavior, bool isDisplayString)
 {
     // The initial buffer size can be critical for performance: https://bugs.webkit.org/show_bug.cgi?id=81192
     static const unsigned initialCapacity = 1 << 15;
 
+    if (!start.document())
+        return { };
+    auto document = makeRef(*start.document());
+
     unsigned bufferLength = 0;
     StringBuilder builder;
     builder.reserveCapacity(initialCapacity);
@@ -2665,8 +2658,8 @@
     TextIteratorBehavior behavior = defaultBehavior;
     if (!isDisplayString)
         behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsTextsWithoutTranscoding);
-    
-    for (TextIterator it(r, behavior); !it.atEnd(); it.advance()) {
+
+    for (TextIterator it(start, end, behavior); !it.atEnd(); it.advance()) {
         it.appendTextToStringBuilder(builder);
         bufferLength += it.text().length();
     }
@@ -2677,11 +2670,18 @@
     String result = builder.toString();
 
     if (isDisplayString)
-        r->ownerDocument().displayStringModifiedByEncoding(result);
+        document->displayStringModifiedByEncoding(result);
 
     return result;
 }
 
+String plainText(const Range* range, TextIteratorBehavior defaultBehavior, bool isDisplayString)
+{
+    if (!range)
+        return emptyString();
+    return plainText(range->startPosition(), range->endPosition(), defaultBehavior, isDisplayString);
+}
+
 String plainTextReplacingNoBreakSpace(const Range* range, TextIteratorBehavior defaultBehavior, bool isDisplayString)
 {
     return plainText(range, defaultBehavior, isDisplayString).replace(noBreakSpace, ' ');

Modified: trunk/Source/WebCore/editing/TextIterator.h (236784 => 236785)


--- trunk/Source/WebCore/editing/TextIterator.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/TextIterator.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -43,6 +43,8 @@
 class RunResolver;
 }
 
+WEBCORE_EXPORT String plainText(Position start, Position end, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
+
 WEBCORE_EXPORT String plainText(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
 WEBCORE_EXPORT String plainTextReplacingNoBreakSpace(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
 Ref<Range> findPlainText(const Range&, const String&, FindOptions);
@@ -99,6 +101,7 @@
 
 class TextIterator {
 public:
+    explicit TextIterator(Position start, Position end, TextIteratorBehavior = TextIteratorDefaultBehavior);
     WEBCORE_EXPORT explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior);
     WEBCORE_EXPORT ~TextIterator();
 
@@ -118,6 +121,7 @@
     WEBCORE_EXPORT static Ref<Range> subrange(Range& entireRange, int characterOffset, int characterCount);
 
 private:
+    void init();
     void exitNode(Node*);
     bool shouldRepresentNodeOffsetZero();
     bool shouldEmitSpaceBeforeAndAfterNode(Node&);

Modified: trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm (236784 => 236785)


--- trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm	2018-10-03 06:28:07 UTC (rev 236785)
@@ -49,6 +49,7 @@
 #import "Pasteboard.h"
 #import "RenderElement.h"
 #import "RenderStyle.h"
+#import "Settings.h"
 #import "Text.h"
 #import "WebContentReader.h"
 #import "WebCoreNSURLExtras.h"
@@ -75,7 +76,8 @@
 
 String Editor::selectionInHTMLFormat()
 {
-    return serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
+    return serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
+        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
 }
 
 #if ENABLE(ATTACHMENT_ELEMENT)

Modified: trunk/Source/WebCore/editing/gtk/EditorGtk.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/gtk/EditorGtk.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/gtk/EditorGtk.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -43,6 +43,7 @@
 #include "SVGElement.h"
 #include "SVGImageElement.h"
 #include "SelectionData.h"
+#include "Settings.h"
 #include "XLinkNames.h"
 #include "markup.h"
 #include <cairo.h>
@@ -146,7 +147,8 @@
     PasteboardWebContent pasteboardContent;
     pasteboardContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
     pasteboardContent.text = selectedTextForDataTransfer();
-    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
+    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
+        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
     pasteboard.write(pasteboardContent);
 }
 

Modified: trunk/Source/WebCore/editing/markup.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/markup.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/markup.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -37,6 +37,7 @@
 #include "CacheStorageProvider.h"
 #include "ChildListMutationScope.h"
 #include "Comment.h"
+#include "ComposedTreeIterator.h"
 #include "DocumentFragment.h"
 #include "DocumentLoader.h"
 #include "DocumentType.h"
@@ -219,8 +220,8 @@
 public:
     enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
 
-    StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes,
-        ResolveURLs, AnnotateForInterchange, MSOListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr);
+    StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs, SerializeComposedTree,
+        AnnotateForInterchange, MSOListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr);
 
     Node* serializeNodes(const Position& start, const Position& end);
     void wrapWithNode(Node&, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode);
@@ -232,6 +233,13 @@
 
     using MarkupAccumulator::appendString;
 
+    ContainerNode* parentNode(Node& node)
+    {
+        if (UNLIKELY(m_useComposedTree))
+            return node.parentInComposedTree();
+        return node.parentOrShadowHostNode();
+    }
+
 private:
     void appendStyleNodeOpenTag(StringBuilder&, StyleProperties*, Document&, bool isBlock = false);
     const String& styleNodeCloseTag(bool isBlock = false);
@@ -242,6 +250,7 @@
     bool shouldPreserveMSOListStyleForElement(const Element&);
 
     void appendElement(StringBuilder& out, const Element&, bool addDisplayInline, RangeFullySelectsNode);
+    void appendEndElement(StringBuilder& out, const Element&) override;
     void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override;
 
     void appendText(StringBuilder& out, const Text&) override;
@@ -250,6 +259,48 @@
         appendElement(out, element, false, DoesFullySelectNode);
     }
 
+    Node* firstChild(Node& node)
+    {
+        if (UNLIKELY(m_useComposedTree))
+            return firstChildInComposedTreeIgnoringUserAgentShadow(node);
+        return node.firstChild();
+    }
+
+    Node* nextSibling(Node& node)
+    {
+        if (UNLIKELY(m_useComposedTree))
+            return nextSiblingInComposedTreeIgnoringUserAgentShadow(node);
+        return node.nextSibling();
+    }
+    
+    Node* nextSkippingChildren(Node& node)
+    {
+        if (UNLIKELY(m_useComposedTree)) {
+            if (auto* sibling = nextSiblingInComposedTreeIgnoringUserAgentShadow(node))
+                return sibling;
+            for (auto* ancestor = node.parentInComposedTree(); ancestor; ancestor = ancestor->parentInComposedTree()) {
+                if (auto* sibling = nextSiblingInComposedTreeIgnoringUserAgentShadow(*ancestor))
+                    return sibling;
+            }
+            return nullptr;
+        }
+        return NodeTraversal::nextSkippingChildren(node);
+    }
+
+    bool hasChildNodes(Node& node)
+    {
+        if (UNLIKELY(m_useComposedTree))
+            return firstChildInComposedTreeIgnoringUserAgentShadow(node);
+        return node.hasChildNodes();
+    }
+
+    bool isDescendantOf(Node& node, Node& possibleAncestor)
+    {
+        if (UNLIKELY(m_useComposedTree))
+            return node.isDescendantOrShadowDescendantOf(&possibleAncestor);
+        return node.isDescendantOf(&possibleAncestor);
+    }
+
     enum class NodeTraversalMode { EmitString, DoNotEmitString };
     Node* traverseNodesForSerialization(Node* startNode, Node* pastEnd, NodeTraversalMode);
 
@@ -271,6 +322,7 @@
     const AnnotateForInterchange m_annotate;
     RefPtr<Node> m_highestNodeToBeSerialized;
     RefPtr<EditingStyle> m_wrappingStyle;
+    bool m_useComposedTree;
     bool m_needsPositionStyleConversion;
     bool m_needRelativeStyleWrapper { false };
     bool m_needClearingDiv { false };
@@ -278,13 +330,14 @@
     bool m_inMSOList { false };
 };
 
-inline StyledMarkupAccumulator::StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes,
-    ResolveURLs urlsToResolve, AnnotateForInterchange annotate, MSOListMode msoListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized)
+inline StyledMarkupAccumulator::StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs urlsToResolve, SerializeComposedTree serializeComposedTree,
+    AnnotateForInterchange annotate, MSOListMode msoListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized)
     : MarkupAccumulator(nodes, urlsToResolve)
     , m_start(start)
     , m_end(end)
     , m_annotate(annotate)
     , m_highestNodeToBeSerialized(highestNodeToBeSerialized)
+    , m_useComposedTree(serializeComposedTree == SerializeComposedTree::Yes)
     , m_needsPositionStyleConversion(needsPositionStyleConversion)
     , m_shouldPreserveMSOList(msoListMode == MSOListMode::Preserve)
 {
@@ -445,12 +498,16 @@
 void StyledMarkupAccumulator::appendElement(StringBuilder& out, const Element& element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode)
 {
     const bool documentIsHTML = element.document().isHTMLDocument();
-    appendOpenTag(out, element, 0);
+    const bool isSlotElement = is<HTMLSlotElement>(element);
+    if (UNLIKELY(isSlotElement))
+        out.append("<span");
+    else
+        appendOpenTag(out, element, nullptr);
 
     appendCustomAttributes(out, element, nullptr);
 
     const bool shouldAnnotateOrForceInline = element.isHTMLElement() && (shouldAnnotate() || addDisplayInline);
-    bool shouldOverrideStyleAttr = (shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element)) && !shouldPreserveMSOListStyleForElement(element);
+    bool shouldOverrideStyleAttr = (shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element) || isSlotElement) && !shouldPreserveMSOListStyleForElement(element);
     if (element.hasAttributes()) {
         for (const Attribute& attribute : element.attributesIterator()) {
             // We'll handle the style attribute separately, below.
@@ -472,6 +529,9 @@
         } else
             newInlineStyle = EditingStyle::create();
 
+        if (isSlotElement)
+            newInlineStyle->addDisplayContents();
+
         if (is<StyledElement>(element) && downcast<StyledElement>(element).inlineStyle())
             newInlineStyle->overrideWithStyle(*downcast<StyledElement>(element).inlineStyle());
 
@@ -503,13 +563,21 @@
     appendCloseTag(out, element);
 }
 
+void StyledMarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
+{
+    if (UNLIKELY(is<HTMLSlotElement>(element)))
+        out.append("</span>");
+    else
+        MarkupAccumulator::appendEndElement(out, element);
+}
+
 Node* StyledMarkupAccumulator::serializeNodes(const Position& start, const Position& end)
 {
     ASSERT(comparePositions(start, end) <= 0);
     auto startNode = start.firstNode();
     Node* pastEnd = end.computeNodeAfterPosition();
-    if (!pastEnd)
-        pastEnd = NodeTraversal::nextSkippingChildren(*end.containerNode());
+    if (!pastEnd && end.containerNode())
+        pastEnd = nextSkippingChildren(*end.containerNode());
 
     if (!m_highestNodeToBeSerialized) {
         Node* lastClosed = traverseNodesForSerialization(startNode.get(), pastEnd, NodeTraversalMode::DoNotEmitString);
@@ -535,7 +603,8 @@
                 return false;
         }
 
-        if (!node.renderer() && !enclosingElementWithTag(firstPositionInOrBeforeNode(&node), selectTag))
+        bool isDisplayContents = is<Element>(node) && downcast<Element>(node).hasDisplayContents();
+        if (!node.renderer() && !isDisplayContents && !enclosingElementWithTag(firstPositionInOrBeforeNode(&node), selectTag))
             return false;
 
         ++depth;
@@ -561,20 +630,19 @@
 
     Node* lastNode = nullptr;
     Node* next = nullptr;
-    for (Node* n = startNode; n != pastEnd; n = next) {
-        lastNode = n;
+    for (auto* n = startNode; n != pastEnd; lastNode = n, n = next) {
 
         Vector<Node*, 8> exitedAncestors;
         next = nullptr;
-        if (auto* firstChild = n->firstChild())
-            next = firstChild;
-        else if (auto* nextSibling = n->nextSibling())
-            next = nextSibling;
+        if (auto* child = firstChild(*n))
+            next = child;
+        else if (auto* sibling = nextSibling(*n))
+            next = sibling;
         else {
-            for (auto* ancestor = n->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
+            for (auto* ancestor = parentNode(*n); ancestor; ancestor = parentNode(*ancestor)) {
                 exitedAncestors.append(ancestor);
-                if (auto* nextSibling = ancestor->nextSibling()) {
-                    next = nextSibling;
+                if (auto* sibling = nextSibling(*ancestor)) {
+                    next = sibling;
                     break;
                 }
             }
@@ -588,10 +656,10 @@
         if (!enterNode(*n)) {
             next = NodeTraversal::nextSkippingChildren(*n);
             // Don't skip over pastEnd.
-            if (pastEnd && pastEnd->isDescendantOf(*n))
+            if (pastEnd && isDescendantOf(*pastEnd, *n))
                 next = pastEnd;
         } else {
-            if (!n->hasChildNodes())
+            if (!hasChildNodes(*n))
                 exitNode(*n);
         }
 
@@ -601,10 +669,13 @@
             exitNode(*ancestor);
         }
     }
+    
+    ASSERT(lastNode || !depth);
+    if (depth) {
+        for (auto* ancestor = parentNode(pastEnd ? *pastEnd : *lastNode); ancestor && depth; ancestor = parentNode(*ancestor))
+            exitNode(*ancestor);
+    }
 
-    for (auto* ancestor = (pastEnd ? pastEnd : lastNode)->parentNode(); ancestor && depth; ancestor = ancestor->parentNode())
-        exitNode(*ancestor);
-
     return lastClosed;
 }
 
@@ -754,15 +825,27 @@
     return specialCommonAncestor;
 }
 
-static String serializePreservingVisualAppearanceInternal(const Position& start, const Position& end, Vector<Node*>* nodes,
-    AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, ResolveURLs urlsToResolve, MSOListMode msoListMode)
+static RefPtr<Node> commonShadowIncludingAncestor(const Position& a, const Position& b)
 {
+    TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode());
+    if (!commonScope)
+        return nullptr;
+    auto* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode());
+    ASSERT(nodeA);
+    auto* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode());
+    ASSERT(nodeB);
+    return Range::commonAncestorContainer(nodeA, nodeB);
+}
+
+static String serializePreservingVisualAppearanceInternal(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs urlsToResolve, SerializeComposedTree serializeComposedTree,
+    AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, MSOListMode msoListMode)
+{
     static NeverDestroyed<const String> interchangeNewlineString(MAKE_STATIC_STRING_IMPL("<br class=\"" AppleInterchangeNewline "\">"));
 
     if (!comparePositions(start, end))
         return emptyString();
 
-    RefPtr<Node> commonAncestor = Range::commonAncestorContainer(start.containerNode(), end.containerNode());
+    RefPtr<Node> commonAncestor = commonShadowIncludingAncestor(start, end);
     if (!commonAncestor)
         return emptyString();
 
@@ -781,7 +864,7 @@
 
     Node* specialCommonAncestor = highestAncestorToWrapMarkup(start, end, *commonAncestor, annotate);
 
-    StyledMarkupAccumulator accumulator(start, end, nodes, urlsToResolve, annotate, msoListMode, needsPositionStyleConversion, specialCommonAncestor);
+    StyledMarkupAccumulator accumulator(start, end, nodes, urlsToResolve, serializeComposedTree, annotate, msoListMode, needsPositionStyleConversion, specialCommonAncestor);
 
     Position startAdjustedForInterchangeNewline = start;
     if (annotate == AnnotateForInterchange::Yes && needInterchangeNewlineAfter(visibleStart)) {
@@ -799,7 +882,7 @@
 
     if (specialCommonAncestor && lastClosed) {
         // Also include all of the ancestors of lastClosed up to this special ancestor.
-        for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
+        for (ContainerNode* ancestor = accumulator.parentNode(*lastClosed); ancestor; ancestor = accumulator.parentNode(*ancestor)) {
             if (ancestor == fullySelectedRoot && convertBlocksToInlines == ConvertBlocksToInlines::No) {
                 RefPtr<EditingStyle> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(*fullySelectedRoot);
 
@@ -849,13 +932,14 @@
 
 String serializePreservingVisualAppearance(const Range& range, Vector<Node*>* nodes, AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, ResolveURLs urlsToReslve)
 {
-    return serializePreservingVisualAppearanceInternal(range.startPosition(), range.endPosition(), nodes, annotate, convertBlocksToInlines, urlsToReslve, MSOListMode::DoNotPreserve);
+    return serializePreservingVisualAppearanceInternal(range.startPosition(), range.endPosition(), nodes, urlsToReslve, SerializeComposedTree::No,
+        annotate, convertBlocksToInlines, MSOListMode::DoNotPreserve);
 }
 
-String serializePreservingVisualAppearance(const VisibleSelection& selection, ResolveURLs resolveURLs, Vector<Node*>* nodes)
+String serializePreservingVisualAppearance(const VisibleSelection& selection, ResolveURLs resolveURLs, SerializeComposedTree serializeComposedTree, Vector<Node*>* nodes)
 {
-    return serializePreservingVisualAppearanceInternal(selection.start(), selection.end(), nodes,
-        AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, resolveURLs, MSOListMode::DoNotPreserve);
+    return serializePreservingVisualAppearanceInternal(selection.start(), selection.end(), nodes, resolveURLs, serializeComposedTree,
+        AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, MSOListMode::DoNotPreserve);
 }
 
 
@@ -880,8 +964,9 @@
     ASSERT(bodyElement);
     bodyElement->appendChild(fragment.get());
 
+    // SerializeComposedTree::No because there can't be a shadow tree in the pasted fragment.
     auto result = serializePreservingVisualAppearanceInternal(firstPositionInNode(bodyElement.get()), lastPositionInNode(bodyElement.get()), nullptr,
-        AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, ResolveURLs::YesExcludingLocalFileURLsForPrivacy, msoListMode);
+        ResolveURLs::YesExcludingLocalFileURLsForPrivacy, SerializeComposedTree::No, AnnotateForInterchange::Yes, ConvertBlocksToInlines::No,  msoListMode);
 
     if (msoListMode == MSOListMode::Preserve) {
         StringBuilder builder;

Modified: trunk/Source/WebCore/editing/markup.h (236784 => 236785)


--- trunk/Source/WebCore/editing/markup.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/markup.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -70,8 +70,9 @@
 
 enum class ResolveURLs : uint8_t { No, Yes, YesExcludingLocalFileURLsForPrivacy };
 enum class ConvertBlocksToInlines : uint8_t { No, Yes };
+enum class SerializeComposedTree : uint8_t { No, Yes };
 WEBCORE_EXPORT String serializePreservingVisualAppearance(const Range&, Vector<Node*>* = nullptr, AnnotateForInterchange = AnnotateForInterchange::No, ConvertBlocksToInlines = ConvertBlocksToInlines::No, ResolveURLs = ResolveURLs::No);
-String serializePreservingVisualAppearance(const VisibleSelection&, ResolveURLs = ResolveURLs::No, Vector<Node*>* = nullptr);
+String serializePreservingVisualAppearance(const VisibleSelection&, ResolveURLs = ResolveURLs::No, SerializeComposedTree = SerializeComposedTree::No, Vector<Node*>* = nullptr);
 
 enum class SerializedNodes : uint8_t { SubtreeIncludingNode, SubtreesOfChildren };
 enum class SerializationSyntax : uint8_t { HTML, XML };

Modified: trunk/Source/WebCore/editing/wpe/EditorWPE.cpp (236784 => 236785)


--- trunk/Source/WebCore/editing/wpe/EditorWPE.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/editing/wpe/EditorWPE.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -64,7 +64,8 @@
 {
     PasteboardWebContent pasteboardContent;
     pasteboardContent.text = selectedTextForDataTransfer();
-    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
+    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
+        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
     pasteboard.write(pasteboardContent);
 }
 

Modified: trunk/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp (236784 => 236785)


--- trunk/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -554,7 +554,8 @@
     builder.append(documentTypeString(*document));
 
     Vector<Node*> nodeList;
-    builder.append(serializePreservingVisualAppearance(frame->selection().selection(), ResolveURLs::No, &nodeList));
+    auto serializeComposedTree = frame->settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No;
+    builder.append(serializePreservingVisualAppearance(frame->selection().selection(), ResolveURLs::No, serializeComposedTree, &nodeList));
 
     auto archive = create(builder.toString(), *frame, nodeList, nullptr);
     

Modified: trunk/Source/WebCore/page/PageSerializer.cpp (236784 => 236785)


--- trunk/Source/WebCore/page/PageSerializer.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/page/PageSerializer.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -105,7 +105,7 @@
     void appendText(StringBuilder&, const Text&) override;
     void appendElement(StringBuilder&, const Element&, Namespaces*) override;
     void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override;
-    void appendEndTag(const Element&) override;
+    void appendEndElement(StringBuilder&, const Element&) override;
 };
 
 PageSerializer::SerializerMarkupAccumulator::SerializerMarkupAccumulator(PageSerializer& serializer, Document& document, Vector<Node*>* nodes)
@@ -158,10 +158,10 @@
     appendAttribute(out, element, Attribute(frameOwnerURLAttributeName(frameOwner), url.string()), namespaces);
 }
 
-void PageSerializer::SerializerMarkupAccumulator::appendEndTag(const Element& element)
+void PageSerializer::SerializerMarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
 {
     if (!shouldIgnoreElement(element))
-        MarkupAccumulator::appendEndTag(element);
+        MarkupAccumulator::appendEndElement(out, element);
 }
 
 PageSerializer::PageSerializer(Vector<PageSerializer::Resource>& resources)

Modified: trunk/Source/WebCore/testing/Internals.cpp (236784 => 236785)


--- trunk/Source/WebCore/testing/Internals.cpp	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/testing/Internals.cpp	2018-10-03 06:28:07 UTC (rev 236785)
@@ -3449,6 +3449,13 @@
     return DOMRect::create(document->frame()->selection().selectionBounds());
 }
 
+void Internals::setSelectionWithoutValidation(Ref<Node> baseNode, unsigned baseOffset, RefPtr<Node> extentNode, unsigned extentOffset)
+{
+    contextDocument()->frame()->selection().moveTo(
+        VisiblePosition { createLegacyEditingPosition(baseNode.ptr(), baseOffset) },
+        VisiblePosition { createLegacyEditingPosition(extentNode.get(), extentOffset) });
+}
+
 ExceptionOr<bool> Internals::isPluginUnavailabilityIndicatorObscured(Element& element)
 {
     if (!is<HTMLPlugInElement>(element))

Modified: trunk/Source/WebCore/testing/Internals.h (236784 => 236785)


--- trunk/Source/WebCore/testing/Internals.h	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/testing/Internals.h	2018-10-03 06:28:07 UTC (rev 236785)
@@ -534,6 +534,7 @@
 #endif
 
     ExceptionOr<Ref<DOMRect>> selectionBounds();
+    void setSelectionWithoutValidation(Ref<Node> baseNode, unsigned baseOffset, RefPtr<Node> extentNode, unsigned extentOffset);
 
     ExceptionOr<bool> isPluginUnavailabilityIndicatorObscured(Element&);
     ExceptionOr<String> unavailablePluginReplacementText(Element&);

Modified: trunk/Source/WebCore/testing/Internals.idl (236784 => 236785)


--- trunk/Source/WebCore/testing/Internals.idl	2018-10-03 01:59:04 UTC (rev 236784)
+++ trunk/Source/WebCore/testing/Internals.idl	2018-10-03 06:28:07 UTC (rev 236785)
@@ -541,6 +541,7 @@
     boolean isPluginSnapshotted(Element element);
 
     [MayThrowException] DOMRect selectionBounds();
+    void setSelectionWithoutValidation(Node baseNode, unsigned long baseOffset, Node? extentNode, unsigned long extentOffset);
 
     [Conditional=MEDIA_SOURCE] void initializeMockMediaSource();
     [Conditional=MEDIA_SOURCE] sequence<DOMString> bufferedSamplesForTrackID(SourceBuffer buffer, DOMString trackID);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to