Title: [97497] trunk/Source
Revision
97497
Author
dch...@chromium.org
Date
2011-10-14 12:55:59 -0700 (Fri, 14 Oct 2011)

Log Message

Context-aware HTML paste for Chromium
https://bugs.webkit.org/show_bug.cgi?id=62112

Reviewed by Ryosuke Niwa.

Source/WebCore:

Add createFragmentFromMarkupWithContext which understands enough about DOM structure to
retain necessary elements to preserve structure and appearance when extracting a subset of
a DOM tree.

Covered by existing layout tests.

* editing/MarkupAccumulator.h:
* editing/markup.cpp:
(WebCore::isNonTableCellHTMLBlockElement):
(WebCore::isHTMLBlockElement):
(WebCore::ancestorToRetainStructureAndAppearanceForBlock):
(WebCore::ancestorToRetainStructureAndAppearance):
(WebCore::ancestorToRetainStructureAndAppearanceWithNoRenderer):
(WebCore::findNodesSurroundingContext):
(WebCore::trimFragment):
(WebCore::createFragmentFromMarkupWithContext):
* editing/markup.h:
* platform/chromium/ChromiumDataObject.cpp:
(WebCore::ChromiumDataObject::getData):
* platform/chromium/DataTransferItemChromium.cpp:
(WebCore::DataTransferItemChromium::getAsString):
* platform/chromium/PasteboardChromium.cpp:
(WebCore::Pasteboard::documentFragment):
* platform/chromium/PlatformSupport.h:

Source/WebKit/chromium:

Add WebKit side for plumbing to receive context for HTML paste.

* public/WebClipboard.h:
(WebKit::WebClipboard::readHTML):
* src/PlatformSupport.cpp:
(WebCore::PlatformSupport::clipboardReadHTML):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (97496 => 97497)


--- trunk/Source/WebCore/ChangeLog	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/ChangeLog	2011-10-14 19:55:59 UTC (rev 97497)
@@ -1,3 +1,35 @@
+2011-10-14  Daniel Cheng  <dch...@chromium.org>
+
+        Context-aware HTML paste for Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=62112
+
+        Reviewed by Ryosuke Niwa.
+
+        Add createFragmentFromMarkupWithContext which understands enough about DOM structure to
+        retain necessary elements to preserve structure and appearance when extracting a subset of
+        a DOM tree.
+
+        Covered by existing layout tests.
+
+        * editing/MarkupAccumulator.h:
+        * editing/markup.cpp:
+        (WebCore::isNonTableCellHTMLBlockElement):
+        (WebCore::isHTMLBlockElement):
+        (WebCore::ancestorToRetainStructureAndAppearanceForBlock):
+        (WebCore::ancestorToRetainStructureAndAppearance):
+        (WebCore::ancestorToRetainStructureAndAppearanceWithNoRenderer):
+        (WebCore::findNodesSurroundingContext):
+        (WebCore::trimFragment):
+        (WebCore::createFragmentFromMarkupWithContext):
+        * editing/markup.h:
+        * platform/chromium/ChromiumDataObject.cpp:
+        (WebCore::ChromiumDataObject::getData):
+        * platform/chromium/DataTransferItemChromium.cpp:
+        (WebCore::DataTransferItemChromium::getAsString):
+        * platform/chromium/PasteboardChromium.cpp:
+        (WebCore::Pasteboard::documentFragment):
+        * platform/chromium/PlatformSupport.h:
+
 2011-10-14  Peter Beverloo  <pe...@chromium.org>
 
         [Chromium] Inherit settings from Chromium's envsetup.sh, address a NDK todo

Modified: trunk/Source/WebCore/editing/MarkupAccumulator.h (97496 => 97497)


--- trunk/Source/WebCore/editing/MarkupAccumulator.h	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/editing/MarkupAccumulator.h	2011-10-14 19:55:59 UTC (rev 97497)
@@ -71,6 +71,8 @@
 
     String serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly);
 
