summaryrefslogtreecommitdiff
path: root/Source/WebCore/editing/markup.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing/markup.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/editing/markup.cpp')
-rw-r--r--Source/WebCore/editing/markup.cpp483
1 files changed, 252 insertions, 231 deletions
diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp
index 5b6173b15..db57589af 100644
--- a/Source/WebCore/editing/markup.cpp
+++ b/Source/WebCore/editing/markup.cpp
@@ -13,10 +13,10 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
@@ -29,7 +29,6 @@
#include "config.h"
#include "markup.h"
-#include "CDATASection.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyNames.h"
#include "CSSValue.h"
@@ -40,30 +39,33 @@
#include "Editor.h"
#include "ElementIterator.h"
#include "ExceptionCode.h"
-#include "ExceptionCodePlaceholder.h"
+#include "File.h"
#include "Frame.h"
+#include "HTMLAttachmentElement.h"
+#include "HTMLBRElement.h"
#include "HTMLBodyElement.h"
-#include "HTMLElement.h"
+#include "HTMLDivElement.h"
+#include "HTMLHeadElement.h"
+#include "HTMLHtmlElement.h"
#include "HTMLNames.h"
#include "HTMLTableElement.h"
#include "HTMLTextAreaElement.h"
#include "HTMLTextFormControlElement.h"
#include "URL.h"
#include "MarkupAccumulator.h"
+#include "NodeList.h"
#include "Range.h"
#include "RenderBlock.h"
+#include "Settings.h"
#include "StyleProperties.h"
#include "TextIterator.h"
+#include "TypedElementDescendantIterator.h"
#include "VisibleSelection.h"
#include "VisibleUnits.h"
#include "htmlediting.h"
#include <wtf/StdLibExtras.h>
#include <wtf/text/StringBuilder.h>
-#if ENABLE(DELETION_UI)
-#include "DeleteButtonController.h"
-#endif
-
namespace WebCore {
using namespace HTMLNames;
@@ -77,7 +79,7 @@ public:
{
}
- AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value)
+ AttributeChange(Element* element, const QualifiedName& name, const String& value)
: m_element(element), m_name(name), m_value(value)
{
}
@@ -103,26 +105,28 @@ static void completeURLs(DocumentFragment* fragment, const String& baseURL)
if (!element.hasAttributes())
continue;
for (const Attribute& attribute : element.attributesIterator()) {
- if (element.isURLAttribute(attribute) && !attribute.value().isEmpty())
- changes.append(AttributeChange(&element, attribute.name(), URL(parsedBaseURL, attribute.value()).string()));
+ if (element.attributeContainsURL(attribute) && !attribute.value().isEmpty())
+ changes.append(AttributeChange(&element, attribute.name(), element.completeURLsInAttributeValue(parsedBaseURL, attribute)));
}
}
- size_t numChanges = changes.size();
- for (size_t i = 0; i < numChanges; ++i)
- changes[i].apply();
+ for (auto& change : changes)
+ change.apply();
}
class StyledMarkupAccumulator final : public MarkupAccumulator {
public:
enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
- StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs, EAnnotateForInterchange, const Range*, Node* highestNodeToBeSerialized = 0);
+ StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs, EAnnotateForInterchange, const Range*, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized = nullptr);
Node* serializeNodes(Node* startNode, Node* pastEnd);
void wrapWithNode(Node&, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode);
void wrapWithStyleNode(StyleProperties*, Document&, bool isBlock = false);
String takeResults();
+
+ bool needRelativeStyleWrapper() const { return m_needRelativeStyleWrapper; }
+ bool needClearingDiv() const { return m_needClearingDiv; }
using MarkupAccumulator::appendString;
@@ -134,9 +138,10 @@ private:
String stringValueForRange(const Node&, const Range*);
void appendElement(StringBuilder& out, const Element&, bool addDisplayInline, RangeFullySelectsNode);
+ void appendCustomAttributes(StringBuilder&, const Element&, Namespaces*) override;
- virtual void appendText(StringBuilder& out, const Text&) override;
- virtual void appendElement(StringBuilder& out, const Element& element, Namespaces*) override
+ void appendText(StringBuilder& out, const Text&) override;
+ void appendElement(StringBuilder& out, const Element& element, Namespaces*) override
{
appendElement(out, element, false, DoesFullySelectNode);
}
@@ -158,23 +163,28 @@ private:
const EAnnotateForInterchange m_shouldAnnotate;
Node* m_highestNodeToBeSerialized;
RefPtr<EditingStyle> m_wrappingStyle;
+ bool m_needRelativeStyleWrapper;
+ bool m_needsPositionStyleConversion;
+ bool m_needClearingDiv;
};
-inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate,
- const Range* range, Node* highestNodeToBeSerialized)
+inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range, bool needsPositionStyleConversion, Node* highestNodeToBeSerialized)
: MarkupAccumulator(nodes, shouldResolveURLs, range)
, m_shouldAnnotate(shouldAnnotate)
, m_highestNodeToBeSerialized(highestNodeToBeSerialized)
+ , m_needRelativeStyleWrapper(false)
+ , m_needsPositionStyleConversion(needsPositionStyleConversion)
+ , m_needClearingDiv(false)
{
}
void StyledMarkupAccumulator::wrapWithNode(Node& node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode)
{
StringBuilder markup;
- if (node.isElementNode())
- appendElement(markup, toElement(node), convertBlocksToInlines && isBlock(&node), rangeFullySelectsNode);
+ if (is<Element>(node))
+ appendElement(markup, downcast<Element>(node), convertBlocksToInlines && isBlock(&node), rangeFullySelectsNode);
else
- appendStartMarkup(markup, node, 0);
+ appendStartMarkup(markup, node, nullptr);
m_reversedPrecedingMarkup.append(markup.toString());
appendEndTag(node);
if (m_nodes)
@@ -203,8 +213,8 @@ void StyledMarkupAccumulator::appendStyleNodeOpenTag(StringBuilder& out, StylePr
const String& StyledMarkupAccumulator::styleNodeCloseTag(bool isBlock)
{
- DEFINE_STATIC_LOCAL(const String, divClose, (ASCIILiteral("</div>")));
- DEFINE_STATIC_LOCAL(const String, styleSpanClose, (ASCIILiteral("</span>")));
+ static NeverDestroyed<const String> divClose(ASCIILiteral("</div>"));
+ static NeverDestroyed<const String> styleSpanClose(ASCIILiteral("</span>"));
return isBlock ? divClose : styleSpanClose;
}
@@ -224,7 +234,7 @@ String StyledMarkupAccumulator::takeResults()
void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text)
{
- const bool parentIsTextarea = text.parentElement() && isHTMLTextAreaElement(text.parentElement());
+ const bool parentIsTextarea = is<HTMLTextAreaElement>(text.parentElement());
const bool wrappingSpan = shouldApplyWrappingStyle(text) && !parentIsTextarea;
if (wrappingSpan) {
RefPtr<EditingStyle> wrappingStyle = m_wrappingStyle->copy();
@@ -240,7 +250,7 @@ void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text)
if (!shouldAnnotate() || parentIsTextarea)
MarkupAccumulator::appendText(out, text);
else {
- const bool useRenderedText = !enclosingNodeWithTag(firstPositionInNode(const_cast<Text*>(&text)), selectTag);
+ const bool useRenderedText = !enclosingElementWithTag(firstPositionInNode(const_cast<Text*>(&text)), selectTag);
String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range);
StringBuilder buffer;
appendCharactersReplacingEntities(buffer, content, 0, content.length(), EntityMaskInPCDATA);
@@ -253,21 +263,24 @@ void StyledMarkupAccumulator::appendText(StringBuilder& out, const Text& text)
String StyledMarkupAccumulator::renderedText(const Node& node, const Range* range)
{
- if (!node.isTextNode())
+ if (!is<Text>(node))
return String();
- const Text& textNode = toText(node);
+ const Text& textNode = downcast<Text>(node);
unsigned startOffset = 0;
unsigned endOffset = textNode.length();
- if (range && &node == range->startContainer())
+ TextIteratorBehavior behavior = TextIteratorDefaultBehavior;
+ if (range && &node == &range->startContainer())
startOffset = range->startOffset();
- if (range && &node == range->endContainer())
+ if (range && &node == &range->endContainer())
endOffset = range->endOffset();
+ else if (range)
+ behavior = TextIteratorBehavesAsIfNodesFollowing;
Position start = createLegacyEditingPosition(const_cast<Node*>(&node), startOffset);
Position end = createLegacyEditingPosition(const_cast<Node*>(&node), endOffset);
- return plainText(Range::create(node.document(), start, end).get());
+ return plainText(Range::create(node.document(), start, end).ptr(), behavior);
}
String StyledMarkupAccumulator::stringValueForRange(const Node& node, const Range* range)
@@ -276,18 +289,36 @@ String StyledMarkupAccumulator::stringValueForRange(const Node& node, const Rang
return node.nodeValue();
String nodeValue = node.nodeValue();
- if (&node == range->endContainer())
+ if (&node == &range->endContainer())
nodeValue.truncate(range->endOffset());
- if (&node == range->startContainer())
+ if (&node == &range->startContainer())
nodeValue.remove(0, range->startOffset());
return nodeValue;
}
+void StyledMarkupAccumulator::appendCustomAttributes(StringBuilder& out, const Element&element, Namespaces* namespaces)
+{
+#if ENABLE(ATTACHMENT_ELEMENT)
+ if (!is<HTMLAttachmentElement>(element))
+ return;
+
+ const HTMLAttachmentElement& attachment = downcast<HTMLAttachmentElement>(element);
+ if (attachment.file())
+ appendAttribute(out, element, Attribute(webkitattachmentpathAttr, attachment.file()->path()), namespaces);
+#else
+ UNUSED_PARAM(out);
+ UNUSED_PARAM(element);
+ UNUSED_PARAM(namespaces);
+#endif
+}
+
void StyledMarkupAccumulator::appendElement(StringBuilder& out, const Element& element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode)
{
const bool documentIsHTML = element.document().isHTMLDocument();
appendOpenTag(out, element, 0);
+ appendCustomAttributes(out, element, nullptr);
+
const bool shouldAnnotateOrForceInline = element.isHTMLElement() && (shouldAnnotate() || addDisplayInline);
const bool shouldOverrideStyleAttr = shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element);
if (element.hasAttributes()) {
@@ -309,15 +340,20 @@ void StyledMarkupAccumulator::appendElement(StringBuilder& out, const Element& e
} else
newInlineStyle = EditingStyle::create();
- if (element.isStyledElement() && toStyledElement(element).inlineStyle())
- newInlineStyle->overrideWithStyle(toStyledElement(element).inlineStyle());
+ if (is<StyledElement>(element) && downcast<StyledElement>(element).inlineStyle())
+ newInlineStyle->overrideWithStyle(downcast<StyledElement>(element).inlineStyle());
if (shouldAnnotateOrForceInline) {
if (shouldAnnotate())
- newInlineStyle->mergeStyleFromRulesForSerialization(toHTMLElement(const_cast<Element*>(&element)));
+ newInlineStyle->mergeStyleFromRulesForSerialization(downcast<HTMLElement>(const_cast<Element*>(&element)));
if (addDisplayInline)
newInlineStyle->forceInline();
+
+ if (m_needsPositionStyleConversion) {
+ m_needRelativeStyleWrapper |= newInlineStyle->convertPositionStyle();
+ m_needClearingDiv |= newInlineStyle->isFloating();
+ }
// If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
// only the ones that affect it and the nodes within it.
@@ -353,7 +389,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
const bool shouldEmit = traversalMode == EmitString;
Vector<Node*> ancestorsToClose;
Node* next;
- Node* lastClosed = 0;
+ Node* lastClosed = nullptr;
for (Node* n = startNode; n != pastEnd; n = next) {
// According to <rdar://problem/5730668>, it is possible for n to blow
// past pastEnd and become null here. This shouldn't be possible.
@@ -363,17 +399,18 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
if (!n)
break;
- next = NodeTraversal::next(n);
+ next = NodeTraversal::next(*n);
bool openedTag = false;
- if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
+ if (isBlock(n) && canHaveChildrenForEditing(*n) && next == pastEnd) {
// Don't write out empty block containers that aren't fully selected.
continue;
+ }
- if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) {
- next = NodeTraversal::nextSkippingChildren(n);
+ if (!n->renderer() && !enclosingElementWithTag(firstPositionInOrBeforeNode(n), selectTag)) {
+ next = NodeTraversal::nextSkippingChildren(*n);
// Don't skip over pastEnd.
- if (pastEnd && pastEnd->isDescendantOf(n))
+ if (pastEnd && pastEnd->isDescendantOf(*n))
next = pastEnd;
} else {
// Add the node to the markup if we're not skipping the descendants
@@ -381,7 +418,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
appendStartTag(*n);
// If node has no children, close the tag now.
- if (!n->childNodeCount()) {
+ if (!n->hasChildNodes()) {
if (shouldEmit)
appendEndTag(*n);
lastClosed = n;
@@ -415,7 +452,7 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
if (!parent->renderer())
continue;
// or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
- ASSERT(startNode->isDescendantOf(parent));
+ ASSERT(startNode->isDescendantOf(*parent));
if (shouldEmit)
wrapWithNode(*parent);
lastClosed = parent;
@@ -430,11 +467,11 @@ Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, No
static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestorBlock)
{
if (!commonAncestorBlock)
- return 0;
+ return nullptr;
if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
ContainerNode* table = commonAncestorBlock->parentNode();
- while (table && !isHTMLTableElement(table))
+ while (table && !is<HTMLTableElement>(*table))
table = table->parentNode();
return table;
@@ -443,7 +480,7 @@ static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestor
if (isNonTableCellHTMLBlockElement(commonAncestorBlock))
return commonAncestorBlock;
- return 0;
+ return nullptr;
}
static inline Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor)
@@ -458,9 +495,9 @@ static bool propertyMissingOrEqualToNone(StyleProperties* style, CSSPropertyID p
RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
if (!value)
return true;
- if (!value->isPrimitiveValue())
+ if (!is<CSSPrimitiveValue>(*value))
return false;
- return toCSSPrimitiveValue(value.get())->getValueID() == CSSValueNone;
+ return downcast<CSSPrimitiveValue>(*value).valueID() == CSSValueNone;
}
static bool needInterchangeNewlineAfter(const VisiblePosition& v)
@@ -472,17 +509,17 @@ static bool needInterchangeNewlineAfter(const VisiblePosition& v)
return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
}
-static PassRefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const Node* node)
+static RefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const Node* node)
{
if (!node->isHTMLElement())
- return 0;
+ return nullptr;
// FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
// the non-const-ness of styleFromMatchedRulesForElement.
HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
RefPtr<EditingStyle> style = EditingStyle::create(element->inlineStyle());
style->mergeStyleFromRules(element);
- return style.release();
+ return style;
}
static bool isElementPresentational(const Node* node)
@@ -493,18 +530,18 @@ static bool isElementPresentational(const Node* node)
static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterchange shouldAnnotate)
{
- Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION);
+ auto* commonAncestor = range->commonAncestorContainer();
ASSERT(commonAncestor);
- Node* specialCommonAncestor = 0;
+ Node* specialCommonAncestor = nullptr;
if (shouldAnnotate == AnnotateForInterchange) {
// Include ancestors that aren't completely inside the range but are required to retain
// the structure and appearance of the copied markup.
specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor);
- if (Node* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isListItem)) {
- if (WebCore::areRangesEqual(VisibleSelection::selectionFromContentsOfNode(parentListNode).toNormalizedRange().get(), range)) {
+ if (auto* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isListItem)) {
+ if (!editingIgnoresContent(*parentListNode) && WebCore::areRangesEqual(VisibleSelection::selectionFromContentsOfNode(parentListNode).toNormalizedRange().get(), range)) {
specialCommonAncestor = parentListNode->parentNode();
- while (specialCommonAncestor && !isListElement(specialCommonAncestor))
+ while (specialCommonAncestor && !isListHTMLElement(specialCommonAncestor))
specialCommonAncestor = specialCommonAncestor->parentNode();
}
}
@@ -514,7 +551,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch
specialCommonAncestor = highestMailBlockquote;
}
- Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
+ auto* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
if (checkAncestor->renderer() && checkAncestor->renderer()->containingBlock()) {
Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(firstPositionInNode(checkAncestor), &isElementPresentational, CanCrossEditingBoundary, checkAncestor->renderer()->containingBlock()->element());
if (newSpecialCommonAncestor)
@@ -530,7 +567,7 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch
if (!specialCommonAncestor && isTabSpanNode(commonAncestor))
specialCommonAncestor = commonAncestor;
- if (Node *enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag))
+ if (auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag))
specialCommonAncestor = enclosingAnchor;
return specialCommonAncestor;
@@ -538,33 +575,36 @@ static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterch
// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
// FIXME: At least, annotation and style info should probably not be included in range.markupString()
-static String createMarkupInternal(Document& document, const Range& range, const Range& updatedRange, Vector<Node*>* nodes,
+static String createMarkupInternal(Document& document, const Range& range, Vector<Node*>* nodes,
EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs)
{
- DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, (ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">")));
+ static NeverDestroyed<const String> interchangeNewlineString(ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">"));
- bool collapsed = updatedRange.collapsed(ASSERT_NO_EXCEPTION);
+ bool collapsed = range.collapsed();
if (collapsed)
return emptyString();
- Node* commonAncestor = updatedRange.commonAncestorContainer(ASSERT_NO_EXCEPTION);
+ Node* commonAncestor = range.commonAncestorContainer();
if (!commonAncestor)
return emptyString();
document.updateLayoutIgnorePendingStylesheets();
- Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag);
- Node* fullySelectedRoot = 0;
+ auto* body = enclosingElementWithTag(firstPositionInNode(commonAncestor), bodyTag);
+ Element* fullySelectedRoot = nullptr;
// FIXME: Do this for all fully selected blocks, not just the body.
- if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), &range))
+ if (body && VisiblePosition(firstPositionInNode(body)) == VisiblePosition(range.startPosition())
+ && VisiblePosition(lastPositionInNode(body)) == VisiblePosition(range.endPosition()))
fullySelectedRoot = body;
- Node* specialCommonAncestor = highestAncestorToWrapMarkup(&updatedRange, shouldAnnotate);
+ Node* specialCommonAncestor = highestAncestorToWrapMarkup(&range, shouldAnnotate);
- StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, &updatedRange, specialCommonAncestor);
- Node* pastEnd = updatedRange.pastLastNode();
+ bool needsPositionStyleConversion = body && fullySelectedRoot == body
+ && document.settings().shouldConvertPositionStyleOnCopy();
+ StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, &range, needsPositionStyleConversion, specialCommonAncestor);
+ Node* pastEnd = range.pastLastNode();
- Node* startNode = updatedRange.firstNode();
- VisiblePosition visibleStart(updatedRange.startPosition(), VP_DEFAULT_AFFINITY);
- VisiblePosition visibleEnd(updatedRange.endPosition(), VP_DEFAULT_AFFINITY);
+ Node* startNode = range.firstNode();
+ VisiblePosition visibleStart(range.startPosition(), VP_DEFAULT_AFFINITY);
+ VisiblePosition visibleEnd(range.endPosition(), VP_DEFAULT_AFFINITY);
if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) {
if (visibleStart == visibleEnd.previous())
return interchangeNewlineString;
@@ -572,7 +612,7 @@ static String createMarkupInternal(Document& document, const Range& range, const
accumulator.appendString(interchangeNewlineString);
startNode = visibleStart.next().deepEquivalent().deprecatedNode();
- if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ASSERT_NO_EXCEPTION) >= 0)
+ if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0).releaseReturnValue() >= 0)
return interchangeNewlineString;
}
@@ -587,8 +627,8 @@ static String createMarkupInternal(Document& document, const Range& range, const
// Bring the background attribute over, but not as an attribute because a background attribute on a div
// appears to have no effect.
if ((!fullySelectedRootStyle || !fullySelectedRootStyle->style() || !fullySelectedRootStyle->style()->getPropertyCSSValue(CSSPropertyBackgroundImage))
- && toElement(fullySelectedRoot)->hasAttribute(backgroundAttr))
- fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + toElement(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
+ && fullySelectedRoot->hasAttributeWithoutSynchronization(backgroundAttr))
+ fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + fullySelectedRoot->getAttribute(backgroundAttr) + "')");
if (fullySelectedRootStyle->style()) {
// Reset the CSS properties to avoid an assertion error in addStyleMarkup().
@@ -608,12 +648,18 @@ static String createMarkupInternal(Document& document, const Range& range, const
if (nodes)
nodes->append(ancestor);
- lastClosed = ancestor;
-
if (ancestor == specialCommonAncestor)
break;
}
}
+
+ if (accumulator.needRelativeStyleWrapper() && needsPositionStyleConversion) {
+ if (accumulator.needClearingDiv())
+ accumulator.appendString("<div style=\"clear: both;\"></div>");
+ RefPtr<EditingStyle> positionRelativeStyle = styleFromMatchedRulesAndInlineDecl(body);
+ positionRelativeStyle->style()->setProperty(CSSPropertyPosition, CSSValueRelative);
+ accumulator.wrapWithStyleNode(positionRelativeStyle->style(), document, true);
+ }
// FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous()))
@@ -624,62 +670,46 @@ static String createMarkupInternal(Document& document, const Range& range, const
String createMarkup(const Range& range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs)
{
- Document& document = range.ownerDocument();
- const Range* updatedRange = &range;
-
-#if ENABLE(DELETION_UI)
- // Disable the delete button so it's elements are not serialized into the markup,
- // but make sure neither endpoint is inside the delete user interface.
- Frame* frame = document.frame();
- DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame);
-
- RefPtr<Range> updatedRangeRef;
- if (frame) {
- updatedRangeRef = frame->editor().avoidIntersectionWithDeleteButtonController(&range);
- updatedRange = updatedRangeRef.get();
- if (!updatedRange)
- return emptyString();
- }
-#endif
-
- return createMarkupInternal(document, range, *updatedRange, nodes, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs);
+ return createMarkupInternal(range.ownerDocument(), range, nodes, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs);
}
-PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document& document, const String& markup, const String& baseURL, ParserContentPolicy parserContentPolicy)
+Ref<DocumentFragment> createFragmentFromMarkup(Document& document, const String& markup, const String& baseURL, ParserContentPolicy parserContentPolicy)
{
// We use a fake body element here to trick the HTML parser to using the InBody insertion mode.
- RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(document);
- RefPtr<DocumentFragment> fragment = DocumentFragment::create(document);
+ auto fakeBody = HTMLBodyElement::create(document);
+ auto fragment = DocumentFragment::create(document);
+
+ fragment->parseHTML(markup, fakeBody.ptr(), parserContentPolicy);
- fragment->parseHTML(markup, fakeBody.get(), parserContentPolicy);
+#if ENABLE(ATTACHMENT_ELEMENT)
+ // When creating a fragment we must strip the webkit-attachment-path attribute after restoring the File object.
+ Vector<Ref<HTMLAttachmentElement>> attachments;
+ for (auto& attachment : descendantsOfType<HTMLAttachmentElement>(fragment))
+ attachments.append(attachment);
+ for (auto& attachment : attachments) {
+ attachment->setFile(File::create(attachment->attributeWithoutSynchronization(webkitattachmentpathAttr)).ptr());
+ attachment->removeAttribute(webkitattachmentpathAttr);
+ }
+#endif
if (!baseURL.isEmpty() && baseURL != blankURL() && baseURL != document.baseURL())
- completeURLs(fragment.get(), baseURL);
+ completeURLs(fragment.ptr(), baseURL);
- return fragment.release();
+ return fragment;
}
String createMarkup(const Node& node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, Vector<QualifiedName>* tagNamesToSkip, EFragmentSerialization fragmentSerialization)
{
- HTMLElement* deleteButtonContainerElement = 0;
-#if ENABLE(DELETION_UI)
- if (Frame* frame = node.document().frame()) {
- deleteButtonContainerElement = frame->editor().deleteButtonController().containerElement();
- if (node.isDescendantOf(deleteButtonContainerElement))
- return emptyString();
- }
-#endif
-
MarkupAccumulator accumulator(nodes, shouldResolveURLs, 0, fragmentSerialization);
- return accumulator.serializeNodes(const_cast<Node&>(node), deleteButtonContainerElement, childrenOnly, tagNamesToSkip);
+ return accumulator.serializeNodes(const_cast<Node&>(node), childrenOnly, tagNamesToSkip);
}
-static void fillContainerFromString(ContainerNode* paragraph, const String& string)
+static void fillContainerFromString(ContainerNode& paragraph, const String& string)
{
- Document& document = paragraph->document();
+ Document& document = paragraph.document();
if (string.isEmpty()) {
- paragraph->appendChild(createBlockPlaceholderElement(document), ASSERT_NO_EXCEPTION);
+ paragraph.appendChild(createBlockPlaceholderElement(document));
return;
}
@@ -696,11 +726,11 @@ static void fillContainerFromString(ContainerNode* paragraph, const String& stri
// append the non-tab textual part
if (!s.isEmpty()) {
if (!tabText.isEmpty()) {
- paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION);
+ paragraph.appendChild(createTabSpanElement(document, tabText));
tabText = emptyString();
}
- RefPtr<Node> textNode = document.createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
- paragraph->appendChild(textNode.release(), ASSERT_NO_EXCEPTION);
+ Ref<Node> textNode = document.createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
+ paragraph.appendChild(textNode);
}
// there is a tab after every entry, except the last entry
@@ -708,21 +738,34 @@ static void fillContainerFromString(ContainerNode* paragraph, const String& stri
if (i + 1 != numEntries)
tabText.append('\t');
else if (!tabText.isEmpty())
- paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION);
+ paragraph.appendChild(createTabSpanElement(document, tabText));
first = false;
}
}
-bool isPlainTextMarkup(Node *node)
+bool isPlainTextMarkup(Node* node)
{
- if (!node->isElementNode() || !node->hasTagName(divTag) || toElement(node)->hasAttributes())
+ ASSERT(node);
+ if (!is<HTMLDivElement>(*node))
+ return false;
+
+ HTMLDivElement& element = downcast<HTMLDivElement>(*node);
+ if (element.hasAttributes())
+ return false;
+
+ Node* firstChild = element.firstChild();
+ if (!firstChild)
return false;
+
+ Node* secondChild = firstChild->nextSibling();
+ if (!secondChild)
+ return firstChild->isTextNode() || firstChild->firstChild();
- if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild())))
- return true;
+ if (secondChild->nextSibling())
+ return false;
- return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode());
+ return isTabSpanTextNode(firstChild->firstChild()) && secondChild->isTextNode();
}
static bool contextPreservesNewline(const Range& context)
@@ -735,37 +778,37 @@ static bool contextPreservesNewline(const Range& context)
return container->renderer()->style().preserveNewline();
}
-PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String& text)
+Ref<DocumentFragment> createFragmentFromText(Range& context, const String& text)
{
Document& document = context.ownerDocument();
- RefPtr<DocumentFragment> fragment = document.createDocumentFragment();
+ Ref<DocumentFragment> fragment = document.createDocumentFragment();
if (text.isEmpty())
- return fragment.release();
+ return fragment;
String string = text;
string.replace("\r\n", "\n");
string.replace('\r', '\n');
if (contextPreservesNewline(context)) {
- fragment->appendChild(document.createTextNode(string), ASSERT_NO_EXCEPTION);
+ fragment->appendChild(document.createTextNode(string));
if (string.endsWith('\n')) {
- RefPtr<Element> element = createBreakElement(document);
- element->setAttribute(classAttr, AppleInterchangeNewline);
- fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION);
+ auto element = HTMLBRElement::create(document);
+ element->setAttributeWithoutSynchronization(classAttr, AppleInterchangeNewline);
+ fragment->appendChild(element);
}
- return fragment.release();
+ return fragment;
}
// A string with no newlines gets added inline, rather than being put into a paragraph.
if (string.find('\n') == notFound) {
- fillContainerFromString(fragment.get(), string);
- return fragment.release();
+ fillContainerFromString(fragment, string);
+ return fragment;
}
// Break string into paragraphs. Extra line breaks turn into empty paragraphs.
Node* blockNode = enclosingBlock(context.firstNode());
- Element* block = toElement(blockNode);
+ Element* block = downcast<Element>(blockNode);
bool useClonesOfEnclosingBlock = blockNode
&& blockNode->isElementNode()
&& !block->hasTagName(bodyTag)
@@ -782,21 +825,21 @@ PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String
RefPtr<Element> element;
if (s.isEmpty() && i + 1 == numLines) {
// For last line, use the "magic BR" rather than a P.
- element = createBreakElement(document);
- element->setAttribute(classAttr, AppleInterchangeNewline);
+ element = HTMLBRElement::create(document);
+ element->setAttributeWithoutSynchronization(classAttr, AppleInterchangeNewline);
} else if (useLineBreak) {
- element = createBreakElement(document);
- fillContainerFromString(fragment.get(), s);
+ element = HTMLBRElement::create(document);
+ fillContainerFromString(fragment, s);
} else {
if (useClonesOfEnclosingBlock)
- element = block->cloneElementWithoutChildren();
+ element = block->cloneElementWithoutChildren(document);
else
element = createDefaultParagraphElement(document);
- fillContainerFromString(element.get(), s);
+ fillContainerFromString(*element, s);
}
- fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION);
+ fragment->appendChild(*element);
}
- return fragment.release();
+ return fragment;
}
String documentTypeString(const Document& document)
@@ -821,81 +864,73 @@ String createFullMarkup(const Node& node)
String createFullMarkup(const Range& range)
{
- Node* node = range.startContainer();
- if (!node)
- return String();
-
// FIXME: This is always "for interchange". Is that right?
- return documentTypeString(node->document()) + createMarkup(range, 0, AnnotateForInterchange);
+ return documentTypeString(range.startContainer().document()) + createMarkup(range, 0, AnnotateForInterchange);
}
String urlToMarkup(const URL& url, const String& title)
{
StringBuilder markup;
- markup.append("<a href=\"");
+ markup.appendLiteral("<a href=\"");
markup.append(url.string());
- markup.append("\">");
+ markup.appendLiteral("\">");
MarkupAccumulator::appendCharactersReplacingEntities(markup, title, 0, title.length(), EntityMaskInPCDATA);
- markup.append("</a>");
+ markup.appendLiteral("</a>");
return markup.toString();
}
-PassRefPtr<DocumentFragment> createFragmentForInnerOuterHTML(const String& markup, Element* contextElement, ParserContentPolicy parserContentPolicy, ExceptionCode& ec)
+ExceptionOr<Ref<DocumentFragment>> createFragmentForInnerOuterHTML(Element& contextElement, const String& markup, ParserContentPolicy parserContentPolicy)
{
- Document* document = &contextElement->document();
-#if ENABLE(TEMPLATE_ELEMENT)
- if (contextElement->hasTagName(templateTag))
- document = document->ensureTemplateDocument();
-#endif
- RefPtr<DocumentFragment> fragment = DocumentFragment::create(*document);
+ auto* document = &contextElement.document();
+ if (contextElement.hasTagName(templateTag))
+ document = &document->ensureTemplateDocument();
+ auto fragment = DocumentFragment::create(*document);
if (document->isHTMLDocument()) {
- fragment->parseHTML(markup, contextElement, parserContentPolicy);
- return fragment;
+ fragment->parseHTML(markup, &contextElement, parserContentPolicy);
+ return WTFMove(fragment);
}
- bool wasValid = fragment->parseXML(markup, contextElement, parserContentPolicy);
- if (!wasValid) {
- ec = SYNTAX_ERR;
- return 0;
- }
- return fragment.release();
+ bool wasValid = fragment->parseXML(markup, &contextElement, parserContentPolicy);
+ if (!wasValid)
+ return Exception { SYNTAX_ERR };
+ return WTFMove(fragment);
}
-PassRefPtr<DocumentFragment> createFragmentForTransformToFragment(const String& sourceString, const String& sourceMIMEType, Document* outputDoc)
+RefPtr<DocumentFragment> createFragmentForTransformToFragment(Document& outputDoc, const String& sourceString, const String& sourceMIMEType)
{
- RefPtr<DocumentFragment> fragment = outputDoc->createDocumentFragment();
+ RefPtr<DocumentFragment> fragment = outputDoc.createDocumentFragment();
if (sourceMIMEType == "text/html") {
// As far as I can tell, there isn't a spec for how transformToFragment is supposed to work.
// Based on the documentation I can find, it looks like we want to start parsing the fragment in the InBody insertion mode.
// Unfortunately, that's an implementation detail of the parser.
// We achieve that effect here by passing in a fake body element as context for the fragment.
- RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(*outputDoc);
+ RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(outputDoc);
fragment->parseHTML(sourceString, fakeBody.get());
} else if (sourceMIMEType == "text/plain")
- fragment->parserAppendChild(Text::create(*outputDoc, sourceString));
+ fragment->parserAppendChild(Text::create(outputDoc, sourceString));
else {
bool successfulParse = fragment->parseXML(sourceString, 0);
if (!successfulParse)
- return 0;
+ return nullptr;
}
// FIXME: Do we need to mess with URLs here?
- return fragment.release();
+ return fragment;
}
static Vector<Ref<HTMLElement>> collectElementsToRemoveFromFragment(ContainerNode& container)
{
Vector<Ref<HTMLElement>> toRemove;
for (auto& element : childrenOfType<HTMLElement>(container)) {
- if (isHTMLHtmlElement(element)) {
+ if (is<HTMLHtmlElement>(element)) {
toRemove.append(element);
collectElementsToRemoveFromFragment(element);
continue;
}
- if (isHTMLHeadElement(element) || isHTMLBodyElement(element))
+ if (is<HTMLHeadElement>(element) || is<HTMLBodyElement>(element))
toRemove.append(element);
}
return toRemove;
@@ -906,94 +941,80 @@ static void removeElementFromFragmentPreservingChildren(DocumentFragment& fragme
RefPtr<Node> nextChild;
for (RefPtr<Node> child = element.firstChild(); child; child = nextChild) {
nextChild = child->nextSibling();
- element.removeChild(child.get(), ASSERT_NO_EXCEPTION);
- fragment.insertBefore(child, &element, ASSERT_NO_EXCEPTION);
+ element.removeChild(*child);
+ fragment.insertBefore(*child, &element);
}
- fragment.removeChild(&element, ASSERT_NO_EXCEPTION);
+ fragment.removeChild(element);
}
-PassRefPtr<DocumentFragment> createContextualFragment(const String& markup, HTMLElement* element, ParserContentPolicy parserContentPolicy, ExceptionCode& ec)
+ExceptionOr<Ref<DocumentFragment>> createContextualFragment(Element& element, const String& markup, ParserContentPolicy parserContentPolicy)
{
- ASSERT(element);
- if (element->ieForbidsInsertHTML()) {
- ec = NOT_SUPPORTED_ERR;
- return 0;
- }
-
- if (element->hasLocalName(colTag) || element->hasLocalName(colgroupTag) || element->hasLocalName(framesetTag)
- || element->hasLocalName(headTag) || element->hasLocalName(styleTag) || element->hasLocalName(titleTag)) {
- ec = NOT_SUPPORTED_ERR;
- return 0;
- }
+ auto result = createFragmentForInnerOuterHTML(element, markup, parserContentPolicy);
+ if (result.hasException())
+ return result.releaseException();
- RefPtr<DocumentFragment> fragment = createFragmentForInnerOuterHTML(markup, element, parserContentPolicy, ec);
- if (!fragment)
- return 0;
+ auto fragment = result.releaseReturnValue();
// We need to pop <html> and <body> elements and remove <head> to
// accommodate folks passing complete HTML documents to make the
// child of an element.
- auto toRemove = collectElementsToRemoveFromFragment(*fragment);
- for (unsigned i = 0; i < toRemove.size(); ++i)
- removeElementFromFragmentPreservingChildren(*fragment, toRemove[i].get());
+ auto toRemove = collectElementsToRemoveFromFragment(fragment);
+ for (auto& element : toRemove)
+ removeElementFromFragmentPreservingChildren(fragment, element);
- return fragment.release();
+ return WTFMove(fragment);
}
-static inline bool hasOneChild(ContainerNode* node)
+static inline bool hasOneChild(ContainerNode& node)
{
- Node* firstChild = node->firstChild();
+ Node* firstChild = node.firstChild();
return firstChild && !firstChild->nextSibling();
}
-static inline bool hasOneTextChild(ContainerNode* node)
+static inline bool hasOneTextChild(ContainerNode& node)
{
- return hasOneChild(node) && node->firstChild()->isTextNode();
+ return hasOneChild(node) && node.firstChild()->isTextNode();
}
-void replaceChildrenWithFragment(ContainerNode& container, PassRefPtr<DocumentFragment> fragment, ExceptionCode& ec)
+static inline bool hasMutationEventListeners(const Document& document)
{
- Ref<ContainerNode> containerNode(container);
- ChildListMutationScope mutation(containerNode.get());
-
- if (!fragment->firstChild()) {
- containerNode->removeChildren();
- return;
- }
-
- if (hasOneTextChild(&containerNode.get()) && hasOneTextChild(fragment.get())) {
- toText(containerNode->firstChild())->setData(toText(fragment->firstChild())->data(), ec);
- return;
- }
-
- if (hasOneChild(&containerNode.get())) {
- containerNode->replaceChild(fragment, containerNode->firstChild(), ec);
- return;
- }
+ return document.hasListenerType(Document::DOMSUBTREEMODIFIED_LISTENER)
+ || document.hasListenerType(Document::DOMNODEINSERTED_LISTENER)
+ || document.hasListenerType(Document::DOMNODEREMOVED_LISTENER)
+ || document.hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)
+ || document.hasListenerType(Document::DOMCHARACTERDATAMODIFIED_LISTENER);
+}
- containerNode->removeChildren();
- containerNode->appendChild(fragment, ec);
+// We can use setData instead of replacing Text node as long as script can't observe the difference.
+static inline bool canUseSetDataOptimization(const Text& containerChild, const ChildListMutationScope& mutationScope)
+{
+ bool authorScriptMayHaveReference = containerChild.refCount();
+ return !authorScriptMayHaveReference && !mutationScope.canObserve() && !hasMutationEventListeners(containerChild.document());
}
-void replaceChildrenWithText(ContainerNode& container, const String& text, ExceptionCode& ec)
+ExceptionOr<void> replaceChildrenWithFragment(ContainerNode& container, Ref<DocumentFragment>&& fragment)
{
Ref<ContainerNode> containerNode(container);
- ChildListMutationScope mutation(containerNode.get());
+ ChildListMutationScope mutation(containerNode);
- if (hasOneTextChild(&containerNode.get())) {
- toText(containerNode->firstChild())->setData(text, ec);
- return;
+ if (!fragment->firstChild()) {
+ containerNode->removeChildren();
+ return { };
}
- RefPtr<Text> textNode = Text::create(containerNode->document(), text);
+ auto* containerChild = containerNode->firstChild();
+ if (containerChild && !containerChild->nextSibling()) {
+ if (is<Text>(*containerChild) && hasOneTextChild(fragment) && canUseSetDataOptimization(downcast<Text>(*containerChild), mutation)) {
+ ASSERT(!fragment->firstChild()->refCount());
+ downcast<Text>(*containerChild).setData(downcast<Text>(*fragment->firstChild()).data());
+ return { };
+ }
- if (hasOneChild(&containerNode.get())) {
- containerNode->replaceChild(textNode.release(), containerNode->firstChild(), ec);
- return;
+ return containerNode->replaceChild(fragment, *containerChild);
}
containerNode->removeChildren();
- containerNode->appendChild(textNode.release(), ec);
+ return containerNode->appendChild(fragment);
}
}