diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing/markup.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/editing/markup.cpp')
-rw-r--r-- | Source/WebCore/editing/markup.cpp | 483 |
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 = ⦥ - -#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); } } |