+    static void appendComment(StringBuilder& out, const String& comment);
+
 protected:
     virtual void appendString(const String&);
     void appendStartTag(Node*, Namespaces* = 0);
@@ -86,7 +88,6 @@
     void appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&);
     EntityMask entityMaskForText(Text*) const;
     virtual void appendText(StringBuilder& out, Text*);
-    void appendComment(StringBuilder& out, const String& comment);
     void appendDocumentType(StringBuilder& result, const DocumentType*);
     void appendProcessingInstruction(StringBuilder& out, const String& target, const String& data);
     virtual void appendElement(StringBuilder& out, Element*, Namespaces*);

Modified: trunk/Source/WebCore/editing/markup.cpp (97496 => 97497)


--- trunk/Source/WebCore/editing/markup.cpp	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/editing/markup.cpp	2011-10-14 19:55:59 UTC (rev 97497)
@@ -428,10 +428,33 @@
     return lastClosed;
 }
 
-static Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor)
+bool isNonTableCellHTMLBlockElement(const Node* node)
 {
-    Node* commonAncestorBlock = enclosingBlock(commonAncestor);
+    return node->hasTagName(listingTag)
+        || node->hasTagName(olTag)
+        || node->hasTagName(preTag)
+        || node->hasTagName(tableTag)
+        || node->hasTagName(ulTag)
+        || node->hasTagName(xmpTag)
+        || node->hasTagName(h1Tag)
+        || node->hasTagName(h2Tag)
+        || node->hasTagName(h3Tag)
+        || node->hasTagName(h4Tag)
+        || node->hasTagName(h5Tag);
+}
 
+static bool isHTMLBlockElement(const Node* node)
+{
+    return node->hasTagName(tdTag)
+        || node->hasTagName(thTag)
+        || isNonTableCellHTMLBlockElement(node);
+}
+
+// FIXME: Do we want to handle mail quotes here instead?
+// This is currently handled in highestAncestorToWrapMarkup but it might make more
+// sense to move that into here.
+static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestorBlock)
+{
     if (!commonAncestorBlock)
         return 0;
 
@@ -443,22 +466,23 @@
         return table;
     }
 
-    if (commonAncestorBlock->hasTagName(listingTag)
-        || commonAncestorBlock->hasTagName(olTag)
-        || commonAncestorBlock->hasTagName(preTag)
-        || commonAncestorBlock->hasTagName(tableTag)
-        || commonAncestorBlock->hasTagName(ulTag)
-        || commonAncestorBlock->hasTagName(xmpTag)
-        || commonAncestorBlock->hasTagName(h1Tag)
-        || commonAncestorBlock->hasTagName(h2Tag)
-        || commonAncestorBlock->hasTagName(h3Tag)
-        || commonAncestorBlock->hasTagName(h4Tag)
-        || commonAncestorBlock->hasTagName(h5Tag))
+    if (isNonTableCellHTMLBlockElement(commonAncestorBlock))
         return commonAncestorBlock;
 
     return 0;
 }
 
+static inline Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor)
+{
+    return ancestorToRetainStructureAndAppearanceForBlock(enclosingBlock(commonAncestor));
+}
+
+static inline Node* ancestorToRetainStructureAndAppearanceWithNoRenderer(Node* commonAncestor)
+{
+    Node* commonAncestorBlock = enclosingNodeOfType(firstPositionInOrBeforeNode(commonAncestor), isHTMLBlockElement);
+    return ancestorToRetainStructureAndAppearanceForBlock(commonAncestorBlock);
+}
+
 static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propertyID)
 {
     if (!style)
@@ -684,6 +708,92 @@
     return fragment.release();
 }
 
