Changeset 236785 in webkit


Ignore:
Timestamp:
Oct 2, 2018 11:28:07 PM (6 years ago)
Author:
rniwa@webkit.org
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.
Location:
trunk
Files:
19 added
21 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r236781 r236785  
     12018-10-02  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Copying content with shadow DOM doesn't copy any contents
     4        https://bugs.webkit.org/show_bug.cgi?id=157443
     5
     6        Reviewed by Wenson Hsieh.
     7
     8        Added tests for copying and pasting across shadow boundaries with HTML and plain text.
     9
     10        * editing/pasteboard/copy-paste-across-shadow-boundaries-1-expected.txt: Added.
     11        * editing/pasteboard/copy-paste-across-shadow-boundaries-1.html: Added.
     12        * editing/pasteboard/copy-paste-across-shadow-boundaries-2-expected.txt: Added.
     13        * editing/pasteboard/copy-paste-across-shadow-boundaries-2.html: Added.
     14        * editing/pasteboard/copy-paste-across-shadow-boundaries-3-expected.txt: Added.
     15        * editing/pasteboard/copy-paste-across-shadow-boundaries-3.html: Added.
     16        * editing/pasteboard/copy-paste-across-shadow-boundaries-4-expected.txt: Added.
     17        * editing/pasteboard/copy-paste-across-shadow-boundaries-4.html: Added.
     18        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1-expected.txt: Added.
     19        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html: Added.
     20        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2-expected.txt: Added.
     21        * editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html: Added.
     22        * editing/pasteboard/copy-paste-with-shadow-content-expected.txt: Added.
     23        * editing/pasteboard/copy-paste-with-shadow-content.html: Added.
     24
    1252018-10-01  Ryosuke Niwa  <rniwa@webkit.org>
    226
  • trunk/Source/WebCore/ChangeLog

    r236784 r236785  
     12018-10-02  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Copying content with shadow DOM doesn't copy any contents
     4        https://bugs.webkit.org/show_bug.cgi?id=157443
     5
     6        Reviewed by Wenson Hsieh.
     7
     8        This patch adds the support for copying and pasting content across shadow boundaries in HTML and plain text,
     9        which is enabled whenever selection across shadow boundaries is enabled.
     10
     11        To do this, TextIterator now has a constructor which takes two Positions, and the node traversal code in
     12        StyledMarkupAccumulator has been abstracted via helper functions as done for TextIterator.
     13
     14        When serializing a HTMl slot element, serialize it as a span with "display: contents" to make sure when
     15        the content is pasted into a shadow tree, it wouldn't affect the slot assignment of the shadow tree.
     16
     17        Tests: editing/pasteboard/copy-paste-across-shadow-boundaries-1.html
     18               editing/pasteboard/copy-paste-across-shadow-boundaries-2.html
     19               editing/pasteboard/copy-paste-across-shadow-boundaries-3.html
     20               editing/pasteboard/copy-paste-across-shadow-boundaries-4.html
     21               editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-1.html
     22               editing/pasteboard/copy-paste-across-shadow-boundaries-with-style-2.html
     23               editing/pasteboard/copy-paste-with-shadow-content.html
     24
     25        * dom/ComposedTreeIterator.h:
     26        (WebCore::assignedSlotIgnoringUserAgentShadow): Moved from TextIterator.cpp.
     27        (WebCore::shadowRootIgnoringUserAgentShadow): Ditto.
     28        (WebCore::firstChildInComposedTreeIgnoringUserAgentShadow): Ditto.
     29        (WebCore::nextSiblingInComposedTreeIgnoringUserAgentShadow): Ditto.
     30        * dom/Position.h:
     31        (WebCore::Position::treeScope const): Added.
     32        * editing/EditingStyle.cpp:
     33        (WebCore::EditingStyle::addDisplayContents): Added.
     34        * editing/EditingStyle.h:
     35        * editing/Editor.cpp:
     36        (WebCore::Editor::selectedText const): Use the new behavior when selectionAcrossShadowBoundariesEnabled is set.
     37        (WebCore::Editor::selectedTextForDataTransfer const): Ditto.
     38        * editing/MarkupAccumulator.cpp:
     39        (WebCore::MarkupAccumulator::appendEndElement): Renamed from appendEndTag. Now takes StringBuilder.
     40        * editing/MarkupAccumulator.h:
     41        (WebCore::MarkupAccumulator::appendEndTag):
     42        * editing/TextIterator.cpp:
     43        (WebCore::TextIterator::TextIterator): Added a new variant which takes two positions.
     44        (WebCore::TextIterator::init):
     45        (WebCore::firstChild):
     46        (WebCore::nextSibling):
     47        (WebCore::plainText): Ditto.
     48        * editing/TextIterator.h:
     49        * editing/cocoa/EditorCocoa.mm:
     50        (WebCore::Editor::selectionInHTMLFormat): Use the new behavior if selectionAcrossShadowBoundariesEnabled is set.
     51        * editing/gtk/EditorGtk.cpp:
     52        (WebCore::Editor::writeSelectionToPasteboard): Ditto.
     53        * editing/markup.cpp:
     54        (WebCore::StyledMarkupAccumulator::parentNode): Added.
     55        (WebCore::StyledMarkupAccumulator::firstChild): Added.
     56        (WebCore::StyledMarkupAccumulator::nextSibling): Added.
     57        (WebCore::StyledMarkupAccumulator::nextSkippingChildren): Added.
     58        (WebCore::StyledMarkupAccumulator::hasChildNodes): Added.
     59        (WebCore::StyledMarkupAccumulator::isDescendantOf): Added.
     60        (WebCore::StyledMarkupAccumulator::StyledMarkupAccumulator):
     61        (WebCore::StyledMarkupAccumulator::appendElement): Serialize a slot element as a span with display: contents.
     62        (WebCore::StyledMarkupAccumulator::appendEndElement): Added. Ditto.
     63        (WebCore::StyledMarkupAccumulator::serializeNodes):
     64        (WebCore::StyledMarkupAccumulator::traverseNodesForSerialization): Use the newly added helper functions to
     65        traverse the composed tree when m_useComposedTree is set.
     66        (WebCore::commonShadowIncludingAncestor): Added.
     67        (WebCore::serializePreservingVisualAppearanceInternal): Added SerializeComposedTree as an argument. Also use
     68        StyledMarkupAccumulator::parentNode to serialize special common ancestors; e.g. to preserve b, i, etc...
     69        (WebCore::serializePreservingVisualAppearance): Ditto to the variant which takes VisibleSelection.
     70        (WebCore::sanitizedMarkupForFragmentInDocument):
     71        * editing/markup.h:
     72        * editing/wpe/EditorWPE.cpp:
     73        (WebCore::Editor::writeSelectionToPasteboard):
     74        * loader/archive/cf/LegacyWebArchive.cpp:
     75        (WebCore::LegacyWebArchive::createFromSelection):
     76        * page/PageSerializer.cpp:
     77        (WebCore::PageSerializer::SerializerMarkupAccumulator::appendEndElement):
     78        * testing/Internals.cpp:
     79        (WebCore::Internals::setSelectionWithoutValidation): Added. A helper function to create a selection across
     80        shadow boundaries for testing purposes.
     81        * testing/Internals.h:
     82        * testing/Internals.idl:
     83
    1842018-10-02  Chris Dumez  <cdumez@apple.com>
    285
  • trunk/Source/WebCore/dom/ComposedTreeIterator.h

    r208179 r236785  
    2727
    2828#include "ElementAndTextDescendantIterator.h"
     29#include "HTMLSlotElement.h"
    2930#include "ShadowRoot.h"
    3031
     
    200201WEBCORE_EXPORT String composedTreeAsText(ContainerNode& root, ComposedTreeAsTextMode = ComposedTreeAsTextMode::Normal);
    201202
     203
     204// Helper functions for walking the composed tree.
     205// FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work.
     206
     207inline HTMLSlotElement* assignedSlotIgnoringUserAgentShadow(Node& node)
     208{
     209    auto* slot = node.assignedSlot();
     210    if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent)
     211        return nullptr;
     212    return slot;
     213}
     214
     215inline ShadowRoot* shadowRootIgnoringUserAgentShadow(Node& node)
     216{
     217    auto* shadowRoot = node.shadowRoot();
     218    if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent)
     219        return nullptr;
     220    return shadowRoot;
     221}
     222
     223inline Node* firstChildInComposedTreeIgnoringUserAgentShadow(Node& node)
     224{
     225    if (auto* shadowRoot = shadowRootIgnoringUserAgentShadow(node))
     226        return shadowRoot->firstChild();
     227    if (is<HTMLSlotElement>(node)) {
     228        if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes())
     229            return assignedNodes->at(0);
     230    }
     231    return node.firstChild();
     232}
     233
     234inline Node* nextSiblingInComposedTreeIgnoringUserAgentShadow(Node& node)
     235{
     236    if (auto* slot = assignedSlotIgnoringUserAgentShadow(node)) {
     237        auto* assignedNodes = slot->assignedNodes();
     238        ASSERT(assignedNodes);
     239        auto nodeIndex = assignedNodes->find(&node);
     240        ASSERT(nodeIndex != notFound);
     241        if (assignedNodes->size() > nodeIndex + 1)
     242            return assignedNodes->at(nodeIndex + 1);
     243        return nullptr;
     244    }
     245    return node.nextSibling();
     246}
     247
    202248} // namespace WebCore
  • trunk/Source/WebCore/dom/Position.h

    r236649 r236785  
    118118
    119119    Document* document() const { return m_anchorNode ? &m_anchorNode->document() : nullptr; }
     120    TreeScope* treeScope() const { return m_anchorNode ? &m_anchorNode->treeScope() : nullptr; }
    120121    Element* rootEditableElement() const
    121122    {
  • trunk/Source/WebCore/editing/EditingStyle.cpp

    r235914 r236785  
    13701370}
    13711371
     1372void EditingStyle::addDisplayContents()
     1373{
     1374    if (!m_mutableStyle)
     1375        m_mutableStyle = MutableStyleProperties::create();
     1376    m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueContents);
     1377}
     1378
    13721379bool EditingStyle::convertPositionStyle()
    13731380{
  • trunk/Source/WebCore/editing/EditingStyle.h

    r234116 r236785  
    150150    void removePropertiesInElementDefaultStyle(Element&);
    151151    void forceInline();
     152    void addDisplayContents();
    152153    bool convertPositionStyle();
    153154    bool isFloating();
  • trunk/Source/WebCore/editing/Editor.cpp

    r236634 r236785  
    31833183String Editor::selectedText() const
    31843184{
    3185     return selectedText(TextIteratorDefaultBehavior);
     3185    TextIteratorBehavior behavior = TextIteratorDefaultBehavior;
     3186    if (m_frame.settings().selectionAcrossShadowBoundariesEnabled())
     3187        behavior |= TextIteratorTraversesFlatTree;
     3188    return selectedText(behavior);
    31863189}
    31873190
    31883191String Editor::selectedTextForDataTransfer() const
    31893192{
    3190     return selectedText(TextIteratorEmitsImageAltText);
     3193    TextIteratorBehavior behavior = TextIteratorEmitsImageAltText;
     3194    if (m_frame.settings().selectionAcrossShadowBoundariesEnabled())
     3195        behavior |= TextIteratorTraversesFlatTree;
     3196    return selectedText(behavior);
    31913197}
    31923198
     
    31943200{
    31953201    // We remove '\0' characters because they are not visibly rendered to the user.
    3196     return plainText(m_frame.selection().toNormalizedRange().get(), behavior).replaceWithLiteral('\0', "");
     3202    auto& selection = m_frame.selection().selection();
     3203    return plainText(selection.start(), selection.end(), behavior).replaceWithLiteral('\0', "");
    31973204}
    31983205
  • trunk/Source/WebCore/editing/MarkupAccumulator.cpp

    r236649 r236785  
    193193}
    194194
    195 void MarkupAccumulator::appendEndTag(const Element& element)
    196 {
    197     appendEndMarkup(m_markup, element);
     195void MarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
     196{
     197    appendEndMarkup(out, element);
    198198}
    199199
  • trunk/Source/WebCore/editing/MarkupAccumulator.h

    r236649 r236785  
    7777    {
    7878        if (is<Element>(node))
    79             appendEndTag(downcast<Element>(node));
     79            appendEndElement(m_markup, downcast<Element>(node));
    8080    }
    8181
    82     virtual void appendEndTag(const Element&);
     82    virtual void appendEndElement(StringBuilder&, const Element&);
    8383    virtual void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*);
    8484    virtual void appendText(StringBuilder&, const Text&);
  • trunk/Source/WebCore/editing/TextIterator.cpp

    r236607 r236785  
    2828#include "TextIterator.h"
    2929
     30#include "ComposedTreeIterator.h"
    3031#include "Document.h"
    3132#include "Editing.h"
     
    340341// --------
    341342
     343
     344TextIterator::TextIterator(Position start, Position end, TextIteratorBehavior behavior)
     345    : m_behavior(behavior)
     346{
     347    if (start.isNull() || end.isNull())
     348        return;
     349    ASSERT(comparePositions(start, end) <= 0);
     350
     351    RELEASE_ASSERT(behavior & TextIteratorTraversesFlatTree || start.treeScope() == end.treeScope());
     352
     353    start.document()->updateLayoutIgnorePendingStylesheets();
     354
     355    // FIXME: Use Position / PositionIterator instead to avoid offset computation.
     356    m_startContainer = start.containerNode();
     357    m_startOffset = start.computeOffsetInContainerNode();
     358
     359    m_endContainer = end.containerNode();
     360    m_endOffset = end.computeOffsetInContainerNode();
     361
     362    m_node = start.firstNode().get();
     363    if (!m_node)
     364        return;
     365
     366    init();
     367}
     368
    342369TextIterator::TextIterator(const Range* range, TextIteratorBehavior behavior)
    343370    : m_behavior(behavior)
    344371{
    345     // FIXME: Only m_positionNode above needs to be initialized if range is null.
    346372    if (!range)
    347373        return;
     
    359385    m_endOffset = range->endOffset();
    360386
    361     // Set up the current node for processing.
    362387    m_node = range->firstNode();
    363388    if (!m_node)
    364389        return;
    365390
     391    init();
     392}
     393
     394void TextIterator::init()
     395{
    366396    if (isClippedByFrameAncestor(m_node->document(), m_behavior))
    367397        return;
     
    380410TextIterator::~TextIterator() = default;
    381411
    382 static HTMLSlotElement* assignedAuthorSlot(Node& node)
    383 {
    384     auto* slot = node.assignedSlot();
    385     if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent)
    386         return nullptr;
    387     return slot;
    388 }
    389 
    390 static ShadowRoot* authorShadowRoot(Node& node)
    391 {
    392     auto* shadowRoot = node.shadowRoot();
    393     if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent)
    394         return nullptr;
    395     return shadowRoot;
    396 }
    397 
    398412// FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work.
    399 static inline Node* firstChildInFlatTreeIgnoringUserAgentShadow(Node& node)
    400 {
    401     if (auto* shadowRoot = authorShadowRoot(node))
    402         return shadowRoot->firstChild();
    403     if (is<HTMLSlotElement>(node)) {
    404         if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes())
    405             return assignedNodes->at(0);
    406     }
     413static inline Node* firstChild(TextIteratorBehavior options, Node& node)
     414{
     415    if (UNLIKELY(options & TextIteratorTraversesFlatTree))
     416        return firstChildInComposedTreeIgnoringUserAgentShadow(node);
    407417    return node.firstChild();
    408418}
    409419
    410 static inline Node* nextSiblingInFlatTreeIgnoringUserAgentShadow(Node& node)
    411 {
    412     if (auto* slot = assignedAuthorSlot(node)) {
    413         auto* assignedNodes = slot->assignedNodes();
    414         ASSERT(assignedNodes);
    415         auto nodeIndex = assignedNodes->find(&node);
    416         ASSERT(nodeIndex != notFound);
    417         if (assignedNodes->size() > nodeIndex + 1)
    418             return assignedNodes->at(nodeIndex + 1);
    419         return nullptr;
    420     }
    421     return node.nextSibling();
    422 }
    423 
    424 static inline Node* firstChild(TextIteratorBehavior options, Node& node)
     420static inline Node* nextSibling(TextIteratorBehavior options, Node& node)
    425421{
    426422    if (UNLIKELY(options & TextIteratorTraversesFlatTree))
    427         return firstChildInFlatTreeIgnoringUserAgentShadow(node);
    428     return node.firstChild();
    429 }
    430 
    431 static inline Node* nextSibling(TextIteratorBehavior options, Node& node)
    432 {
    433     if (UNLIKELY(options & TextIteratorTraversesFlatTree))
    434         return nextSiblingInFlatTreeIgnoringUserAgentShadow(node);
     423        return nextSiblingInComposedTreeIgnoringUserAgentShadow(node);
    435424    return node.nextSibling();
    436425}
     
    26552644}
    26562645
    2657 String plainText(const Range* r, TextIteratorBehavior defaultBehavior, bool isDisplayString)
     2646String plainText(Position start, Position end, TextIteratorBehavior defaultBehavior, bool isDisplayString)
    26582647{
    26592648    // The initial buffer size can be critical for performance: https://bugs.webkit.org/show_bug.cgi?id=81192
    26602649    static const unsigned initialCapacity = 1 << 15;
     2650
     2651    if (!start.document())
     2652        return { };
     2653    auto document = makeRef(*start.document());
    26612654
    26622655    unsigned bufferLength = 0;
     
    26662659    if (!isDisplayString)
    26672660        behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsTextsWithoutTranscoding);
    2668    
    2669     for (TextIterator it(r, behavior); !it.atEnd(); it.advance()) {
     2661
     2662    for (TextIterator it(start, end, behavior); !it.atEnd(); it.advance()) {
    26702663        it.appendTextToStringBuilder(builder);
    26712664        bufferLength += it.text().length();
     
    26782671
    26792672    if (isDisplayString)
    2680         r->ownerDocument().displayStringModifiedByEncoding(result);
     2673        document->displayStringModifiedByEncoding(result);
    26812674
    26822675    return result;
     2676}
     2677
     2678String plainText(const Range* range, TextIteratorBehavior defaultBehavior, bool isDisplayString)
     2679{
     2680    if (!range)
     2681        return emptyString();
     2682    return plainText(range->startPosition(), range->endPosition(), defaultBehavior, isDisplayString);
    26832683}
    26842684
  • trunk/Source/WebCore/editing/TextIterator.h

    r225117 r236785  
    4444}
    4545
     46WEBCORE_EXPORT String plainText(Position start, Position end, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
     47
    4648WEBCORE_EXPORT String plainText(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
    4749WEBCORE_EXPORT String plainTextReplacingNoBreakSpace(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
     
    100102class TextIterator {
    101103public:
     104    explicit TextIterator(Position start, Position end, TextIteratorBehavior = TextIteratorDefaultBehavior);
    102105    WEBCORE_EXPORT explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior);
    103106    WEBCORE_EXPORT ~TextIterator();
     
    119122
    120123private:
     124    void init();
    121125    void exitNode(Node*);
    122126    bool shouldRepresentNodeOffsetZero();
  • trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm

    r236707 r236785  
    5050#import "RenderElement.h"
    5151#import "RenderStyle.h"
     52#import "Settings.h"
    5253#import "Text.h"
    5354#import "WebContentReader.h"
     
    7677String Editor::selectionInHTMLFormat()
    7778{
    78     return serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
     79    return serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
     80        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
    7981}
    8082
  • trunk/Source/WebCore/editing/gtk/EditorGtk.cpp

    r236707 r236785  
    4444#include "SVGImageElement.h"
    4545#include "SelectionData.h"
     46#include "Settings.h"
    4647#include "XLinkNames.h"
    4748#include "markup.h"
     
    147148    pasteboardContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
    148149    pasteboardContent.text = selectedTextForDataTransfer();
    149     pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
     150    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
     151        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
    150152    pasteboard.write(pasteboardContent);
    151153}
  • trunk/Source/WebCore/editing/markup.cpp

    r236762 r236785  
    3838#include "ChildListMutationScope.h"
    3939#include "Comment.h"
     40#include "ComposedTreeIterator.h"
    4041#include "DocumentFragment.h"
    4142#include "DocumentLoader.h"
     
    220221    enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
    221222
    222     StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes,
    223         ResolveURLs, AnnotateForInterchange, MSOListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr);
     223    StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs, SerializeComposedTree,
     224        AnnotateForInterchange, MSOListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr);
    224225
    225226    Node* serializeNodes(const Position& start, const Position& end);
     
    233234    using MarkupAccumulator::appendString;
    234235
     236    ContainerNode* parentNode(Node& node)
     237    {
     238        if (UNLIKELY(m_useComposedTree))
     239            return node.parentInComposedTree();
     240        return node.parentOrShadowHostNode();
     241    }
     242
    235243private:
    236244    void appendStyleNodeOpenTag(StringBuilder&, StyleProperties*, Document&, bool isBlock = false);
     
    243251
    244252    void appendElement(StringBuilder& out, const Element&, bool addDisplayInline, RangeFullySelectsNode);
     253    void appendEndElement(StringBuilder& out, const Element&) override;
    245254    void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override;
    246255
     
    249258    {
    250259        appendElement(out, element, false, DoesFullySelectNode);
     260    }
     261
     262    Node* firstChild(Node& node)
     263    {
     264        if (UNLIKELY(m_useComposedTree))
     265            return firstChildInComposedTreeIgnoringUserAgentShadow(node);
     266        return node.firstChild();
     267    }
     268
     269    Node* nextSibling(Node& node)
     270    {
     271        if (UNLIKELY(m_useComposedTree))
     272            return nextSiblingInComposedTreeIgnoringUserAgentShadow(node);
     273        return node.nextSibling();
     274    }
     275   
     276    Node* nextSkippingChildren(Node& node)
     277    {
     278        if (UNLIKELY(m_useComposedTree)) {
     279            if (auto* sibling = nextSiblingInComposedTreeIgnoringUserAgentShadow(node))
     280                return sibling;
     281            for (auto* ancestor = node.parentInComposedTree(); ancestor; ancestor = ancestor->parentInComposedTree()) {
     282                if (auto* sibling = nextSiblingInComposedTreeIgnoringUserAgentShadow(*ancestor))
     283                    return sibling;
     284            }
     285            return nullptr;
     286        }
     287        return NodeTraversal::nextSkippingChildren(node);
     288    }
     289
     290    bool hasChildNodes(Node& node)
     291    {
     292        if (UNLIKELY(m_useComposedTree))
     293            return firstChildInComposedTreeIgnoringUserAgentShadow(node);
     294        return node.hasChildNodes();
     295    }
     296
     297    bool isDescendantOf(Node& node, Node& possibleAncestor)
     298    {
     299        if (UNLIKELY(m_useComposedTree))
     300            return node.isDescendantOrShadowDescendantOf(&possibleAncestor);
     301        return node.isDescendantOf(&possibleAncestor);
    251302    }
    252303
     
    272323    RefPtr<Node> m_highestNodeToBeSerialized;
    273324    RefPtr<EditingStyle> m_wrappingStyle;
     325    bool m_useComposedTree;
    274326    bool m_needsPositionStyleConversion;
    275327    bool m_needRelativeStyleWrapper { false };
     
    279331};
    280332
    281 inline StyledMarkupAccumulator::StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes,
    282     ResolveURLs urlsToResolve, AnnotateForInterchange annotate, MSOListMode msoListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized)
     333inline StyledMarkupAccumulator::StyledMarkupAccumulator(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs urlsToResolve, SerializeComposedTree serializeComposedTree,
     334    AnnotateForInterchange annotate, MSOListMode msoListMode, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized)
    283335    : MarkupAccumulator(nodes, urlsToResolve)
    284336    , m_start(start)
     
    286338    , m_annotate(annotate)
    287339    , m_highestNodeToBeSerialized(highestNodeToBeSerialized)
     340    , m_useComposedTree(serializeComposedTree == SerializeComposedTree::Yes)
    288341    , m_needsPositionStyleConversion(needsPositionStyleConversion)
    289342    , m_shouldPreserveMSOList(msoListMode == MSOListMode::Preserve)
     
    446499{
    447500    const bool documentIsHTML = element.document().isHTMLDocument();
    448     appendOpenTag(out, element, 0);
     501    const bool isSlotElement = is<HTMLSlotElement>(element);
     502    if (UNLIKELY(isSlotElement))
     503        out.append("<span");
     504    else
     505        appendOpenTag(out, element, nullptr);
    449506
    450507    appendCustomAttributes(out, element, nullptr);
    451508
    452509    const bool shouldAnnotateOrForceInline = element.isHTMLElement() && (shouldAnnotate() || addDisplayInline);
    453     bool shouldOverrideStyleAttr = (shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element)) && !shouldPreserveMSOListStyleForElement(element);
     510    bool shouldOverrideStyleAttr = (shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element) || isSlotElement) && !shouldPreserveMSOListStyleForElement(element);
    454511    if (element.hasAttributes()) {
    455512        for (const Attribute& attribute : element.attributesIterator()) {
     
    473530            newInlineStyle = EditingStyle::create();
    474531
     532        if (isSlotElement)
     533            newInlineStyle->addDisplayContents();
     534
    475535        if (is<StyledElement>(element) && downcast<StyledElement>(element).inlineStyle())
    476536            newInlineStyle->overrideWithStyle(*downcast<StyledElement>(element).inlineStyle());
     
    504564}
    505565
     566void StyledMarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
     567{
     568    if (UNLIKELY(is<HTMLSlotElement>(element)))
     569        out.append("</span>");
     570    else
     571        MarkupAccumulator::appendEndElement(out, element);
     572}
     573
    506574Node* StyledMarkupAccumulator::serializeNodes(const Position& start, const Position& end)
    507575{
     
    509577    auto startNode = start.firstNode();
    510578    Node* pastEnd = end.computeNodeAfterPosition();
    511     if (!pastEnd)
    512         pastEnd = NodeTraversal::nextSkippingChildren(*end.containerNode());
     579    if (!pastEnd && end.containerNode())
     580        pastEnd = nextSkippingChildren(*end.containerNode());
    513581
    514582    if (!m_highestNodeToBeSerialized) {
     
    536604        }
    537605
    538         if (!node.renderer() && !enclosingElementWithTag(firstPositionInOrBeforeNode(&node), selectTag))
     606        bool isDisplayContents = is<Element>(node) && downcast<Element>(node).hasDisplayContents();
     607        if (!node.renderer() && !isDisplayContents && !enclosingElementWithTag(firstPositionInOrBeforeNode(&node), selectTag))
    539608            return false;
    540609
     
    562631    Node* lastNode = nullptr;
    563632    Node* next = nullptr;
    564     for (Node* n = startNode; n != pastEnd; n = next) {
    565         lastNode = n;
     633    for (auto* n = startNode; n != pastEnd; lastNode = n, n = next) {
    566634
    567635        Vector<Node*, 8> exitedAncestors;
    568636        next = nullptr;
    569         if (auto* firstChild = n->firstChild())
    570             next = firstChild;
    571         else if (auto* nextSibling = n->nextSibling())
    572             next = nextSibling;
     637        if (auto* child = firstChild(*n))
     638            next = child;
     639        else if (auto* sibling = nextSibling(*n))
     640            next = sibling;
    573641        else {
    574             for (auto* ancestor = n->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
     642            for (auto* ancestor = parentNode(*n); ancestor; ancestor = parentNode(*ancestor)) {
    575643                exitedAncestors.append(ancestor);
    576                 if (auto* nextSibling = ancestor->nextSibling()) {
    577                     next = nextSibling;
     644                if (auto* sibling = nextSibling(*ancestor)) {
     645                    next = sibling;
    578646                    break;
    579647                }
     
    589657            next = NodeTraversal::nextSkippingChildren(*n);
    590658            // Don't skip over pastEnd.
    591             if (pastEnd && pastEnd->isDescendantOf(*n))
     659            if (pastEnd && isDescendantOf(*pastEnd, *n))
    592660                next = pastEnd;
    593661        } else {
    594             if (!n->hasChildNodes())
     662            if (!hasChildNodes(*n))
    595663                exitNode(*n);
    596664        }
     
    602670        }
    603671    }
    604 
    605     for (auto* ancestor = (pastEnd ? pastEnd : lastNode)->parentNode(); ancestor && depth; ancestor = ancestor->parentNode())
    606         exitNode(*ancestor);
     672   
     673    ASSERT(lastNode || !depth);
     674    if (depth) {
     675        for (auto* ancestor = parentNode(pastEnd ? *pastEnd : *lastNode); ancestor && depth; ancestor = parentNode(*ancestor))
     676            exitNode(*ancestor);
     677    }
    607678
    608679    return lastClosed;
     
    755826}
    756827
    757 static String serializePreservingVisualAppearanceInternal(const Position& start, const Position& end, Vector<Node*>* nodes,
    758     AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, ResolveURLs urlsToResolve, MSOListMode msoListMode)
     828static RefPtr<Node> commonShadowIncludingAncestor(const Position& a, const Position& b)
     829{
     830    TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode());
     831    if (!commonScope)
     832        return nullptr;
     833    auto* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode());
     834    ASSERT(nodeA);
     835    auto* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode());
     836    ASSERT(nodeB);
     837    return Range::commonAncestorContainer(nodeA, nodeB);
     838}
     839
     840static String serializePreservingVisualAppearanceInternal(const Position& start, const Position& end, Vector<Node*>* nodes, ResolveURLs urlsToResolve, SerializeComposedTree serializeComposedTree,
     841    AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, MSOListMode msoListMode)
    759842{
    760843    static NeverDestroyed<const String> interchangeNewlineString(MAKE_STATIC_STRING_IMPL("<br class=\"" AppleInterchangeNewline "\">"));
     
    763846        return emptyString();
    764847
    765     RefPtr<Node> commonAncestor = Range::commonAncestorContainer(start.containerNode(), end.containerNode());
     848    RefPtr<Node> commonAncestor = commonShadowIncludingAncestor(start, end);
    766849    if (!commonAncestor)
    767850        return emptyString();
     
    782865    Node* specialCommonAncestor = highestAncestorToWrapMarkup(start, end, *commonAncestor, annotate);
    783866
    784     StyledMarkupAccumulator accumulator(start, end, nodes, urlsToResolve, annotate, msoListMode, needsPositionStyleConversion, specialCommonAncestor);
     867    StyledMarkupAccumulator accumulator(start, end, nodes, urlsToResolve, serializeComposedTree, annotate, msoListMode, needsPositionStyleConversion, specialCommonAncestor);
    785868
    786869    Position startAdjustedForInterchangeNewline = start;
     
    800883    if (specialCommonAncestor && lastClosed) {
    801884        // Also include all of the ancestors of lastClosed up to this special ancestor.
    802         for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
     885        for (ContainerNode* ancestor = accumulator.parentNode(*lastClosed); ancestor; ancestor = accumulator.parentNode(*ancestor)) {
    803886            if (ancestor == fullySelectedRoot && convertBlocksToInlines == ConvertBlocksToInlines::No) {
    804887                RefPtr<EditingStyle> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(*fullySelectedRoot);
     
    850933String serializePreservingVisualAppearance(const Range& range, Vector<Node*>* nodes, AnnotateForInterchange annotate, ConvertBlocksToInlines convertBlocksToInlines, ResolveURLs urlsToReslve)
    851934{
    852     return serializePreservingVisualAppearanceInternal(range.startPosition(), range.endPosition(), nodes, annotate, convertBlocksToInlines, urlsToReslve, MSOListMode::DoNotPreserve);
    853 }
    854 
    855 String serializePreservingVisualAppearance(const VisibleSelection& selection, ResolveURLs resolveURLs, Vector<Node*>* nodes)
    856 {
    857     return serializePreservingVisualAppearanceInternal(selection.start(), selection.end(), nodes,
    858         AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, resolveURLs, MSOListMode::DoNotPreserve);
     935    return serializePreservingVisualAppearanceInternal(range.startPosition(), range.endPosition(), nodes, urlsToReslve, SerializeComposedTree::No,
     936        annotate, convertBlocksToInlines, MSOListMode::DoNotPreserve);
     937}
     938
     939String serializePreservingVisualAppearance(const VisibleSelection& selection, ResolveURLs resolveURLs, SerializeComposedTree serializeComposedTree, Vector<Node*>* nodes)
     940{
     941    return serializePreservingVisualAppearanceInternal(selection.start(), selection.end(), nodes, resolveURLs, serializeComposedTree,
     942        AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, MSOListMode::DoNotPreserve);
    859943}
    860944
     
    881965    bodyElement->appendChild(fragment.get());
    882966
     967    // SerializeComposedTree::No because there can't be a shadow tree in the pasted fragment.
    883968    auto result = serializePreservingVisualAppearanceInternal(firstPositionInNode(bodyElement.get()), lastPositionInNode(bodyElement.get()), nullptr,
    884         AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, ResolveURLs::YesExcludingLocalFileURLsForPrivacy, msoListMode);
     969        ResolveURLs::YesExcludingLocalFileURLsForPrivacy, SerializeComposedTree::No, AnnotateForInterchange::Yes, ConvertBlocksToInlines::No, msoListMode);
    885970
    886971    if (msoListMode == MSOListMode::Preserve) {
  • trunk/Source/WebCore/editing/markup.h

    r236707 r236785  
    7171enum class ResolveURLs : uint8_t { No, Yes, YesExcludingLocalFileURLsForPrivacy };
    7272enum class ConvertBlocksToInlines : uint8_t { No, Yes };
     73enum class SerializeComposedTree : uint8_t { No, Yes };
    7374WEBCORE_EXPORT String serializePreservingVisualAppearance(const Range&, Vector<Node*>* = nullptr, AnnotateForInterchange = AnnotateForInterchange::No, ConvertBlocksToInlines = ConvertBlocksToInlines::No, ResolveURLs = ResolveURLs::No);
    74 String serializePreservingVisualAppearance(const VisibleSelection&, ResolveURLs = ResolveURLs::No, Vector<Node*>* = nullptr);
     75String serializePreservingVisualAppearance(const VisibleSelection&, ResolveURLs = ResolveURLs::No, SerializeComposedTree = SerializeComposedTree::No, Vector<Node*>* = nullptr);
    7576
    7677enum class SerializedNodes : uint8_t { SubtreeIncludingNode, SubtreesOfChildren };
  • trunk/Source/WebCore/editing/wpe/EditorWPE.cpp

    r236707 r236785  
    6565    PasteboardWebContent pasteboardContent;
    6666    pasteboardContent.text = selectedTextForDataTransfer();
    67     pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy);
     67    pasteboardContent.markup = serializePreservingVisualAppearance(m_frame.selection().selection(), ResolveURLs::YesExcludingLocalFileURLsForPrivacy,
     68        m_frame.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No);
    6869    pasteboard.write(pasteboardContent);
    6970}
  • trunk/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp

    r236762 r236785  
    555555
    556556    Vector<Node*> nodeList;
    557     builder.append(serializePreservingVisualAppearance(frame->selection().selection(), ResolveURLs::No, &nodeList));
     557    auto serializeComposedTree = frame->settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No;
     558    builder.append(serializePreservingVisualAppearance(frame->selection().selection(), ResolveURLs::No, serializeComposedTree, &nodeList));
    558559
    559560    auto archive = create(builder.toString(), *frame, nodeList, nullptr);
  • trunk/Source/WebCore/page/PageSerializer.cpp

    r236762 r236785  
    106106    void appendElement(StringBuilder&, const Element&, Namespaces*) override;
    107107    void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override;
    108     void appendEndTag(const Element&) override;
     108    void appendEndElement(StringBuilder&, const Element&) override;
    109109};
    110110
     
    159159}
    160160
    161 void PageSerializer::SerializerMarkupAccumulator::appendEndTag(const Element& element)
     161void PageSerializer::SerializerMarkupAccumulator::appendEndElement(StringBuilder& out, const Element& element)
    162162{
    163163    if (!shouldIgnoreElement(element))
    164         MarkupAccumulator::appendEndTag(element);
     164        MarkupAccumulator::appendEndElement(out, element);
    165165}
    166166
  • trunk/Source/WebCore/testing/Internals.cpp

    r236773 r236785  
    34503450}
    34513451
     3452void Internals::setSelectionWithoutValidation(Ref<Node> baseNode, unsigned baseOffset, RefPtr<Node> extentNode, unsigned extentOffset)
     3453{
     3454    contextDocument()->frame()->selection().moveTo(
     3455        VisiblePosition { createLegacyEditingPosition(baseNode.ptr(), baseOffset) },
     3456        VisiblePosition { createLegacyEditingPosition(extentNode.get(), extentOffset) });
     3457}
     3458
    34523459ExceptionOr<bool> Internals::isPluginUnavailabilityIndicatorObscured(Element& element)
    34533460{
  • trunk/Source/WebCore/testing/Internals.h

    r236773 r236785  
    535535
    536536    ExceptionOr<Ref<DOMRect>> selectionBounds();
     537    void setSelectionWithoutValidation(Ref<Node> baseNode, unsigned baseOffset, RefPtr<Node> extentNode, unsigned extentOffset);
    537538
    538539    ExceptionOr<bool> isPluginUnavailabilityIndicatorObscured(Element&);
  • trunk/Source/WebCore/testing/Internals.idl

    r236773 r236785  
    542542
    543543    [MayThrowException] DOMRect selectionBounds();
     544    void setSelectionWithoutValidation(Node baseNode, unsigned long baseOffset, Node? extentNode, unsigned long extentOffset);
    544545
    545546    [Conditional=MEDIA_SOURCE] void initializeMockMediaSource();
Note: See TracChangeset for help on using the changeset viewer.