1 /* 2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "markup.h" 28 29 #include "CDATASection.h" 30 #include "CSSComputedStyleDeclaration.h" 31 #include "CSSMutableStyleDeclaration.h" 32 #include "CSSPrimitiveValue.h" 33 #include "CSSProperty.h" 34 #include "CSSPropertyNames.h" 35 #include "CSSRule.h" 36 #include "CSSRuleList.h" 37 #include "CSSStyleRule.h" 38 #include "CSSStyleSelector.h" 39 #include "CSSValue.h" 40 #include "CSSValueKeywords.h" 41 #include "DeleteButtonController.h" 42 #include "DocumentFragment.h" 43 #include "DocumentType.h" 44 #include "Editor.h" 45 #include "Frame.h" 46 #include "HTMLBodyElement.h" 47 #include "HTMLElement.h" 48 #include "HTMLNames.h" 49 #include "KURL.h" 50 #include "MarkupAccumulator.h" 51 #include "Range.h" 52 #include "TextIterator.h" 53 #include "VisibleSelection.h" 54 #include "XMLNSNames.h" 55 #include "htmlediting.h" 56 #include "visible_units.h" 57 #include <wtf/StdLibExtras.h> 58 #include <wtf/unicode/CharacterNames.h> 59 60 using namespace std; 61 62 namespace WebCore { 63 64 using namespace HTMLNames; 65 66 static bool propertyMissingOrEqualToNone(CSSStyleDeclaration*, int propertyID); 67 68 class AttributeChange { 69 public: 70 AttributeChange() 71 : m_name(nullAtom, nullAtom, nullAtom) 72 { 73 } 74 75 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value) 76 : m_element(element), m_name(name), m_value(value) 77 { 78 } 79 80 void apply() 81 { 82 m_element->setAttribute(m_name, m_value); 83 } 84 85 private: 86 RefPtr<Element> m_element; 87 QualifiedName m_name; 88 String m_value; 89 }; 90 91 static void completeURLs(Node* node, const String& baseURL) 92 { 93 Vector<AttributeChange> changes; 94 95 KURL parsedBaseURL(ParsedURLString, baseURL); 96 97 Node* end = node->traverseNextSibling(); 98 for (Node* n = node; n != end; n = n->traverseNextNode()) { 99 if (n->isElementNode()) { 100 Element* e = static_cast<Element*>(n); 101 NamedNodeMap* attributes = e->attributes(); 102 unsigned length = attributes->length(); 103 for (unsigned i = 0; i < length; i++) { 104 Attribute* attribute = attributes->attributeItem(i); 105 if (e->isURLAttribute(attribute)) 106 changes.append(AttributeChange(e, attribute->name(), KURL(parsedBaseURL, attribute->value()).string())); 107 } 108 } 109 } 110 111 size_t numChanges = changes.size(); 112 for (size_t i = 0; i < numChanges; ++i) 113 changes[i].apply(); 114 } 115 116 class StyledMarkupAccumulator : public MarkupAccumulator { 117 public: 118 enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode }; 119 120 StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range) 121 : MarkupAccumulator(nodes, shouldResolveURLs, range) 122 , m_shouldAnnotate(shouldAnnotate) 123 { 124 } 125 126 Node* serializeNodes(Node* startNode, Node* pastEnd); 127 virtual void appendString(const String& s) { return MarkupAccumulator::appendString(s); } 128 void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); 129 void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false); 130 String takeResults(); 131 132 private: 133 virtual void appendText(Vector<UChar>& out, Text*); 134 String renderedText(const Node*, const Range*); 135 String stringValueForRange(const Node*, const Range*); 136 void removeExteriorStyles(CSSMutableStyleDeclaration*); 137 void appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode); 138 void appendElement(Vector<UChar>& out, Element* element, Namespaces*) { appendElement(out, element, false, DoesFullySelectNode); } 139 140 bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; } 141 142 Vector<String> m_reversedPrecedingMarkup; 143 const EAnnotateForInterchange m_shouldAnnotate; 144 }; 145 146 void StyledMarkupAccumulator::wrapWithNode(Node* node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode) 147 { 148 Vector<UChar> markup; 149 if (node->isElementNode()) 150 appendElement(markup, static_cast<Element*>(node), convertBlocksToInlines && isBlock(const_cast<Node*>(node)), rangeFullySelectsNode); 151 else 152 appendStartMarkup(markup, node, 0); 153 m_reversedPrecedingMarkup.append(String::adopt(markup)); 154 appendEndTag(node); 155 if (m_nodes) 156 m_nodes->append(node); 157 } 158 159 void StyledMarkupAccumulator::wrapWithStyleNode(CSSStyleDeclaration* style, Document* document, bool isBlock) 160 { 161 // All text-decoration-related elements should have been treated as special ancestors 162 // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here 163 ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect)); 164 DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\"")); 165 DEFINE_STATIC_LOCAL(const String, divClose, ("</div>")); 166 DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\"")); 167 DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>")); 168 Vector<UChar> openTag; 169 append(openTag, isBlock ? divStyle : styleSpanOpen); 170 appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument()); 171 openTag.append('\"'); 172 openTag.append('>'); 173 m_reversedPrecedingMarkup.append(String::adopt(openTag)); 174 appendString(isBlock ? divClose : styleSpanClose); 175 } 176 177 String StyledMarkupAccumulator::takeResults() 178 { 179 Vector<UChar> result; 180 result.reserveInitialCapacity(totalLength(m_reversedPrecedingMarkup) + length()); 181 182 for (size_t i = m_reversedPrecedingMarkup.size(); i > 0; --i) 183 append(result, m_reversedPrecedingMarkup[i - 1]); 184 185 concatenateMarkup(result); 186 187 // We remove '\0' characters because they are not visibly rendered to the user. 188 return String::adopt(result).replace(0, ""); 189 } 190 191 void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text) 192 { 193 if (!shouldAnnotate() || (text->parentElement() && text->parentElement()->tagQName() == textareaTag)) { 194 MarkupAccumulator::appendText(out, text); 195 return; 196 } 197 198 bool useRenderedText = !enclosingNodeWithTag(firstPositionInNode(text), selectTag); 199 String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range); 200 Vector<UChar> buffer; 201 appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA); 202 append(out, convertHTMLTextToInterchangeFormat(String::adopt(buffer), text)); 203 } 204 205 String StyledMarkupAccumulator::renderedText(const Node* node, const Range* range) 206 { 207 if (!node->isTextNode()) 208 return String(); 209 210 ExceptionCode ec; 211 const Text* textNode = static_cast<const Text*>(node); 212 unsigned startOffset = 0; 213 unsigned endOffset = textNode->length(); 214 215 if (range && node == range->startContainer(ec)) 216 startOffset = range->startOffset(ec); 217 if (range && node == range->endContainer(ec)) 218 endOffset = range->endOffset(ec); 219 220 Position start(const_cast<Node*>(node), startOffset); 221 Position end(const_cast<Node*>(node), endOffset); 222 return plainText(Range::create(node->document(), start, end).get()); 223 } 224 225 String StyledMarkupAccumulator::stringValueForRange(const Node* node, const Range* range) 226 { 227 if (!range) 228 return node->nodeValue(); 229 230 String str = node->nodeValue(); 231 ExceptionCode ec; 232 if (node == range->endContainer(ec)) 233 str.truncate(range->endOffset(ec)); 234 if (node == range->startContainer(ec)) 235 str.remove(0, range->startOffset(ec)); 236 return str; 237 } 238 239 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true) 240 { 241 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create(); 242 RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly); 243 if (matchedRules) { 244 for (unsigned i = 0; i < matchedRules->length(); i++) { 245 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) { 246 RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style(); 247 style->merge(s.get(), true); 248 } 249 } 250 } 251 252 return style.release(); 253 } 254 255 void StyledMarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode) 256 { 257 bool documentIsHTML = element->document()->isHTMLDocument(); 258 appendOpenTag(out, element, 0); 259 260 NamedNodeMap* attributes = element->attributes(); 261 unsigned length = attributes->length(); 262 for (unsigned int i = 0; i < length; i++) { 263 Attribute* attribute = attributes->attributeItem(i); 264 // We'll handle the style attribute separately, below. 265 if (attribute->name() == styleAttr && element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) 266 continue; 267 appendAttribute(out, element, *attribute, 0); 268 } 269 270 if (element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) { 271 RefPtr<CSSMutableStyleDeclaration> style = toHTMLElement(element)->getInlineStyleDecl()->copy(); 272 if (shouldAnnotate()) { 273 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(element)); 274 // Styles from the inline style declaration, held in the variable "style", take precedence 275 // over those from matched rules. 276 styleFromMatchedRules->merge(style.get()); 277 style = styleFromMatchedRules; 278 279 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element); 280 RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create(); 281 282 { 283 CSSMutableStyleDeclaration::const_iterator end = style->end(); 284 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { 285 const CSSProperty& property = *it; 286 CSSValue* value = property.value(); 287 // The property value, if it's a percentage, may not reflect the actual computed value. 288 // For example: style="height: 1%; overflow: visible;" in quirksmode 289 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem 290 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) 291 if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) 292 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id())) 293 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue)); 294 } 295 } 296 style->merge(fromComputedStyle.get()); 297 } 298 if (addDisplayInline) 299 style->setProperty(CSSPropertyDisplay, CSSValueInline, true); 300 // 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 301 // only the ones that affect it and the nodes within it. 302 if (rangeFullySelectsNode == DoesNotFullySelectNode) 303 removeExteriorStyles(style.get()); 304 if (style->length() > 0) { 305 DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\"")); 306 append(out, stylePrefix); 307 appendAttributeValue(out, style->cssText(), documentIsHTML); 308 out.append('\"'); 309 } 310 } 311 312 appendCloseTag(out, element); 313 } 314 315 void StyledMarkupAccumulator::removeExteriorStyles(CSSMutableStyleDeclaration* style) 316 { 317 style->removeProperty(CSSPropertyFloat); 318 } 319 320 Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) 321 { 322 Vector<Node*> ancestorsToClose; 323 Node* next; 324 Node* lastClosed = 0; 325 for (Node* n = startNode; n != pastEnd; n = next) { 326 // According to <rdar://problem/5730668>, it is possible for n to blow 327 // past pastEnd and become null here. This shouldn't be possible. 328 // This null check will prevent crashes (but create too much markup) 329 // and the ASSERT will hopefully lead us to understanding the problem. 330 ASSERT(n); 331 if (!n) 332 break; 333 334 next = n->traverseNextNode(); 335 bool openedTag = false; 336 337 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) 338 // Don't write out empty block containers that aren't fully selected. 339 continue; 340 341 if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) { 342 next = n->traverseNextSibling(); 343 // Don't skip over pastEnd. 344 if (pastEnd && pastEnd->isDescendantOf(n)) 345 next = pastEnd; 346 } else { 347 // Add the node to the markup if we're not skipping the descendants 348 appendStartTag(n); 349 350 // If node has no children, close the tag now. 351 if (!n->childNodeCount()) { 352 appendEndTag(n); 353 lastClosed = n; 354 } else { 355 openedTag = true; 356 ancestorsToClose.append(n); 357 } 358 } 359 360 // 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. 361 // FIXME: What happens if we just inserted open tag and reached the end? 362 if (!openedTag && (!n->nextSibling() || next == pastEnd)) { 363 // Close up the ancestors. 364 while (!ancestorsToClose.isEmpty()) { 365 Node* ancestor = ancestorsToClose.last(); 366 if (next != pastEnd && next->isDescendantOf(ancestor)) 367 break; 368 // Not at the end of the range, close ancestors up to sibling of next node. 369 appendEndTag(ancestor); 370 lastClosed = ancestor; 371 ancestorsToClose.removeLast(); 372 } 373 374 // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. 375 ContainerNode* nextParent = next ? next->parentNode() : 0; 376 if (next != pastEnd && n != nextParent) { 377 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; 378 for (ContainerNode* parent = lastAncestorClosedOrSelf->parentNode(); parent && parent != nextParent; parent = parent->parentNode()) { 379 // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: 380 if (!parent->renderer()) 381 continue; 382 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: 383 ASSERT(startNode->isDescendantOf(parent)); 384 wrapWithNode(parent); 385 lastClosed = parent; 386 } 387 } 388 } 389 } 390 391 return lastClosed; 392 } 393 394 static Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor) 395 { 396 Node* commonAncestorBlock = enclosingBlock(commonAncestor); 397 398 if (!commonAncestorBlock) 399 return 0; 400 401 if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { 402 ContainerNode* table = commonAncestorBlock->parentNode(); 403 while (table && !table->hasTagName(tableTag)) 404 table = table->parentNode(); 405 406 return table; 407 } 408 409 if (commonAncestorBlock->hasTagName(listingTag) 410 || commonAncestorBlock->hasTagName(olTag) 411 || commonAncestorBlock->hasTagName(preTag) 412 || commonAncestorBlock->hasTagName(tableTag) 413 || commonAncestorBlock->hasTagName(ulTag) 414 || commonAncestorBlock->hasTagName(xmpTag) 415 || commonAncestorBlock->hasTagName(h1Tag) 416 || commonAncestorBlock->hasTagName(h2Tag) 417 || commonAncestorBlock->hasTagName(h3Tag) 418 || commonAncestorBlock->hasTagName(h4Tag) 419 || commonAncestorBlock->hasTagName(h5Tag)) 420 return commonAncestorBlock; 421 422 return 0; 423 } 424 425 static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propertyID) 426 { 427 if (!style) 428 return false; 429 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); 430 if (!value) 431 return true; 432 if (!value->isPrimitiveValue()) 433 return false; 434 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone; 435 } 436 437 static bool needInterchangeNewlineAfter(const VisiblePosition& v) 438 { 439 VisiblePosition next = v.next(); 440 Node* upstreamNode = next.deepEquivalent().upstream().deprecatedNode(); 441 Node* downstreamNode = v.deepEquivalent().downstream().deprecatedNode(); 442 // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it. 443 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode); 444 } 445 446 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node) 447 { 448 if (!node->isHTMLElement()) 449 return 0; 450 451 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle 452 // the non-const-ness of styleFromMatchedRulesForElement. 453 HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node)); 454 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element); 455 RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl(); 456 style->merge(inlineStyleDecl.get()); 457 return style.release(); 458 } 459 460 static bool isElementPresentational(const Node* node) 461 { 462 if (node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag) 463 || node->hasTagName(iTag) || node->hasTagName(emTag) || node->hasTagName(bTag) || node->hasTagName(strongTag)) 464 return true; 465 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node); 466 if (!style) 467 return false; 468 return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration); 469 } 470 471 static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CSSMutableStyleDeclaration* style) 472 { 473 if (fullySelectedRoot->isElementNode() && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr)) 474 return true; 475 476 return style->getPropertyCSSValue(CSSPropertyBackgroundImage) || style->getPropertyCSSValue(CSSPropertyBackgroundColor); 477 } 478 479 static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelectedRoot, EAnnotateForInterchange shouldAnnotate) 480 { 481 ExceptionCode ec; 482 Node* commonAncestor = range->commonAncestorContainer(ec); 483 ASSERT(commonAncestor); 484 Node* specialCommonAncestor = 0; 485 if (shouldAnnotate == AnnotateForInterchange) { 486 // Include ancestors that aren't completely inside the range but are required to retain 487 // the structure and appearance of the copied markup. 488 specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor); 489 490 // Retain the Mail quote level by including all ancestor mail block quotes. 491 if (Node* highestMailBlockquote = highestEnclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isMailBlockquote, CanCrossEditingBoundary)) 492 specialCommonAncestor = highestMailBlockquote; 493 } 494 495 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; 496 if (checkAncestor->renderer()) { 497 Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(firstPositionInNode(checkAncestor), &isElementPresentational); 498 if (newSpecialCommonAncestor) 499 specialCommonAncestor = newSpecialCommonAncestor; 500 } 501 502 // If a single tab is selected, commonAncestor will be a text node inside a tab span. 503 // If two or more tabs are selected, commonAncestor will be the tab span. 504 // In either case, if there is a specialCommonAncestor already, it will necessarily be above 505 // any tab span that needs to be included. 506 if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor)) 507 specialCommonAncestor = commonAncestor->parentNode(); 508 if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) 509 specialCommonAncestor = commonAncestor; 510 511 if (Node *enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(specialCommonAncestor ? specialCommonAncestor : commonAncestor), aTag)) 512 specialCommonAncestor = enclosingAnchor; 513 514 if (shouldAnnotate == AnnotateForInterchange && fullySelectedRoot) { 515 RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); 516 if (shouldIncludeWrapperForFullySelectedRoot(fullySelectedRoot, fullySelectedRootStyle.get())) 517 specialCommonAncestor = fullySelectedRoot; 518 } 519 return specialCommonAncestor; 520 } 521 522 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 523 // FIXME: At least, annotation and style info should probably not be included in range.markupString() 524 String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) 525 { 526 DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">")); 527 528 if (!range) 529 return ""; 530 531 Document* document = range->ownerDocument(); 532 if (!document) 533 return ""; 534 535 // Disable the delete button so it's elements are not serialized into the markup, 536 // but make sure neither endpoint is inside the delete user interface. 537 Frame* frame = document->frame(); 538 DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0; 539 RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0); 540 if (!updatedRange) 541 return ""; 542 543 if (deleteButton) 544 deleteButton->disable(); 545 546 ExceptionCode ec = 0; 547 bool collapsed = updatedRange->collapsed(ec); 548 ASSERT(!ec); 549 if (collapsed) 550 return ""; 551 Node* commonAncestor = updatedRange->commonAncestorContainer(ec); 552 ASSERT(!ec); 553 if (!commonAncestor) 554 return ""; 555 556 document->updateLayoutIgnorePendingStylesheets(); 557 558 StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, updatedRange.get()); 559 Node* pastEnd = updatedRange->pastLastNode(); 560 561 Node* startNode = updatedRange->firstNode(); 562 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY); 563 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY); 564 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) { 565 if (visibleStart == visibleEnd.previous()) { 566 if (deleteButton) 567 deleteButton->enable(); 568 return interchangeNewlineString; 569 } 570 571 accumulator.appendString(interchangeNewlineString); 572 startNode = visibleStart.next().deepEquivalent().deprecatedNode(); 573 574 ExceptionCode ec = 0; 575 if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ec) >= 0) { 576 ASSERT(!ec); 577 if (deleteButton) 578 deleteButton->enable(); 579 return interchangeNewlineString; 580 } 581 ASSERT(!ec); 582 } 583 584 Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag); 585 Node* fullySelectedRoot = 0; 586 // FIXME: Do this for all fully selected blocks, not just the body. 587 if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), range)) 588 fullySelectedRoot = body; 589 590 Node* specialCommonAncestor = highestAncestorToWrapMarkup(updatedRange.get(), fullySelectedRoot, shouldAnnotate); 591 592 Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); 593 594 if (specialCommonAncestor && lastClosed) { 595 // Also include all of the ancestors of lastClosed up to this special ancestor. 596 for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { 597 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { 598 RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); 599 600 // Bring the background attribute over, but not as an attribute because a background attribute on a div 601 // appears to have no effect. 602 if (!fullySelectedRootStyle->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr)) 603 fullySelectedRootStyle->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); 604 605 if (fullySelectedRootStyle->length()) { 606 // Reset the CSS properties to avoid an assertion error in addStyleMarkup(). 607 // This assertion is caused at least when we select all text of a <body> element whose 608 // 'text-decoration' property is "inherit", and copy it. 609 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle.get(), CSSPropertyTextDecoration)) 610 fullySelectedRootStyle->setProperty(CSSPropertyTextDecoration, CSSValueNone); 611 if (!propertyMissingOrEqualToNone(fullySelectedRootStyle.get(), CSSPropertyWebkitTextDecorationsInEffect)) 612 fullySelectedRootStyle->setProperty(CSSPropertyWebkitTextDecorationsInEffect, CSSValueNone); 613 accumulator.wrapWithStyleNode(fullySelectedRootStyle.get(), document, true); 614 } 615 } else { 616 // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode 617 // so that styles that affect the exterior of the node are not included. 618 accumulator.wrapWithNode(ancestor, convertBlocksToInlines, StyledMarkupAccumulator::DoesNotFullySelectNode); 619 } 620 if (nodes) 621 nodes->append(ancestor); 622 623 lastClosed = ancestor; 624 625 if (ancestor == specialCommonAncestor) 626 break; 627 } 628 } 629 630 // Add a wrapper span with the styles that all of the nodes in the markup inherit. 631 ContainerNode* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0; 632 if (parentOfLastClosed && parentOfLastClosed->renderer()) { 633 RefPtr<EditingStyle> style = EditingStyle::create(parentOfLastClosed); 634 635 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help 636 // us differentiate those styles from ones that the user has applied. This helps us 637 // get the color of content pasted into blockquotes right. 638 style->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInNode(parentOfLastClosed), isMailBlockquote, CanCrossEditingBoundary)); 639 640 // Document default styles will be added on another wrapper span. 641 if (document && document->documentElement()) 642 style->prepareToApplyAt(firstPositionInNode(document->documentElement())); 643 644 // Since we are converting blocks to inlines, remove any inherited block properties that are in the style. 645 // This cuts out meaningless properties and prevents properties from magically affecting blocks later 646 // if the style is cloned for a new block element during a future editing operation. 647 if (convertBlocksToInlines) 648 style->removeBlockProperties(); 649 650 if (!style->isEmpty()) 651 accumulator.wrapWithStyleNode(style->style(), document); 652 } 653 654 if (lastClosed && lastClosed != document->documentElement()) { 655 // Add a style span with the document's default styles. We add these in a separate 656 // span so that at paste time we can differentiate between document defaults and user 657 // applied styles. 658 RefPtr<EditingStyle> defaultStyle = EditingStyle::create(document->documentElement()); 659 if (!defaultStyle->isEmpty()) 660 accumulator.wrapWithStyleNode(defaultStyle->style(), document); 661 } 662 663 // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. 664 if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous())) 665 accumulator.appendString(interchangeNewlineString); 666 667 if (deleteButton) 668 deleteButton->enable(); 669 670 return accumulator.takeResults(); 671 } 672 673 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL, FragmentScriptingPermission scriptingPermission) 674 { 675 // We use a fake body element here to trick the HTML parser to using the 676 // InBody insertion mode. Really, all this code is wrong and need to be 677 // changed not to use deprecatedCreateContextualFragment. 678 RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(document); 679 // FIXME: This should not use deprecatedCreateContextualFragment 680 RefPtr<DocumentFragment> fragment = fakeBody->deprecatedCreateContextualFragment(markup, scriptingPermission); 681 682 if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL()) 683 completeURLs(fragment.get(), baseURL); 684 685 return fragment.release(); 686 } 687 688 String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs) 689 { 690 if (!node) 691 return ""; 692 693 HTMLElement* deleteButtonContainerElement = 0; 694 if (Frame* frame = node->document()->frame()) { 695 deleteButtonContainerElement = frame->editor()->deleteButtonController()->containerElement(); 696 if (node->isDescendantOf(deleteButtonContainerElement)) 697 return ""; 698 } 699 700 MarkupAccumulator accumulator(nodes, shouldResolveURLs); 701 return accumulator.serializeNodes(const_cast<Node*>(node), deleteButtonContainerElement, childrenOnly); 702 } 703 704 static void fillContainerFromString(ContainerNode* paragraph, const String& string) 705 { 706 Document* document = paragraph->document(); 707 708 ExceptionCode ec = 0; 709 if (string.isEmpty()) { 710 paragraph->appendChild(createBlockPlaceholderElement(document), ec); 711 ASSERT(!ec); 712 return; 713 } 714 715 ASSERT(string.find('\n') == notFound); 716 717 Vector<String> tabList; 718 string.split('\t', true, tabList); 719 String tabText = ""; 720 bool first = true; 721 size_t numEntries = tabList.size(); 722 for (size_t i = 0; i < numEntries; ++i) { 723 const String& s = tabList[i]; 724 725 // append the non-tab textual part 726 if (!s.isEmpty()) { 727 if (!tabText.isEmpty()) { 728 paragraph->appendChild(createTabSpanElement(document, tabText), ec); 729 ASSERT(!ec); 730 tabText = ""; 731 } 732 RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); 733 paragraph->appendChild(textNode.release(), ec); 734 ASSERT(!ec); 735 } 736 737 // there is a tab after every entry, except the last entry 738 // (if the last character is a tab, the list gets an extra empty entry) 739 if (i + 1 != numEntries) 740 tabText.append('\t'); 741 else if (!tabText.isEmpty()) { 742 paragraph->appendChild(createTabSpanElement(document, tabText), ec); 743 ASSERT(!ec); 744 } 745 746 first = false; 747 } 748 } 749 750 bool isPlainTextMarkup(Node *node) 751 { 752 if (!node->isElementNode() || !node->hasTagName(divTag) || static_cast<Element*>(node)->attributes()->length()) 753 return false; 754 755 if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild()))) 756 return true; 757 758 return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode()); 759 } 760 761 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text) 762 { 763 if (!context) 764 return 0; 765 766 Node* styleNode = context->firstNode(); 767 if (!styleNode) { 768 styleNode = context->startPosition().deprecatedNode(); 769 if (!styleNode) 770 return 0; 771 } 772 773 Document* document = styleNode->document(); 774 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 775 776 if (text.isEmpty()) 777 return fragment.release(); 778 779 String string = text; 780 string.replace("\r\n", "\n"); 781 string.replace('\r', '\n'); 782 783 ExceptionCode ec = 0; 784 RenderObject* renderer = styleNode->renderer(); 785 if (renderer && renderer->style()->preserveNewline()) { 786 fragment->appendChild(document->createTextNode(string), ec); 787 ASSERT(!ec); 788 if (string.endsWith("\n")) { 789 RefPtr<Element> element = createBreakElement(document); 790 element->setAttribute(classAttr, AppleInterchangeNewline); 791 fragment->appendChild(element.release(), ec); 792 ASSERT(!ec); 793 } 794 return fragment.release(); 795 } 796 797 // A string with no newlines gets added inline, rather than being put into a paragraph. 798 if (string.find('\n') == notFound) { 799 fillContainerFromString(fragment.get(), string); 800 return fragment.release(); 801 } 802 803 // Break string into paragraphs. Extra line breaks turn into empty paragraphs. 804 Node* blockNode = enclosingBlock(context->firstNode()); 805 Element* block = static_cast<Element*>(blockNode); 806 bool useClonesOfEnclosingBlock = blockNode 807 && blockNode->isElementNode() 808 && !block->hasTagName(bodyTag) 809 && !block->hasTagName(htmlTag) 810 && block != editableRootForPosition(context->startPosition()); 811 812 Vector<String> list; 813 string.split('\n', true, list); // true gets us empty strings in the list 814 size_t numLines = list.size(); 815 for (size_t i = 0; i < numLines; ++i) { 816 const String& s = list[i]; 817 818 RefPtr<Element> element; 819 if (s.isEmpty() && i + 1 == numLines) { 820 // For last line, use the "magic BR" rather than a P. 821 element = createBreakElement(document); 822 element->setAttribute(classAttr, AppleInterchangeNewline); 823 } else { 824 if (useClonesOfEnclosingBlock) 825 element = block->cloneElementWithoutChildren(); 826 else 827 element = createDefaultParagraphElement(document); 828 fillContainerFromString(element.get(), s); 829 } 830 fragment->appendChild(element.release(), ec); 831 ASSERT(!ec); 832 } 833 return fragment.release(); 834 } 835 836 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes) 837 { 838 if (!document) 839 return 0; 840 841 // disable the delete button so it's elements are not serialized into the markup 842 if (document->frame()) 843 document->frame()->editor()->deleteButtonController()->disable(); 844 845 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 846 847 ExceptionCode ec = 0; 848 size_t size = nodes.size(); 849 for (size_t i = 0; i < size; ++i) { 850 RefPtr<Element> element = createDefaultParagraphElement(document); 851 element->appendChild(nodes[i], ec); 852 ASSERT(!ec); 853 fragment->appendChild(element.release(), ec); 854 ASSERT(!ec); 855 } 856 857 if (document->frame()) 858 document->frame()->editor()->deleteButtonController()->enable(); 859 860 return fragment.release(); 861 } 862 863 String createFullMarkup(const Node* node) 864 { 865 if (!node) 866 return String(); 867 868 Document* document = node->document(); 869 if (!document) 870 return String(); 871 872 Frame* frame = document->frame(); 873 if (!frame) 874 return String(); 875 876 // FIXME: This is never "for interchange". Is that right? 877 String markupString = createMarkup(node, IncludeNode, 0); 878 Node::NodeType nodeType = node->nodeType(); 879 if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE) 880 markupString = frame->documentTypeString() + markupString; 881 882 return markupString; 883 } 884 885 String createFullMarkup(const Range* range) 886 { 887 if (!range) 888 return String(); 889 890 Node* node = range->startContainer(); 891 if (!node) 892 return String(); 893 894 Document* document = node->document(); 895 if (!document) 896 return String(); 897 898 Frame* frame = document->frame(); 899 if (!frame) 900 return String(); 901 902 // FIXME: This is always "for interchange". Is that right? See the previous method. 903 return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange); 904 } 905 906 String urlToMarkup(const KURL& url, const String& title) 907 { 908 Vector<UChar> markup; 909 append(markup, "<a href=\""); 910 append(markup, url.string()); 911 append(markup, "\">"); 912 appendCharactersReplacingEntities(markup, title.characters(), title.length(), EntityMaskInPCDATA); 913 append(markup, "</a>"); 914 return String::adopt(markup); 915 } 916 917 } 918