+static const char fragmentMarkerTag[] = "webkit-fragment-marker";
+
+static bool findNodesSurroundingContext(Document* document, RefPtr<Node>& nodeBeforeContext, RefPtr<Node>& nodeAfterContext)
+{
+    for (Node* node = document->firstChild(); node; node = node->traverseNextNode()) {
+        if (node->nodeType() == Node::COMMENT_NODE && static_cast<CharacterData*>(node)->data() == fragmentMarkerTag) {
+            if (!nodeBeforeContext)
+                nodeBeforeContext = node;
+            else {
+                nodeAfterContext = node;
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+static void trimFragment(DocumentFragment* fragment, Node* nodeBeforeContext, Node* nodeAfterContext)
+{
+    ExceptionCode ec = 0;
+    Node* next;
+    for (RefPtr<Node> node = fragment->firstChild(); node; node = next) {
+        if (nodeBeforeContext->isDescendantOf(node.get())) {
+            next = node->traverseNextNode();
+            continue;
+        }
+        next = node->traverseNextSibling();
+        ASSERT(!node->contains(nodeAfterContext));
+        node->parentNode()->removeChild(node.get(), ec);
+        if (nodeBeforeContext == node)
+            break;
+    }
+
+    ASSERT(nodeAfterContext->parentNode());
+    for (Node* node = nodeAfterContext; node; node = next) {
+        next = node->traverseNextSibling();
+        node->parentNode()->removeChild(node, ec);
+        ASSERT(!ec);
+    }
+}
+
+PassRefPtr<DocumentFragment> createFragmentFromMarkupWithContext(Document* document, const String& markup, unsigned fragmentStart, unsigned fragmentEnd,
+    const String& baseURL, FragmentScriptingPermission scriptingPermission)
+{
+    // FIXME: Need to handle the case where the markup already contains these markers.
+
+    StringBuilder taggedMarkup;
+    taggedMarkup.append(markup.left(fragmentStart));
+    MarkupAccumulator::appendComment(taggedMarkup, fragmentMarkerTag);
+    taggedMarkup.append(markup.substring(fragmentStart, fragmentEnd - fragmentStart));
+    MarkupAccumulator::appendComment(taggedMarkup, fragmentMarkerTag);
+    taggedMarkup.append(markup.substring(fragmentEnd));
+
+    RefPtr<DocumentFragment> taggedFragment = createFragmentFromMarkup(document, taggedMarkup.toString(), baseURL, scriptingPermission);
+    RefPtr<Document> taggedDocument = Document::create(0, KURL());
+    taggedDocument->takeAllChildrenFrom(taggedFragment.get());
+
+    RefPtr<Node> nodeBeforeContext;
+    RefPtr<Node> nodeAfterContext;
+    if (!findNodesSurroundingContext(taggedDocument.get(), nodeBeforeContext, nodeAfterContext))
+        return 0;
+
+    RefPtr<Range> range = Range::create(taggedDocument.get(),
+        positionAfterNode(nodeBeforeContext.get()).parentAnchoredEquivalent(),
+        positionBeforeNode(nodeAfterContext.get()).parentAnchoredEquivalent());
+
+    ExceptionCode ec = 0;
+    Node* commonAncestor = range->commonAncestorContainer(ec);
+    ASSERT(!ec);
+    Node* specialCommonAncestor = ancestorToRetainStructureAndAppearanceWithNoRenderer(commonAncestor);
+
+    // When there's a special common ancestor outside of the fragment, we must include it as well to
+    // preserve the structure and appearance of the fragment. For example, if the fragment contains
+    // TD, we need to include the enclosing TABLE tag as well.
+    RefPtr<DocumentFragment> fragment = DocumentFragment::create(document);
+    if (specialCommonAncestor) {
+        fragment->appendChild(specialCommonAncestor, ec);
+        ASSERT(!ec);
+    } else
+        fragment->takeAllChildrenFrom(static_cast<ContainerNode*>(commonAncestor));
+
+    trimFragment(fragment.get(), nodeBeforeContext.get(), nodeAfterContext.get());
+
+    return fragment;
+}
+
 String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs)
 {
     if (!node)

Modified: trunk/Source/WebCore/editing/markup.h (97496 => 97497)


--- trunk/Source/WebCore/editing/markup.h	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/editing/markup.h	2011-10-14 19:55:59 UTC (rev 97497)
@@ -45,6 +45,7 @@
 
     PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text);
     PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document*, const String& markup, const String& baseURL, FragmentScriptingPermission = FragmentScriptingAllowed);
+    PassRefPtr<DocumentFragment> createFragmentFromMarkupWithContext(Document*, const String& markup, unsigned fragmentStart, unsigned fragmentEnd, const String& baseURL, FragmentScriptingPermission);
     PassRefPtr<DocumentFragment> createFragmentFromNodes(Document*, const Vector<Node*>&);
 
     bool isPlainTextMarkup(Node *node);

Modified: trunk/Source/WebCore/platform/chromium/ChromiumDataObject.cpp (97496 => 97497)


--- trunk/Source/WebCore/platform/chromium/ChromiumDataObject.cpp	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/platform/chromium/ChromiumDataObject.cpp	2011-10-14 19:55:59 UTC (rev 97497)
@@ -160,7 +160,8 @@
                 PasteboardPrivate::StandardBuffer;
             String htmlText;
             KURL sourceURL;
-            PlatformSupport::clipboardReadHTML(buffer, &htmlText, &sourceURL);
+            unsigned ignored;
+            PlatformSupport::clipboardReadHTML(buffer, &htmlText, &sourceURL, &ignored, &ignored);
             success = !htmlText.isEmpty();
             return htmlText;
         }

Modified: trunk/Source/WebCore/platform/chromium/DataTransferItemChromium.cpp (97496 => 97497)


--- trunk/Source/WebCore/platform/chromium/DataTransferItemChromium.cpp	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/platform/chromium/DataTransferItemChromium.cpp	2011-10-14 19:55:59 UTC (rev 97497)
@@ -97,7 +97,8 @@
     if (type() == mimeTypeTextHTML) {
         String html;
         KURL ignoredSourceURL;
-        PlatformSupport::clipboardReadHTML(PasteboardPrivate::StandardBuffer, &html, &ignoredSourceURL);
+        unsigned ignored;
+        PlatformSupport::clipboardReadHTML(PasteboardPrivate::StandardBuffer, &html, &ignoredSourceURL, &ignored, &ignored);
         callback->scheduleCallback(m_context, html);
         return;
     }

Modified: trunk/Source/WebCore/platform/chromium/PasteboardChromium.cpp (97496 => 97497)


--- trunk/Source/WebCore/platform/chromium/PasteboardChromium.cpp	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/platform/chromium/PasteboardChromium.cpp	2011-10-14 19:55:59 UTC (rev 97497)
@@ -173,10 +173,12 @@
     if (PlatformSupport::clipboardIsFormatAvailable(PasteboardPrivate::HTMLFormat, buffer)) {
         String markup;
         KURL srcURL;
-        PlatformSupport::clipboardReadHTML(buffer, &markup, &srcURL);
+        unsigned fragmentStart = 0;
+        unsigned fragmentEnd = 0;
+        PlatformSupport::clipboardReadHTML(buffer, &markup, &srcURL, &fragmentStart, &fragmentEnd);
 
         RefPtr<DocumentFragment> fragment =
-            createFragmentFromMarkup(frame->document(), markup, srcURL, FragmentScriptingNotAllowed);
+            createFragmentFromMarkupWithContext(frame->document(), markup, fragmentStart, fragmentEnd, srcURL, FragmentScriptingNotAllowed);
         if (fragment)
             return fragment.release();
     }

Modified: trunk/Source/WebCore/platform/chromium/PlatformSupport.h (97496 => 97497)


--- trunk/Source/WebCore/platform/chromium/PlatformSupport.h	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebCore/platform/chromium/PlatformSupport.h	2011-10-14 19:55:59 UTC (rev 97497)
@@ -98,7 +98,7 @@
     static bool clipboardIsFormatAvailable(PasteboardPrivate::ClipboardFormat, PasteboardPrivate::ClipboardBuffer);
 
     static String clipboardReadPlainText(PasteboardPrivate::ClipboardBuffer);
-    static void clipboardReadHTML(PasteboardPrivate::ClipboardBuffer, String*, KURL*);
+    static void clipboardReadHTML(PasteboardPrivate::ClipboardBuffer, String*, KURL*, unsigned* fragmentStart, unsigned* fragmentEnd);
     static PassRefPtr<SharedBuffer> clipboardReadImage(PasteboardPrivate::ClipboardBuffer);
     static uint64_t clipboardGetSequenceNumber();
 

Modified: trunk/Source/WebKit/chromium/ChangeLog (97496 => 97497)


--- trunk/Source/WebKit/chromium/ChangeLog	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebKit/chromium/ChangeLog	2011-10-14 19:55:59 UTC (rev 97497)
@@ -1,3 +1,17 @@
+2011-10-14  Daniel Cheng  <dch...@chromium.org>
+
+        Context-aware HTML paste for Chromium
+        https://bugs.webkit.org/show_bug.cgi?id=62112
+
+        Reviewed by Ryosuke Niwa.
+
+        Add WebKit side for plumbing to receive context for HTML paste.
+
+        * public/WebClipboard.h:
+        (WebKit::WebClipboard::readHTML):
+        * src/PlatformSupport.cpp:
+        (WebCore::PlatformSupport::clipboardReadHTML):
+
 2011-10-14  Peter Beverloo  <pe...@chromium.org>
 
         [Chromium] Inherit settings from Chromium's envsetup.sh, address a NDK todo

Modified: trunk/Source/WebKit/chromium/public/WebClipboard.h (97496 => 97497)


--- trunk/Source/WebKit/chromium/public/WebClipboard.h	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebKit/chromium/public/WebClipboard.h	2011-10-14 19:55:59 UTC (rev 97497)
@@ -63,7 +63,14 @@
     virtual bool isFormatAvailable(Format, Buffer) { return false; }
 
     virtual WebString readPlainText(Buffer) { return WebString(); }
-    virtual WebString readHTML(Buffer, WebURL*) { return WebString(); }
+    // fragmentStart and fragmentEnd are indexes into the returned markup that
+    // indicate the start and end of the fragment if the returned markup
+    // contains additional context. If there is no additional context,
+    // fragmentStart will be zero and fragmentEnd will be the same as the length
+    // of the returned markup.
+    virtual WebString readHTML(
+        Buffer buffer, WebURL* pageURL, unsigned* fragmentStart,
+        unsigned* fragmentEnd) { return WebString(); }
     virtual WebData readImage(Buffer) { return WebData(); }
 
     // Returns an identifier which can be used to determine whether the data
@@ -87,7 +94,7 @@
     // paste, drag and drop, and selection copy (on X).
     virtual WebVector<WebString> readAvailableTypes(
         Buffer, bool* containsFilenames) { return WebVector<WebString>(); }
-    // Returns true if the requested type was successfully read from the buffer. 
+    // Returns true if the requested type was successfully read from the buffer.
     virtual bool readData(
         Buffer, const WebString& type, WebString* data,
         WebString* metadata) { return false; }

Modified: trunk/Source/WebKit/chromium/src/PlatformSupport.cpp (97496 => 97497)


--- trunk/Source/WebKit/chromium/src/PlatformSupport.cpp	2011-10-14 19:52:53 UTC (rev 97496)
+++ trunk/Source/WebKit/chromium/src/PlatformSupport.cpp	2011-10-14 19:55:59 UTC (rev 97497)
@@ -168,11 +168,11 @@
 
 void PlatformSupport::clipboardReadHTML(
     PasteboardPrivate::ClipboardBuffer buffer,
-    String* htmlText, KURL* sourceURL)
+    String* htmlText, KURL* sourceURL, unsigned* fragmentStart, unsigned* fragmentEnd)
 {
     WebURL url;
     *htmlText = webKitPlatformSupport()->clipboard()->readHTML(
-        static_cast<WebClipboard::Buffer>(buffer), &url);
+        static_cast<WebClipboard::Buffer>(buffer), &url, fragmentStart, fragmentEnd);
     *sourceURL = url;
 }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to