1/* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2013 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009, 2010, 2011 Google Inc. All rights reserved. 4 * Copyright (C) 2011 Igalia S.L. 5 * Copyright (C) 2011 Motorola Mobility. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include "config.h" 30#include "markup.h" 31 32#include "CDATASection.h" 33#include "CSSPrimitiveValue.h" 34#include "CSSPropertyNames.h" 35#include "CSSRule.h" 36#include "CSSRuleList.h" 37#include "CSSStyleRule.h" 38#include "CSSValue.h" 39#include "CSSValueKeywords.h" 40#include "ChildListMutationScope.h" 41#include "ContextFeatures.h" 42#if ENABLE(DELETION_UI) 43#include "DeleteButtonController.h" 44#endif 45#include "DocumentFragment.h" 46#include "DocumentType.h" 47#include "Editor.h" 48#include "ExceptionCode.h" 49#include "ExceptionCodePlaceholder.h" 50#include "Frame.h" 51#include "HTMLBodyElement.h" 52#include "HTMLElement.h" 53#include "HTMLNames.h" 54#include "HTMLTextFormControlElement.h" 55#include "KURL.h" 56#include "MarkupAccumulator.h" 57#include "NodeTraversal.h" 58#include "Range.h" 59#include "RenderBlock.h" 60#include "RenderObject.h" 61#include "Settings.h" 62#include "StylePropertySet.h" 63#include "StyleResolver.h" 64#include "TextIterator.h" 65#include "VisibleSelection.h" 66#include "VisibleUnits.h" 67#include "XMLNSNames.h" 68#include "htmlediting.h" 69#include <wtf/StdLibExtras.h> 70#include <wtf/text/StringBuilder.h> 71#include <wtf/unicode/CharacterNames.h> 72 73using namespace std; 74 75namespace WebCore { 76 77using namespace HTMLNames; 78 79static bool propertyMissingOrEqualToNone(StylePropertySet*, CSSPropertyID); 80 81class AttributeChange { 82public: 83 AttributeChange() 84 : m_name(nullAtom, nullAtom, nullAtom) 85 { 86 } 87 88 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value) 89 : m_element(element), m_name(name), m_value(value) 90 { 91 } 92 93 void apply() 94 { 95 m_element->setAttribute(m_name, m_value); 96 } 97 98private: 99 RefPtr<Element> m_element; 100 QualifiedName m_name; 101 String m_value; 102}; 103 104static void completeURLs(DocumentFragment* fragment, const String& baseURL) 105{ 106 Vector<AttributeChange> changes; 107 108 KURL parsedBaseURL(ParsedURLString, baseURL); 109 110 for (Element* element = ElementTraversal::firstWithin(fragment); element; element = ElementTraversal::next(element, fragment)) { 111 if (!element->hasAttributes()) 112 continue; 113 unsigned length = element->attributeCount(); 114 for (unsigned i = 0; i < length; i++) { 115 const Attribute* attribute = element->attributeItem(i); 116 if (element->isURLAttribute(*attribute) && !attribute->value().isEmpty()) 117 changes.append(AttributeChange(element, attribute->name(), KURL(parsedBaseURL, attribute->value()).string())); 118 } 119 } 120 121 size_t numChanges = changes.size(); 122 for (size_t i = 0; i < numChanges; ++i) 123 changes[i].apply(); 124} 125 126class StyledMarkupAccumulator : public MarkupAccumulator { 127public: 128 enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode }; 129 130 StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs, EAnnotateForInterchange, const Range*, Node* highestNodeToBeSerialized = 0); 131 Node* serializeNodes(Node* startNode, Node* pastEnd); 132 virtual void appendString(const String& s) { return MarkupAccumulator::appendString(s); } 133 void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); 134 void wrapWithStyleNode(StylePropertySet*, Document*, bool isBlock = false); 135 String takeResults(); 136 137private: 138 void appendStyleNodeOpenTag(StringBuilder&, StylePropertySet*, Document*, bool isBlock = false); 139 const String& styleNodeCloseTag(bool isBlock = false); 140 virtual void appendText(StringBuilder& out, Text*); 141 String renderedText(const Node*, const Range*); 142 String stringValueForRange(const Node*, const Range*); 143 void appendElement(StringBuilder& out, Element*, bool addDisplayInline, RangeFullySelectsNode); 144 void appendElement(StringBuilder& out, Element* element, Namespaces*) { appendElement(out, element, false, DoesFullySelectNode); } 145 146 enum NodeTraversalMode { EmitString, DoNotEmitString }; 147 Node* traverseNodesForSerialization(Node* startNode, Node* pastEnd, NodeTraversalMode); 148 149 bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; } 150 bool shouldApplyWrappingStyle(Node* node) const 151 { 152 return m_highestNodeToBeSerialized && m_highestNodeToBeSerialized->parentNode() == node->parentNode() 153 && m_wrappingStyle && m_wrappingStyle->style(); 154 } 155 156 Vector<String> m_reversedPrecedingMarkup; 157 const EAnnotateForInterchange m_shouldAnnotate; 158 Node* m_highestNodeToBeSerialized; 159 RefPtr<EditingStyle> m_wrappingStyle; 160}; 161 162inline StyledMarkupAccumulator::StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, 163 const Range* range, Node* highestNodeToBeSerialized) 164 : MarkupAccumulator(nodes, shouldResolveURLs, range) 165 , m_shouldAnnotate(shouldAnnotate) 166 , m_highestNodeToBeSerialized(highestNodeToBeSerialized) 167{ 168} 169 170void StyledMarkupAccumulator::wrapWithNode(Node* node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode) 171{ 172 StringBuilder markup; 173 if (node->isElementNode()) 174 appendElement(markup, toElement(node), convertBlocksToInlines && isBlock(const_cast<Node*>(node)), rangeFullySelectsNode); 175 else 176 appendStartMarkup(markup, node, 0); 177 m_reversedPrecedingMarkup.append(markup.toString()); 178 appendEndTag(node); 179 if (m_nodes) 180 m_nodes->append(node); 181} 182 183void StyledMarkupAccumulator::wrapWithStyleNode(StylePropertySet* style, Document* document, bool isBlock) 184{ 185 StringBuilder openTag; 186 appendStyleNodeOpenTag(openTag, style, document, isBlock); 187 m_reversedPrecedingMarkup.append(openTag.toString()); 188 appendString(styleNodeCloseTag(isBlock)); 189} 190 191void StyledMarkupAccumulator::appendStyleNodeOpenTag(StringBuilder& out, StylePropertySet* style, Document* document, bool isBlock) 192{ 193 // wrappingStyleForSerialization should have removed -webkit-text-decorations-in-effect 194 ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect)); 195 if (isBlock) 196 out.appendLiteral("<div style=\""); 197 else 198 out.appendLiteral("<span style=\""); 199 appendAttributeValue(out, style->asText(), document->isHTMLDocument()); 200 out.appendLiteral("\">"); 201} 202 203const String& StyledMarkupAccumulator::styleNodeCloseTag(bool isBlock) 204{ 205 DEFINE_STATIC_LOCAL(const String, divClose, (ASCIILiteral("</div>"))); 206 DEFINE_STATIC_LOCAL(const String, styleSpanClose, (ASCIILiteral("</span>"))); 207 return isBlock ? divClose : styleSpanClose; 208} 209 210String StyledMarkupAccumulator::takeResults() 211{ 212 StringBuilder result; 213 result.reserveCapacity(totalLength(m_reversedPrecedingMarkup) + length()); 214 215 for (size_t i = m_reversedPrecedingMarkup.size(); i > 0; --i) 216 result.append(m_reversedPrecedingMarkup[i - 1]); 217 218 concatenateMarkup(result); 219 220 // We remove '\0' characters because they are not visibly rendered to the user. 221 return result.toString().replaceWithLiteral('\0', ""); 222} 223 224void StyledMarkupAccumulator::appendText(StringBuilder& out, Text* text) 225{ 226 const bool parentIsTextarea = text->parentElement() && text->parentElement()->tagQName() == textareaTag; 227 const bool wrappingSpan = shouldApplyWrappingStyle(text) && !parentIsTextarea; 228 if (wrappingSpan) { 229 RefPtr<EditingStyle> wrappingStyle = m_wrappingStyle->copy(); 230 // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance 231 // Make sure spans are inline style in paste side e.g. span { display: block }. 232 wrappingStyle->forceInline(); 233 // FIXME: Should this be included in forceInline? 234 wrappingStyle->style()->setProperty(CSSPropertyFloat, CSSValueNone); 235 236 appendStyleNodeOpenTag(out, wrappingStyle->style(), text->document()); 237 } 238 239 if (!shouldAnnotate() || parentIsTextarea) 240 MarkupAccumulator::appendText(out, text); 241 else { 242 const bool useRenderedText = !enclosingNodeWithTag(firstPositionInNode(text), selectTag); 243 String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range); 244 StringBuilder buffer; 245 appendCharactersReplacingEntities(buffer, content, 0, content.length(), EntityMaskInPCDATA); 246 out.append(convertHTMLTextToInterchangeFormat(buffer.toString(), text)); 247 } 248 249 if (wrappingSpan) 250 out.append(styleNodeCloseTag()); 251} 252 253String StyledMarkupAccumulator::renderedText(const Node* node, const Range* range) 254{ 255 if (!node->isTextNode()) 256 return String(); 257 258 const Text* textNode = static_cast<const Text*>(node); 259 unsigned startOffset = 0; 260 unsigned endOffset = textNode->length(); 261 262 if (range && node == range->startContainer()) 263 startOffset = range->startOffset(); 264 if (range && node == range->endContainer()) 265 endOffset = range->endOffset(); 266 267 Position start = createLegacyEditingPosition(const_cast<Node*>(node), startOffset); 268 Position end = createLegacyEditingPosition(const_cast<Node*>(node), endOffset); 269 return plainText(Range::create(node->document(), start, end).get()); 270} 271 272String StyledMarkupAccumulator::stringValueForRange(const Node* node, const Range* range) 273{ 274 if (!range) 275 return node->nodeValue(); 276 277 String str = node->nodeValue(); 278 if (node == range->endContainer()) 279 str.truncate(range->endOffset()); 280 if (node == range->startContainer()) 281 str.remove(0, range->startOffset()); 282 return str; 283} 284 285void StyledMarkupAccumulator::appendElement(StringBuilder& out, Element* element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode) 286{ 287 const bool documentIsHTML = element->document()->isHTMLDocument(); 288 appendOpenTag(out, element, 0); 289 290 const unsigned length = element->hasAttributes() ? element->attributeCount() : 0; 291 const bool shouldAnnotateOrForceInline = element->isHTMLElement() && (shouldAnnotate() || addDisplayInline); 292 const bool shouldOverrideStyleAttr = shouldAnnotateOrForceInline || shouldApplyWrappingStyle(element); 293 for (unsigned i = 0; i < length; ++i) { 294 const Attribute* attribute = element->attributeItem(i); 295 // We'll handle the style attribute separately, below. 296 if (attribute->name() == styleAttr && shouldOverrideStyleAttr) 297 continue; 298 appendAttribute(out, element, *attribute, 0); 299 } 300 301 if (shouldOverrideStyleAttr) { 302 RefPtr<EditingStyle> newInlineStyle; 303 304 if (shouldApplyWrappingStyle(element)) { 305 newInlineStyle = m_wrappingStyle->copy(); 306 newInlineStyle->removePropertiesInElementDefaultStyle(element); 307 newInlineStyle->removeStyleConflictingWithStyleOfNode(element); 308 } else 309 newInlineStyle = EditingStyle::create(); 310 311 if (element->isStyledElement() && static_cast<StyledElement*>(element)->inlineStyle()) 312 newInlineStyle->overrideWithStyle(static_cast<StyledElement*>(element)->inlineStyle()); 313 314 if (shouldAnnotateOrForceInline) { 315 if (shouldAnnotate()) 316 newInlineStyle->mergeStyleFromRulesForSerialization(toHTMLElement(element)); 317 318 if (addDisplayInline) 319 newInlineStyle->forceInline(); 320 321 // 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 322 // only the ones that affect it and the nodes within it. 323 if (rangeFullySelectsNode == DoesNotFullySelectNode && newInlineStyle->style()) 324 newInlineStyle->style()->removeProperty(CSSPropertyFloat); 325 } 326 327 if (!newInlineStyle->isEmpty()) { 328 out.appendLiteral(" style=\""); 329 appendAttributeValue(out, newInlineStyle->style()->asText(), documentIsHTML); 330 out.append('\"'); 331 } 332 } 333 334 appendCloseTag(out, element); 335} 336 337Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) 338{ 339 if (!m_highestNodeToBeSerialized) { 340 Node* lastClosed = traverseNodesForSerialization(startNode, pastEnd, DoNotEmitString); 341 m_highestNodeToBeSerialized = lastClosed; 342 } 343 344 if (m_highestNodeToBeSerialized && m_highestNodeToBeSerialized->parentNode()) 345 m_wrappingStyle = EditingStyle::wrappingStyleForSerialization(m_highestNodeToBeSerialized->parentNode(), shouldAnnotate()); 346 347 return traverseNodesForSerialization(startNode, pastEnd, EmitString); 348} 349 350Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, Node* pastEnd, NodeTraversalMode traversalMode) 351{ 352 const bool shouldEmit = traversalMode == EmitString; 353 Vector<Node*> ancestorsToClose; 354 Node* next; 355 Node* lastClosed = 0; 356 for (Node* n = startNode; n != pastEnd; n = next) { 357 // According to <rdar://problem/5730668>, it is possible for n to blow 358 // past pastEnd and become null here. This shouldn't be possible. 359 // This null check will prevent crashes (but create too much markup) 360 // and the ASSERT will hopefully lead us to understanding the problem. 361 ASSERT(n); 362 if (!n) 363 break; 364 365 next = NodeTraversal::next(n); 366 bool openedTag = false; 367 368 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) 369 // Don't write out empty block containers that aren't fully selected. 370 continue; 371 372 if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) { 373 next = NodeTraversal::nextSkippingChildren(n); 374 // Don't skip over pastEnd. 375 if (pastEnd && pastEnd->isDescendantOf(n)) 376 next = pastEnd; 377 } else { 378 // Add the node to the markup if we're not skipping the descendants 379 if (shouldEmit) 380 appendStartTag(n); 381 382 // If node has no children, close the tag now. 383 if (!n->childNodeCount()) { 384 if (shouldEmit) 385 appendEndTag(n); 386 lastClosed = n; 387 } else { 388 openedTag = true; 389 ancestorsToClose.append(n); 390 } 391 } 392 393 // If we didn't insert open tag and there's no more siblings or we're at the end of the traversal, take care of ancestors. 394 // FIXME: What happens if we just inserted open tag and reached the end? 395 if (!openedTag && (!n->nextSibling() || next == pastEnd)) { 396 // Close up the ancestors. 397 while (!ancestorsToClose.isEmpty()) { 398 Node* ancestor = ancestorsToClose.last(); 399 if (next != pastEnd && next->isDescendantOf(ancestor)) 400 break; 401 // Not at the end of the range, close ancestors up to sibling of next node. 402 if (shouldEmit) 403 appendEndTag(ancestor); 404 lastClosed = ancestor; 405 ancestorsToClose.removeLast(); 406 } 407 408 // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. 409 ContainerNode* nextParent = next ? next->parentNode() : 0; 410 if (next != pastEnd && n != nextParent) { 411 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; 412 for (ContainerNode* parent = lastAncestorClosedOrSelf->parentNode(); parent && parent != nextParent; parent = parent->parentNode()) { 413 // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: 414 if (!parent->renderer()) 415 continue; 416 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: 417 ASSERT(startNode->isDescendantOf(parent)); 418 if (shouldEmit) 419 wrapWithNode(parent); 420 lastClosed = parent; 421 } 422 } 423 } 424 } 425 426 return lastClosed; 427} 428 429static bool isHTMLBlockElement(const Node* node) 430{ 431 return node->hasTagName(tdTag) 432 || node->hasTagName(thTag) 433 || isNonTableCellHTMLBlockElement(node); 434} 435 436static Node* ancestorToRetainStructureAndAppearanceForBlock(Node* commonAncestorBlock) 437{ 438 if (!commonAncestorBlock) 439 return 0; 440 441 if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { 442 ContainerNode* table = commonAncestorBlock->parentNode(); 443 while (table && !table->hasTagName(tableTag)) 444 table = table->parentNode(); 445 446 return table; 447 } 448 449 if (isNonTableCellHTMLBlockElement(commonAncestorBlock)) 450 return commonAncestorBlock; 451 452 return 0; 453} 454 455static inline Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor) 456{ 457 return ancestorToRetainStructureAndAppearanceForBlock(enclosingBlock(commonAncestor)); 458} 459 460static inline Node* ancestorToRetainStructureAndAppearanceWithNoRenderer(Node* commonAncestor) 461{ 462 Node* commonAncestorBlock = enclosingNodeOfType(firstPositionInOrBeforeNode(commonAncestor), isHTMLBlockElement); 463 return ancestorToRetainStructureAndAppearanceForBlock(commonAncestorBlock); 464} 465 466static bool propertyMissingOrEqualToNone(StylePropertySet* style, CSSPropertyID propertyID) 467{ 468 if (!style) 469 return false; 470 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); 471 if (!value) 472 return true; 473 if (!value->isPrimitiveValue()) 474 return false; 475 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone; 476} 477 478static bool needInterchangeNewlineAfter(const VisiblePosition& v) 479{ 480 VisiblePosition next = v.next(); 481 Node* upstreamNode = next.deepEquivalent().upstream().deprecatedNode(); 482 Node* downstreamNode = v.deepEquivalent().downstream().deprecatedNode(); 483 // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it. 484 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode); 485} 486 487static PassRefPtr<EditingStyle> styleFromMatchedRulesAndInlineDecl(const Node* node) 488{ 489 if (!node->isHTMLElement()) 490 return 0; 491 492 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle 493 // the non-const-ness of styleFromMatchedRulesForElement. 494 HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node)); 495 RefPtr<EditingStyle> style = EditingStyle::create(element->inlineStyle()); 496 style->mergeStyleFromRules(element); 497 return style.release(); 498} 499 500static bool isElementPresentational(const Node* node) 501{ 502 return node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag) 503 || node->hasTagName(iTag) || node->hasTagName(emTag) || node->hasTagName(bTag) || node->hasTagName(strongTag); 504} 505 506static Node* highestAncestorToWrapMarkup(const Range* range, EAnnotateForInterchange shouldAnnotate) 507{ 508 Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION); 509 ASSERT(commonAncestor); 510 Node* specialCommonAncestor = 0; 511 if (shouldAnnotate == AnnotateForInterchange) { 512 // Include ancestors that aren't completely inside the range but are required to retain 513 // the structure and appearance of the copied markup. 514 specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor); 515 516 if (Node* parentListNode = enclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isListItem)) { 517 if (WebCore::areRangesEqual(VisibleSelection::selectionFromContentsOfNode(parentListNode).toNormalizedRange().get(), range)) { 518 specialCommonAncestor = parentListNode->parentNode(); 519 while (specialCommonAncestor && !isListElement(specialCommonAncestor)) 520 specialCommonAncestor = specialCommonAncestor->parentNode(); 521 } 522 } 523 524 // Retain the Mail quote level by including all ancestor mail block quotes. 525 if (Node* highestMailBlockquote = highestEnclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isMailBlockquote, CanCrossEditingBoundary)) 526 specialCommonAncestor = highestMailBlockquote; 527 } 528 529 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; 530 if (checkAncestor->renderer() && checkAncestor->renderer()->containingBlock()) { 531 Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(firstPositionInNode(checkAncestor), &isElementPresentational, CanCrossEditingBoundary, checkAncestor->renderer()->containingBlock()->node()); 532 if (newSpecialCommonAncestor) 533 specialCommonAncestor = newSpecialCommonAncestor; 534 } 535 536 // If a single tab is selected, commonAncestor will be a text node inside a tab span. 537 // If two or more tabs are selected, commonAncestor will be the tab span. 538 // In either case, if there is a specialCommonAncestor already, it will necessarily be above 539 // any tab span that needs to be included. 540 if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor)) 541 specialCommonAncestor = commonAncestor->parentNode(); 542 if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) 543 specialCommonAncestor = commonAncestor; 544 545 if (Node *enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag)) 546 specialCommonAncestor = enclosingAnchor; 547 548 return specialCommonAncestor; 549} 550 551// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 552// FIXME: At least, annotation and style info should probably not be included in range.markupString() 553static String createMarkupInternal(Document* document, const Range* range, const Range* updatedRange, Vector<Node*>* nodes, 554 EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) 555{ 556 ASSERT(document); 557 ASSERT(range); 558 ASSERT(updatedRange); 559 DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, (ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">"))); 560 561 bool collapsed = updatedRange->collapsed(ASSERT_NO_EXCEPTION); 562 if (collapsed) 563 return emptyString(); 564 Node* commonAncestor = updatedRange->commonAncestorContainer(ASSERT_NO_EXCEPTION); 565 if (!commonAncestor) 566 return emptyString(); 567 568 document->updateLayoutIgnorePendingStylesheets(); 569 570 Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag); 571 Node* fullySelectedRoot = 0; 572 // FIXME: Do this for all fully selected blocks, not just the body. 573 if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), range)) 574 fullySelectedRoot = body; 575 Node* specialCommonAncestor = highestAncestorToWrapMarkup(updatedRange, shouldAnnotate); 576 StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, updatedRange, specialCommonAncestor); 577 Node* pastEnd = updatedRange->pastLastNode(); 578 579 Node* startNode = updatedRange->firstNode(); 580 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY); 581 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY); 582 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) { 583 if (visibleStart == visibleEnd.previous()) 584 return interchangeNewlineString; 585 586 accumulator.appendString(interchangeNewlineString); 587 startNode = visibleStart.next().deepEquivalent().deprecatedNode(); 588 589 if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ASSERT_NO_EXCEPTION) >= 0) 590 return interchangeNewlineString; 591 } 592 593 Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); 594 595 if (specialCommonAncestor && lastClosed) { 596 // Also include all of the ancestors of lastClosed up to this special ancestor. 597 for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { 598 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { 599 RefPtr<EditingStyle> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); 600 601 // Bring the background attribute over, but not as an attribute because a background attribute on a div 602 // appears to have no effect. 603 if ((!fullySelectedRootStyle || !fullySelectedRootStyle->style() || !fullySelectedRootStyle->style()->getPropertyCSSValue(CSSPropertyBackgroundImage)) 604 && toElement(fullySelectedRoot)->hasAttribute(backgroundAttr)) 605 fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + toElement(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); 606 607 if (fullySelectedRootStyle->style()) { 608 // Reset the CSS properties to avoid an assertion error in addStyleMarkup(). 609 // This assertion is caused at least when we select all text of a <body> element whose 610 // 'text-decoration' property is "inherit", and copy it. 611 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->style(), CSSPropertyTextDecoration)) 612 fullySelectedRootStyle->style()->setProperty(CSSPropertyTextDecoration, CSSValueNone); 613 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->style(), CSSPropertyWebkitTextDecorationsInEffect)) 614 fullySelectedRootStyle->style()->setProperty(CSSPropertyWebkitTextDecorationsInEffect, CSSValueNone); 615 accumulator.wrapWithStyleNode(fullySelectedRootStyle->style(), document, true); 616 } 617 } else { 618 // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode 619 // so that styles that affect the exterior of the node are not included. 620 accumulator.wrapWithNode(ancestor, convertBlocksToInlines, StyledMarkupAccumulator::DoesNotFullySelectNode); 621 } 622 if (nodes) 623 nodes->append(ancestor); 624 625 lastClosed = ancestor; 626 627 if (ancestor == specialCommonAncestor) 628 break; 629 } 630 } 631 632 // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. 633 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous())) 634 accumulator.appendString(interchangeNewlineString); 635 636 return accumulator.takeResults(); 637} 638 639String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) 640{ 641 if (!range) 642 return emptyString(); 643 644 Document* document = range->ownerDocument(); 645 if (!document) 646 return emptyString(); 647 648 const Range* updatedRange = range; 649 650#if ENABLE(DELETION_UI) 651 // Disable the delete button so it's elements are not serialized into the markup, 652 // but make sure neither endpoint is inside the delete user interface. 653 Frame* frame = document->frame(); 654 DeleteButtonControllerDisableScope deleteButtonControllerDisableScope(frame); 655 656 RefPtr<Range> updatedRangeRef; 657 if (frame) { 658 updatedRangeRef = frame->editor().avoidIntersectionWithDeleteButtonController(range); 659 updatedRange = updatedRangeRef.get(); 660 if (!updatedRange) 661 return emptyString(); 662 } 663#endif 664 665 return createMarkupInternal(document, range, updatedRange, nodes, shouldAnnotate, convertBlocksToInlines, shouldResolveURLs); 666} 667 668PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL, ParserContentPolicy parserContentPolicy) 669{ 670 // We use a fake body element here to trick the HTML parser to using the InBody insertion mode. 671 RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(document); 672 RefPtr<DocumentFragment> fragment = DocumentFragment::create(document); 673 674 fragment->parseHTML(markup, fakeBody.get(), parserContentPolicy); 675 676 if (!baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL()) 677 completeURLs(fragment.get(), baseURL); 678 679 return fragment.release(); 680} 681 682static const char fragmentMarkerTag[] = "webkit-fragment-marker"; 683 684static bool findNodesSurroundingContext(Document* document, RefPtr<Node>& nodeBeforeContext, RefPtr<Node>& nodeAfterContext) 685{ 686 for (Node* node = document->firstChild(); node; node = NodeTraversal::next(node)) { 687 if (node->nodeType() == Node::COMMENT_NODE && static_cast<CharacterData*>(node)->data() == fragmentMarkerTag) { 688 if (!nodeBeforeContext) 689 nodeBeforeContext = node; 690 else { 691 nodeAfterContext = node; 692 return true; 693 } 694 } 695 } 696 return false; 697} 698 699static void trimFragment(DocumentFragment* fragment, Node* nodeBeforeContext, Node* nodeAfterContext) 700{ 701 RefPtr<Node> next; 702 for (RefPtr<Node> node = fragment->firstChild(); node; node = next) { 703 if (nodeBeforeContext->isDescendantOf(node.get())) { 704 next = NodeTraversal::next(node.get()); 705 continue; 706 } 707 next = NodeTraversal::nextSkippingChildren(node.get()); 708 ASSERT(!node->contains(nodeAfterContext)); 709 node->parentNode()->removeChild(node.get(), ASSERT_NO_EXCEPTION); 710 if (nodeBeforeContext == node) 711 break; 712 } 713 714 ASSERT(nodeAfterContext->parentNode()); 715 for (RefPtr<Node> node = nodeAfterContext; node; node = next) { 716 next = NodeTraversal::nextSkippingChildren(node.get()); 717 node->parentNode()->removeChild(node.get(), ASSERT_NO_EXCEPTION); 718 } 719} 720 721PassRefPtr<DocumentFragment> createFragmentFromMarkupWithContext(Document* document, const String& markup, unsigned fragmentStart, unsigned fragmentEnd, 722 const String& baseURL, ParserContentPolicy parserContentPolicy) 723{ 724 // FIXME: Need to handle the case where the markup already contains these markers. 725 726 StringBuilder taggedMarkup; 727 taggedMarkup.append(markup.left(fragmentStart)); 728 MarkupAccumulator::appendComment(taggedMarkup, fragmentMarkerTag); 729 taggedMarkup.append(markup.substring(fragmentStart, fragmentEnd - fragmentStart)); 730 MarkupAccumulator::appendComment(taggedMarkup, fragmentMarkerTag); 731 taggedMarkup.append(markup.substring(fragmentEnd)); 732 733 RefPtr<DocumentFragment> taggedFragment = createFragmentFromMarkup(document, taggedMarkup.toString(), baseURL, parserContentPolicy); 734 RefPtr<Document> taggedDocument = Document::create(0, KURL()); 735 taggedDocument->setContextFeatures(document->contextFeatures()); 736 taggedDocument->takeAllChildrenFrom(taggedFragment.get()); 737 738 RefPtr<Node> nodeBeforeContext; 739 RefPtr<Node> nodeAfterContext; 740 if (!findNodesSurroundingContext(taggedDocument.get(), nodeBeforeContext, nodeAfterContext)) 741 return 0; 742 743 RefPtr<Range> range = Range::create(taggedDocument.get(), 744 positionAfterNode(nodeBeforeContext.get()).parentAnchoredEquivalent(), 745 positionBeforeNode(nodeAfterContext.get()).parentAnchoredEquivalent()); 746 747 Node* commonAncestor = range->commonAncestorContainer(ASSERT_NO_EXCEPTION); 748 Node* specialCommonAncestor = ancestorToRetainStructureAndAppearanceWithNoRenderer(commonAncestor); 749 750 // When there's a special common ancestor outside of the fragment, we must include it as well to 751 // preserve the structure and appearance of the fragment. For example, if the fragment contains 752 // TD, we need to include the enclosing TABLE tag as well. 753 RefPtr<DocumentFragment> fragment = DocumentFragment::create(document); 754 if (specialCommonAncestor) 755 fragment->appendChild(specialCommonAncestor, ASSERT_NO_EXCEPTION); 756 else 757 fragment->takeAllChildrenFrom(toContainerNode(commonAncestor)); 758 759 trimFragment(fragment.get(), nodeBeforeContext.get(), nodeAfterContext.get()); 760 761 return fragment; 762} 763 764String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, Vector<QualifiedName>* tagNamesToSkip) 765{ 766 if (!node) 767 return ""; 768 769 HTMLElement* deleteButtonContainerElement = 0; 770#if ENABLE(DELETION_UI) 771 if (Frame* frame = node->document()->frame()) { 772 deleteButtonContainerElement = frame->editor().deleteButtonController()->containerElement(); 773 if (node->isDescendantOf(deleteButtonContainerElement)) 774 return ""; 775 } 776#endif 777 MarkupAccumulator accumulator(nodes, shouldResolveURLs); 778 return accumulator.serializeNodes(const_cast<Node*>(node), deleteButtonContainerElement, childrenOnly, tagNamesToSkip); 779} 780 781static void fillContainerFromString(ContainerNode* paragraph, const String& string) 782{ 783 Document* document = paragraph->document(); 784 785 if (string.isEmpty()) { 786 paragraph->appendChild(createBlockPlaceholderElement(document), ASSERT_NO_EXCEPTION); 787 return; 788 } 789 790 ASSERT(string.find('\n') == notFound); 791 792 Vector<String> tabList; 793 string.split('\t', true, tabList); 794 String tabText = emptyString(); 795 bool first = true; 796 size_t numEntries = tabList.size(); 797 for (size_t i = 0; i < numEntries; ++i) { 798 const String& s = tabList[i]; 799 800 // append the non-tab textual part 801 if (!s.isEmpty()) { 802 if (!tabText.isEmpty()) { 803 paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); 804 tabText = emptyString(); 805 } 806 RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); 807 paragraph->appendChild(textNode.release(), ASSERT_NO_EXCEPTION); 808 } 809 810 // there is a tab after every entry, except the last entry 811 // (if the last character is a tab, the list gets an extra empty entry) 812 if (i + 1 != numEntries) 813 tabText.append('\t'); 814 else if (!tabText.isEmpty()) 815 paragraph->appendChild(createTabSpanElement(document, tabText), ASSERT_NO_EXCEPTION); 816 817 first = false; 818 } 819} 820 821bool isPlainTextMarkup(Node *node) 822{ 823 if (!node->isElementNode() || !node->hasTagName(divTag) || toElement(node)->hasAttributes()) 824 return false; 825 826 if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild()))) 827 return true; 828 829 return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode()); 830} 831 832PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text) 833{ 834 if (!context) 835 return 0; 836 837 Node* styleNode = context->firstNode(); 838 if (!styleNode) { 839 styleNode = context->startPosition().deprecatedNode(); 840 if (!styleNode) 841 return 0; 842 } 843 844 Document* document = styleNode->document(); 845 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 846 847 if (text.isEmpty()) 848 return fragment.release(); 849 850 String string = text; 851 string.replace("\r\n", "\n"); 852 string.replace('\r', '\n'); 853 854 RenderObject* renderer = styleNode->renderer(); 855 if (renderer && renderer->style()->preserveNewline()) { 856 fragment->appendChild(document->createTextNode(string), ASSERT_NO_EXCEPTION); 857 if (string.endsWith('\n')) { 858 RefPtr<Element> element = createBreakElement(document); 859 element->setAttribute(classAttr, AppleInterchangeNewline); 860 fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); 861 } 862 return fragment.release(); 863 } 864 865 // A string with no newlines gets added inline, rather than being put into a paragraph. 866 if (string.find('\n') == notFound) { 867 fillContainerFromString(fragment.get(), string); 868 return fragment.release(); 869 } 870 871 // Break string into paragraphs. Extra line breaks turn into empty paragraphs. 872 Node* blockNode = enclosingBlock(context->firstNode()); 873 Element* block = toElement(blockNode); 874 bool useClonesOfEnclosingBlock = blockNode 875 && blockNode->isElementNode() 876 && !block->hasTagName(bodyTag) 877 && !block->hasTagName(htmlTag) 878 && block != editableRootForPosition(context->startPosition()); 879 bool useLineBreak = enclosingTextFormControl(context->startPosition()); 880 881 Vector<String> list; 882 string.split('\n', true, list); // true gets us empty strings in the list 883 size_t numLines = list.size(); 884 for (size_t i = 0; i < numLines; ++i) { 885 const String& s = list[i]; 886 887 RefPtr<Element> element; 888 if (s.isEmpty() && i + 1 == numLines) { 889 // For last line, use the "magic BR" rather than a P. 890 element = createBreakElement(document); 891 element->setAttribute(classAttr, AppleInterchangeNewline); 892 } else if (useLineBreak) { 893 element = createBreakElement(document); 894 fillContainerFromString(fragment.get(), s); 895 } else { 896 if (useClonesOfEnclosingBlock) 897 element = block->cloneElementWithoutChildren(); 898 else 899 element = createDefaultParagraphElement(document); 900 fillContainerFromString(element.get(), s); 901 } 902 fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); 903 } 904 return fragment.release(); 905} 906 907PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes) 908{ 909 if (!document) 910 return 0; 911 912#if ENABLE(DELETION_UI) 913 // disable the delete button so it's elements are not serialized into the markup 914 DeleteButtonControllerDisableScope(document->frame()); 915#endif 916 917 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 918 919 size_t size = nodes.size(); 920 for (size_t i = 0; i < size; ++i) { 921 RefPtr<Element> element = createDefaultParagraphElement(document); 922 element->appendChild(nodes[i], ASSERT_NO_EXCEPTION); 923 fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); 924 } 925 926 return fragment.release(); 927} 928 929String createFullMarkup(const Node* node) 930{ 931 if (!node) 932 return String(); 933 934 Document* document = node->document(); 935 if (!document) 936 return String(); 937 938 Frame* frame = document->frame(); 939 if (!frame) 940 return String(); 941 942 // FIXME: This is never "for interchange". Is that right? 943 String markupString = createMarkup(node, IncludeNode, 0); 944 Node::NodeType nodeType = node->nodeType(); 945 if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE) 946 markupString = frame->documentTypeString() + markupString; 947 948 return markupString; 949} 950 951String createFullMarkup(const Range* range) 952{ 953 if (!range) 954 return String(); 955 956 Node* node = range->startContainer(); 957 if (!node) 958 return String(); 959 960 Document* document = node->document(); 961 if (!document) 962 return String(); 963 964 Frame* frame = document->frame(); 965 if (!frame) 966 return String(); 967 968 // FIXME: This is always "for interchange". Is that right? See the previous method. 969 return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange); 970} 971 972String urlToMarkup(const KURL& url, const String& title) 973{ 974 StringBuilder markup; 975 markup.append("<a href=\""); 976 markup.append(url.string()); 977 markup.append("\">"); 978 MarkupAccumulator::appendCharactersReplacingEntities(markup, title, 0, title.length(), EntityMaskInPCDATA); 979 markup.append("</a>"); 980 return markup.toString(); 981} 982 983PassRefPtr<DocumentFragment> createFragmentForInnerOuterHTML(const String& markup, Element* contextElement, ParserContentPolicy parserContentPolicy, ExceptionCode& ec) 984{ 985 Document* document = contextElement->document(); 986#if ENABLE(TEMPLATE_ELEMENT) 987 if (contextElement->hasTagName(templateTag)) 988 document = document->ensureTemplateDocument(); 989#endif 990 RefPtr<DocumentFragment> fragment = DocumentFragment::create(document); 991 992 if (document->isHTMLDocument()) { 993 fragment->parseHTML(markup, contextElement, parserContentPolicy); 994 return fragment; 995 } 996 997 bool wasValid = fragment->parseXML(markup, contextElement, parserContentPolicy); 998 if (!wasValid) { 999 ec = SYNTAX_ERR; 1000 return 0; 1001 } 1002 return fragment.release(); 1003} 1004 1005PassRefPtr<DocumentFragment> createFragmentForTransformToFragment(const String& sourceString, const String& sourceMIMEType, Document* outputDoc) 1006{ 1007 RefPtr<DocumentFragment> fragment = outputDoc->createDocumentFragment(); 1008 1009 if (sourceMIMEType == "text/html") { 1010 // As far as I can tell, there isn't a spec for how transformToFragment is supposed to work. 1011 // Based on the documentation I can find, it looks like we want to start parsing the fragment in the InBody insertion mode. 1012 // Unfortunately, that's an implementation detail of the parser. 1013 // We achieve that effect here by passing in a fake body element as context for the fragment. 1014 RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(outputDoc); 1015 fragment->parseHTML(sourceString, fakeBody.get()); 1016 } else if (sourceMIMEType == "text/plain") 1017 fragment->parserAppendChild(Text::create(outputDoc, sourceString)); 1018 else { 1019 bool successfulParse = fragment->parseXML(sourceString, 0); 1020 if (!successfulParse) 1021 return 0; 1022 } 1023 1024 // FIXME: Do we need to mess with URLs here? 1025 1026 return fragment.release(); 1027} 1028 1029static inline void removeElementPreservingChildren(PassRefPtr<DocumentFragment> fragment, HTMLElement* element) 1030{ 1031 RefPtr<Node> nextChild; 1032 for (RefPtr<Node> child = element->firstChild(); child; child = nextChild) { 1033 nextChild = child->nextSibling(); 1034 element->removeChild(child.get(), ASSERT_NO_EXCEPTION); 1035 fragment->insertBefore(child, element, ASSERT_NO_EXCEPTION); 1036 } 1037 fragment->removeChild(element, ASSERT_NO_EXCEPTION); 1038} 1039 1040PassRefPtr<DocumentFragment> createContextualFragment(const String& markup, HTMLElement* element, ParserContentPolicy parserContentPolicy, ExceptionCode& ec) 1041{ 1042 ASSERT(element); 1043 if (element->ieForbidsInsertHTML()) { 1044 ec = NOT_SUPPORTED_ERR; 1045 return 0; 1046 } 1047 1048 if (element->hasLocalName(colTag) || element->hasLocalName(colgroupTag) || element->hasLocalName(framesetTag) 1049 || element->hasLocalName(headTag) || element->hasLocalName(styleTag) || element->hasLocalName(titleTag)) { 1050 ec = NOT_SUPPORTED_ERR; 1051 return 0; 1052 } 1053 1054 RefPtr<DocumentFragment> fragment = createFragmentForInnerOuterHTML(markup, element, parserContentPolicy, ec); 1055 if (!fragment) 1056 return 0; 1057 1058 // We need to pop <html> and <body> elements and remove <head> to 1059 // accommodate folks passing complete HTML documents to make the 1060 // child of an element. 1061 1062 RefPtr<Node> nextNode; 1063 for (RefPtr<Node> node = fragment->firstChild(); node; node = nextNode) { 1064 nextNode = node->nextSibling(); 1065 if (node->hasTagName(htmlTag) || node->hasTagName(headTag) || node->hasTagName(bodyTag)) { 1066 HTMLElement* element = toHTMLElement(node.get()); 1067 if (Node* firstChild = element->firstChild()) 1068 nextNode = firstChild; 1069 removeElementPreservingChildren(fragment, element); 1070 } 1071 } 1072 return fragment.release(); 1073} 1074 1075static inline bool hasOneChild(ContainerNode* node) 1076{ 1077 Node* firstChild = node->firstChild(); 1078 return firstChild && !firstChild->nextSibling(); 1079} 1080 1081static inline bool hasOneTextChild(ContainerNode* node) 1082{ 1083 return hasOneChild(node) && node->firstChild()->isTextNode(); 1084} 1085 1086void replaceChildrenWithFragment(ContainerNode* container, PassRefPtr<DocumentFragment> fragment, ExceptionCode& ec) 1087{ 1088 RefPtr<ContainerNode> containerNode(container); 1089 1090 ChildListMutationScope mutation(containerNode.get()); 1091 1092 if (!fragment->firstChild()) { 1093 containerNode->removeChildren(); 1094 return; 1095 } 1096 1097 if (hasOneTextChild(containerNode.get()) && hasOneTextChild(fragment.get())) { 1098 toText(containerNode->firstChild())->setData(toText(fragment->firstChild())->data(), ec); 1099 return; 1100 } 1101 1102 if (hasOneChild(containerNode.get())) { 1103 containerNode->replaceChild(fragment, containerNode->firstChild(), ec); 1104 return; 1105 } 1106 1107 containerNode->removeChildren(); 1108 containerNode->appendChild(fragment, ec); 1109} 1110 1111void replaceChildrenWithText(ContainerNode* container, const String& text, ExceptionCode& ec) 1112{ 1113 RefPtr<ContainerNode> containerNode(container); 1114 1115 ChildListMutationScope mutation(containerNode.get()); 1116 1117 if (hasOneTextChild(containerNode.get())) { 1118 toText(containerNode->firstChild())->setData(text, ec); 1119 return; 1120 } 1121 1122 RefPtr<Text> textNode = Text::create(containerNode->document(), text); 1123 1124 if (hasOneChild(containerNode.get())) { 1125 containerNode->replaceChild(textNode.release(), containerNode->firstChild(), ec); 1126 return; 1127 } 1128 1129 containerNode->removeChildren(); 1130 containerNode->appendChild(textNode.release(), ec); 1131} 1132 1133} 1134