1 /* 2 * Copyright (C) 2005, 2006, 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 "core/editing/ApplyStyleCommand.h" 28 29 #include "CSSPropertyNames.h" 30 #include "CSSValueKeywords.h" 31 #include "HTMLNames.h" 32 #include "core/css/CSSComputedStyleDeclaration.h" 33 #include "core/css/CSSValuePool.h" 34 #include "core/css/StylePropertySet.h" 35 #include "core/dom/Document.h" 36 #include "core/dom/NodeList.h" 37 #include "core/dom/NodeTraversal.h" 38 #include "core/dom/Range.h" 39 #include "core/dom/Text.h" 40 #include "core/editing/EditingStyle.h" 41 #include "core/editing/HTMLInterchange.h" 42 #include "core/editing/PlainTextRange.h" 43 #include "core/editing/TextIterator.h" 44 #include "core/editing/VisibleUnits.h" 45 #include "core/editing/htmlediting.h" 46 #include "core/rendering/RenderObject.h" 47 #include "core/rendering/RenderText.h" 48 #include "wtf/StdLibExtras.h" 49 #include "wtf/text/StringBuilder.h" 50 51 namespace WebCore { 52 53 using namespace HTMLNames; 54 55 static String& styleSpanClassString() 56 { 57 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass))); 58 return styleSpanClassString; 59 } 60 61 bool isLegacyAppleStyleSpan(const Node *node) 62 { 63 if (!node || !node->isHTMLElement()) 64 return false; 65 66 const HTMLElement* elem = toHTMLElement(node); 67 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString(); 68 } 69 70 static bool hasNoAttributeOrOnlyStyleAttribute(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) 71 { 72 if (!element->hasAttributes()) 73 return true; 74 75 unsigned matchedAttributes = 0; 76 if (element->getAttribute(classAttr) == styleSpanClassString()) 77 matchedAttributes++; 78 if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute 79 || !element->inlineStyle() || element->inlineStyle()->isEmpty())) 80 matchedAttributes++; 81 82 ASSERT(matchedAttributes <= element->attributeCount()); 83 return matchedAttributes == element->attributeCount(); 84 } 85 86 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element) 87 { 88 if (!element || !element->hasTagName(spanTag)) 89 return false; 90 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute); 91 } 92 93 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node) 94 { 95 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 96 return false; 97 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty); 98 } 99 100 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) 101 { 102 if (!element || !element->hasTagName(fontTag)) 103 return false; 104 105 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty); 106 } 107 108 static PassRefPtr<Element> createFontElement(Document& document) 109 { 110 RefPtr<Element> fontNode = createHTMLElement(document, fontTag); 111 return fontNode.release(); 112 } 113 114 PassRefPtr<HTMLElement> createStyleSpanElement(Document& document) 115 { 116 RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag); 117 return styleElement.release(); 118 } 119 120 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel) 121 : CompositeEditCommand(document) 122 , m_style(style->copy()) 123 , m_editingAction(editingAction) 124 , m_propertyLevel(propertyLevel) 125 , m_start(endingSelection().start().downstream()) 126 , m_end(endingSelection().end().upstream()) 127 , m_useEndingSelection(true) 128 , m_styledInlineElement(0) 129 , m_removeOnly(false) 130 , m_isInlineElementToRemoveFunction(0) 131 { 132 } 133 134 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel) 135 : CompositeEditCommand(document) 136 , m_style(style->copy()) 137 , m_editingAction(editingAction) 138 , m_propertyLevel(propertyLevel) 139 , m_start(start) 140 , m_end(end) 141 , m_useEndingSelection(false) 142 , m_styledInlineElement(0) 143 , m_removeOnly(false) 144 , m_isInlineElementToRemoveFunction(0) 145 { 146 } 147 148 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction) 149 : CompositeEditCommand(element->document()) 150 , m_style(EditingStyle::create()) 151 , m_editingAction(editingAction) 152 , m_propertyLevel(PropertyDefault) 153 , m_start(endingSelection().start().downstream()) 154 , m_end(endingSelection().end().upstream()) 155 , m_useEndingSelection(true) 156 , m_styledInlineElement(element) 157 , m_removeOnly(removeOnly) 158 , m_isInlineElementToRemoveFunction(0) 159 { 160 } 161 162 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction) 163 : CompositeEditCommand(document) 164 , m_style(style->copy()) 165 , m_editingAction(editingAction) 166 , m_propertyLevel(PropertyDefault) 167 , m_start(endingSelection().start().downstream()) 168 , m_end(endingSelection().end().upstream()) 169 , m_useEndingSelection(true) 170 , m_styledInlineElement(0) 171 , m_removeOnly(true) 172 , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction) 173 { 174 } 175 176 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd) 177 { 178 ASSERT(comparePositions(newEnd, newStart) >= 0); 179 180 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end)) 181 m_useEndingSelection = true; 182 183 setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional())); 184 m_start = newStart; 185 m_end = newEnd; 186 } 187 188 Position ApplyStyleCommand::startPosition() 189 { 190 if (m_useEndingSelection) 191 return endingSelection().start(); 192 193 return m_start; 194 } 195 196 Position ApplyStyleCommand::endPosition() 197 { 198 if (m_useEndingSelection) 199 return endingSelection().end(); 200 201 return m_end; 202 } 203 204 void ApplyStyleCommand::doApply() 205 { 206 switch (m_propertyLevel) { 207 case PropertyDefault: { 208 // Apply the block-centric properties of the style. 209 RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties(); 210 if (!blockStyle->isEmpty()) 211 applyBlockStyle(blockStyle.get()); 212 // Apply any remaining styles to the inline elements. 213 if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) { 214 applyRelativeFontStyleChange(m_style.get()); 215 applyInlineStyle(m_style.get()); 216 } 217 break; 218 } 219 case ForceBlockProperties: 220 // Force all properties to be applied as block styles. 221 applyBlockStyle(m_style.get()); 222 break; 223 } 224 } 225 226 EditAction ApplyStyleCommand::editingAction() const 227 { 228 return m_editingAction; 229 } 230 231 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) 232 { 233 // update document layout once before removing styles 234 // so that we avoid the expense of updating before each and every call 235 // to check a computed style 236 document().updateLayoutIgnorePendingStylesheets(); 237 238 // get positions we want to use for applying style 239 Position start = startPosition(); 240 Position end = endPosition(); 241 if (comparePositions(end, start) < 0) { 242 Position swap = start; 243 start = end; 244 end = swap; 245 } 246 247 VisiblePosition visibleStart(start); 248 VisiblePosition visibleEnd(end); 249 250 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan()) 251 return; 252 253 // Save and restore the selection endpoints using their indices in the document, since 254 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints. 255 // Calculate start and end indices from the start of the tree that they're in. 256 Node* scope = visibleStart.deepEquivalent().deprecatedNode()->highestAncestor(); 257 RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent()); 258 RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent()); 259 int startIndex = TextIterator::rangeLength(startRange.get(), true); 260 int endIndex = TextIterator::rangeLength(endRange.get(), true); 261 262 VisiblePosition paragraphStart(startOfParagraph(visibleStart)); 263 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); 264 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); 265 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { 266 StyleChange styleChange(style, paragraphStart.deepEquivalent()); 267 if (styleChange.cssStyle().length() || m_removeOnly) { 268 RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode()); 269 if (!m_removeOnly) { 270 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent()); 271 if (newBlock) 272 block = newBlock; 273 } 274 ASSERT(!block || block->isHTMLElement()); 275 if (block && block->isHTMLElement()) { 276 removeCSSStyle(style, toHTMLElement(block)); 277 if (!m_removeOnly) 278 addBlockStyle(styleChange, toHTMLElement(block)); 279 } 280 281 if (nextParagraphStart.isOrphan()) 282 nextParagraphStart = endOfParagraph(paragraphStart).next(); 283 } 284 285 paragraphStart = nextParagraphStart; 286 nextParagraphStart = endOfParagraph(paragraphStart).next(); 287 } 288 289 startRange = PlainTextRange(startIndex).createRangeForSelection(*toContainerNode(scope)); 290 endRange = PlainTextRange(endIndex).createRangeForSelection(*toContainerNode(scope)); 291 if (startRange && endRange) 292 updateStartEnd(startRange->startPosition(), endRange->startPosition()); 293 } 294 295 static PassRefPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style) 296 { 297 if (!style) 298 return MutableStylePropertySet::create(); 299 return style->mutableCopy(); 300 } 301 302 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) 303 { 304 static const float MinimumFontSize = 0.1f; 305 306 if (!style || !style->hasFontSizeDelta()) 307 return; 308 309 Position start = startPosition(); 310 Position end = endPosition(); 311 if (comparePositions(end, start) < 0) { 312 Position swap = start; 313 start = end; 314 end = swap; 315 } 316 317 // Join up any adjacent text nodes. 318 if (start.deprecatedNode()->isTextNode()) { 319 joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end); 320 start = startPosition(); 321 end = endPosition(); 322 } 323 324 if (start.isNull() || end.isNull()) 325 return; 326 327 if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) { 328 joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end); 329 start = startPosition(); 330 end = endPosition(); 331 } 332 333 if (start.isNull() || end.isNull()) 334 return; 335 336 // Split the start text nodes if needed to apply style. 337 if (isValidCaretPositionInTextNode(start)) { 338 splitTextAtStart(start, end); 339 start = startPosition(); 340 end = endPosition(); 341 } 342 343 if (isValidCaretPositionInTextNode(end)) { 344 splitTextAtEnd(start, end); 345 start = startPosition(); 346 end = endPosition(); 347 } 348 349 // Calculate loop end point. 350 // If the end node is before the start node (can only happen if the end node is 351 // an ancestor of the start node), we gather nodes up to the next sibling of the end node 352 Node *beyondEnd; 353 if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) 354 beyondEnd = NodeTraversal::nextSkippingChildren(*end.deprecatedNode()); 355 else 356 beyondEnd = NodeTraversal::next(*end.deprecatedNode()); 357 358 start = start.upstream(); // Move upstream to ensure we do not add redundant spans. 359 Node* startNode = start.deprecatedNode(); 360 ASSERT(startNode); 361 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. 362 startNode = NodeTraversal::next(*startNode); 363 364 // Store away font size before making any changes to the document. 365 // This ensures that changes to one node won't effect another. 366 HashMap<Node*, float> startingFontSizes; 367 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) 368 startingFontSizes.set(node, computedFontSize(node)); 369 370 // These spans were added by us. If empty after font size changes, they can be removed. 371 Vector<RefPtr<HTMLElement> > unstyledSpans; 372 373 Node* lastStyledNode = 0; 374 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) { 375 RefPtr<HTMLElement> element; 376 if (node->isHTMLElement()) { 377 // Only work on fully selected nodes. 378 if (!nodeFullySelected(node, start, end)) 379 continue; 380 element = toHTMLElement(node); 381 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { 382 // Last styled node was not parent node of this text node, but we wish to style this 383 // text node. To make this possible, add a style span to surround this text node. 384 RefPtr<HTMLElement> span = createStyleSpanElement(document()); 385 surroundNodeRangeWithElement(node, node, span.get()); 386 element = span.release(); 387 } else { 388 // Only handle HTML elements and text nodes. 389 continue; 390 } 391 lastStyledNode = node; 392 393 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 394 float currentFontSize = computedFontSize(node); 395 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta()); 396 RefPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize); 397 if (value) { 398 element->removeInlineStyleProperty(CSSPropertyFontSize); 399 currentFontSize = computedFontSize(node); 400 } 401 if (currentFontSize != desiredFontSize) { 402 inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false); 403 setNodeAttribute(element.get(), styleAttr, AtomicString(inlineStyle->asText())); 404 } 405 if (inlineStyle->isEmpty()) { 406 removeNodeAttribute(element.get(), styleAttr); 407 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get())) 408 unstyledSpans.append(element.release()); 409 } 410 } 411 412 size_t size = unstyledSpans.size(); 413 for (size_t i = 0; i < size; ++i) 414 removeNodePreservingChildren(unstyledSpans[i].get()); 415 } 416 417 static Node* dummySpanAncestorForNode(const Node* node) 418 { 419 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node)))) 420 node = node->parentNode(); 421 422 return node ? node->parentNode() : 0; 423 } 424 425 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor) 426 { 427 if (!dummySpanAncestor) 428 return; 429 430 // Dummy spans are created when text node is split, so that style information 431 // can be propagated, which can result in more splitting. If a dummy span gets 432 // cloned/split, the new node is always a sibling of it. Therefore, we scan 433 // all the children of the dummy's parent 434 Node* next; 435 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) { 436 next = node->nextSibling(); 437 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node)) 438 removeNodePreservingChildren(node); 439 } 440 } 441 442 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection) 443 { 444 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection. 445 // In that case, we return the unsplit ancestor. Otherwise, we return 0. 446 Node* block = enclosingBlock(node); 447 if (!block) 448 return 0; 449 450 Node* highestAncestorWithUnicodeBidi = 0; 451 Node* nextHighestAncestorWithUnicodeBidi = 0; 452 int highestAncestorUnicodeBidi = 0; 453 for (Node* n = node->parentNode(); n != block; n = n->parentNode()) { 454 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi); 455 if (unicodeBidi && unicodeBidi != CSSValueNormal) { 456 highestAncestorUnicodeBidi = unicodeBidi; 457 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi; 458 highestAncestorWithUnicodeBidi = n; 459 } 460 } 461 462 if (!highestAncestorWithUnicodeBidi) 463 return 0; 464 465 HTMLElement* unsplitAncestor = 0; 466 467 WritingDirection highestAncestorDirection; 468 if (allowedDirection != NaturalWritingDirection 469 && highestAncestorUnicodeBidi != CSSValueBidiOverride 470 && highestAncestorWithUnicodeBidi->isHTMLElement() 471 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection) 472 && highestAncestorDirection == allowedDirection) { 473 if (!nextHighestAncestorWithUnicodeBidi) 474 return toHTMLElement(highestAncestorWithUnicodeBidi); 475 476 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi); 477 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; 478 } 479 480 // Split every ancestor through highest ancestor with embedding. 481 RefPtr<Node> currentNode = node; 482 while (currentNode) { 483 RefPtr<Element> parent = toElement(currentNode->parentNode()); 484 if (before ? currentNode->previousSibling() : currentNode->nextSibling()) 485 splitElement(parent, before ? currentNode : currentNode->nextSibling()); 486 if (parent == highestAncestorWithUnicodeBidi) 487 break; 488 currentNode = parent; 489 } 490 return unsplitAncestor; 491 } 492 493 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor) 494 { 495 Node* block = enclosingBlock(node); 496 if (!block) 497 return; 498 499 Node* parent = 0; 500 for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) { 501 parent = n->parentNode(); 502 if (!n->isStyledElement()) 503 continue; 504 505 Element* element = toElement(n); 506 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(element).get(), CSSPropertyUnicodeBidi); 507 if (!unicodeBidi || unicodeBidi == CSSValueNormal) 508 continue; 509 510 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration, 511 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. 512 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and 513 // otherwise it sets the property in the inline style declaration. 514 if (element->hasAttribute(dirAttr)) { 515 // FIXME: If this is a BDO element, we should probably just remove it if it has no 516 // other attributes, like we (should) do with B and I elements. 517 removeNodeAttribute(element, dirAttr); 518 } else { 519 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 520 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); 521 inlineStyle->removeProperty(CSSPropertyDirection); 522 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText())); 523 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 524 removeNodePreservingChildren(element); 525 } 526 } 527 } 528 529 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode) 530 { 531 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) { 532 if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) 533 return n; 534 } 535 536 return 0; 537 } 538 539 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) 540 { 541 RefPtr<Node> startDummySpanAncestor = 0; 542 RefPtr<Node> endDummySpanAncestor = 0; 543 544 // update document layout once before removing styles 545 // so that we avoid the expense of updating before each and every call 546 // to check a computed style 547 document().updateLayoutIgnorePendingStylesheets(); 548 549 // adjust to the positions we want to use for applying style 550 Position start = startPosition(); 551 Position end = endPosition(); 552 553 if (start.isNull() || end.isNull()) 554 return; 555 556 if (comparePositions(end, start) < 0) { 557 Position swap = start; 558 start = end; 559 end = swap; 560 } 561 562 // split the start node and containing element if the selection starts inside of it 563 bool splitStart = isValidCaretPositionInTextNode(start); 564 if (splitStart) { 565 if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style)) 566 splitTextElementAtStart(start, end); 567 else 568 splitTextAtStart(start, end); 569 start = startPosition(); 570 end = endPosition(); 571 startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode()); 572 } 573 574 // split the end node and containing element if the selection ends inside of it 575 bool splitEnd = isValidCaretPositionInTextNode(end); 576 if (splitEnd) { 577 if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style)) 578 splitTextElementAtEnd(start, end); 579 else 580 splitTextAtEnd(start, end); 581 start = startPosition(); 582 end = endPosition(); 583 endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode()); 584 } 585 586 // Remove style from the selection. 587 // Use the upstream position of the start for removing style. 588 // This will ensure we remove all traces of the relevant styles from the selection 589 // and prevent us from adding redundant ones, as described in: 590 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags 591 Position removeStart = start.upstream(); 592 WritingDirection textDirection = NaturalWritingDirection; 593 bool hasTextDirection = style->textDirection(textDirection); 594 RefPtr<EditingStyle> styleWithoutEmbedding; 595 RefPtr<EditingStyle> embeddingStyle; 596 if (hasTextDirection) { 597 // Leave alone an ancestor that provides the desired single level embedding, if there is one. 598 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection); 599 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection); 600 removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor); 601 removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor); 602 603 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors. 604 Position embeddingRemoveStart = removeStart; 605 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end)) 606 embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor); 607 608 Position embeddingRemoveEnd = end; 609 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end)) 610 embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream(); 611 612 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) { 613 styleWithoutEmbedding = style->copy(); 614 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); 615 616 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0) 617 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd); 618 } 619 } 620 621 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end); 622 start = startPosition(); 623 end = endPosition(); 624 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan()) 625 return; 626 627 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) { 628 start = startPosition(); 629 end = endPosition(); 630 } 631 632 if (splitEnd) { 633 mergeEndWithNextIfIdentical(start, end); 634 start = startPosition(); 635 end = endPosition(); 636 } 637 638 // update document layout once before running the rest of the function 639 // so that we avoid the expense of updating before each and every call 640 // to check a computed style 641 document().updateLayoutIgnorePendingStylesheets(); 642 643 RefPtr<EditingStyle> styleToApply = style; 644 if (hasTextDirection) { 645 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them. 646 Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode())); 647 Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode())); 648 649 if (embeddingStartNode || embeddingEndNode) { 650 Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start; 651 Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end; 652 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()); 653 654 if (!embeddingStyle) { 655 styleWithoutEmbedding = style->copy(); 656 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); 657 } 658 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd); 659 660 styleToApply = styleWithoutEmbedding; 661 } 662 } 663 664 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end); 665 666 // Remove dummy style spans created by splitting text elements. 667 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get()); 668 if (endDummySpanAncestor != startDummySpanAncestor) 669 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get()); 670 } 671 672 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end) 673 { 674 Node* startNode = start.deprecatedNode(); 675 ASSERT(startNode); 676 677 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) { 678 startNode = NodeTraversal::next(*startNode); 679 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0) 680 return; 681 } 682 683 Node* pastEndNode = end.deprecatedNode(); 684 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode())) 685 pastEndNode = NodeTraversal::nextSkippingChildren(*end.deprecatedNode()); 686 687 // FIXME: Callers should perform this operation on a Range that includes the br 688 // if they want style applied to the empty line. 689 if (start == end && start.deprecatedNode()->hasTagName(brTag)) 690 pastEndNode = NodeTraversal::next(*start.deprecatedNode()); 691 692 // Start from the highest fully selected ancestor so that we can modify the fully selected node. 693 // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run 694 // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font> 695 RefPtr<Range> range = Range::create(startNode->document(), start, end); 696 Element* editableRoot = startNode->rootEditableElement(); 697 if (startNode != editableRoot) { 698 while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get())) 699 startNode = startNode->parentNode(); 700 } 701 702 applyInlineStyleToNodeRange(style, startNode, pastEndNode); 703 } 704 705 static bool containsNonEditableRegion(Node& node) 706 { 707 if (!node.rendererIsEditable()) 708 return true; 709 710 Node* sibling = NodeTraversal::nextSkippingChildren(node); 711 for (Node* descendent = node.firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(*descendent)) { 712 if (!descendent->rendererIsEditable()) 713 return true; 714 } 715 716 return false; 717 } 718 719 struct InlineRunToApplyStyle { 720 InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode) 721 : start(start) 722 , end(end) 723 , pastEndNode(pastEndNode) 724 { 725 ASSERT(start->parentNode() == end->parentNode()); 726 } 727 728 bool startAndEndAreStillInDocument() 729 { 730 return start && end && start->inDocument() && end->inDocument(); 731 } 732 733 RefPtr<Node> start; 734 RefPtr<Node> end; 735 RefPtr<Node> pastEndNode; 736 Position positionForStyleComputation; 737 RefPtr<Node> dummyElement; 738 StyleChange change; 739 }; 740 741 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode) 742 { 743 if (m_removeOnly) 744 return; 745 746 document().updateLayoutIgnorePendingStylesheets(); 747 748 Vector<InlineRunToApplyStyle> runs; 749 RefPtr<Node> node = startNode; 750 for (RefPtr<Node> next; node && node != pastEndNode; node = next) { 751 next = NodeTraversal::next(*node); 752 753 if (!node->renderer() || !node->rendererIsEditable()) 754 continue; 755 756 if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) { 757 // This is a plaintext-only region. Only proceed if it's fully selected. 758 // pastEndNode is the node after the last fully selected node, so if it's inside node then 759 // node isn't fully selected. 760 if (pastEndNode && pastEndNode->isDescendantOf(node.get())) 761 break; 762 // Add to this element's inline style and skip over its contents. 763 HTMLElement* element = toHTMLElement(node); 764 next = NodeTraversal::nextSkippingChildren(*node); 765 if (!style->style()) 766 continue; 767 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 768 inlineStyle->mergeAndOverrideOnConflict(style->style()); 769 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText())); 770 continue; 771 } 772 773 if (isBlock(node.get())) 774 continue; 775 776 if (node->childNodeCount()) { 777 if (node->contains(pastEndNode.get()) || containsNonEditableRegion(*node) || !node->parentNode()->rendererIsEditable()) 778 continue; 779 if (editingIgnoresContent(node.get())) { 780 next = NodeTraversal::nextSkippingChildren(*node); 781 continue; 782 } 783 } 784 785 Node* runStart = node.get(); 786 Node* runEnd = node.get(); 787 Node* sibling = node->nextSibling(); 788 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get()) 789 && (!isBlock(sibling) || sibling->hasTagName(brTag)) 790 && !containsNonEditableRegion(*sibling)) { 791 runEnd = sibling; 792 sibling = runEnd->nextSibling(); 793 } 794 ASSERT(runEnd); 795 next = NodeTraversal::nextSkippingChildren(*runEnd); 796 797 Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd); 798 if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode)) 799 continue; 800 801 runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode)); 802 } 803 804 for (size_t i = 0; i < runs.size(); i++) { 805 removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode); 806 runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement); 807 } 808 809 document().updateLayoutIgnorePendingStylesheets(); 810 811 for (size_t i = 0; i < runs.size(); i++) 812 runs[i].change = StyleChange(style, runs[i].positionForStyleComputation); 813 814 for (size_t i = 0; i < runs.size(); i++) { 815 InlineRunToApplyStyle& run = runs[i]; 816 if (run.dummyElement) 817 removeNode(run.dummyElement); 818 if (run.startAndEndAreStillInDocument()) 819 applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement); 820 } 821 } 822 823 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const 824 { 825 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) 826 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element)); 827 } 828 829 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode) 830 { 831 ASSERT(style && runStart); 832 833 for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(*node)) { 834 if (node->childNodeCount()) 835 continue; 836 // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified 837 if (!style->styleIsPresentInComputedStyleOfNode(node)) 838 return true; 839 if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName())) 840 return true; 841 } 842 return false; 843 } 844 845 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode) 846 { 847 ASSERT(runStart && runEnd); 848 RefPtr<Node> next = runStart; 849 for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) { 850 if (editingIgnoresContent(node.get())) { 851 ASSERT(!node->contains(pastEndNode.get())); 852 next = NodeTraversal::nextSkippingChildren(*node); 853 } else { 854 next = NodeTraversal::next(*node); 855 } 856 if (!node->isHTMLElement()) 857 continue; 858 859 RefPtr<Node> previousSibling = node->previousSibling(); 860 RefPtr<Node> nextSibling = node->nextSibling(); 861 RefPtr<ContainerNode> parent = node->parentNode(); 862 removeInlineStyleFromElement(style, toHTMLElement(node), RemoveAlways); 863 if (!node->inDocument()) { 864 // FIXME: We might need to update the start and the end of current selection here but need a test. 865 if (runStart == node) 866 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild(); 867 if (runEnd == node) 868 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild(); 869 } 870 } 871 } 872 873 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 874 { 875 ASSERT(element); 876 877 if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) 878 return false; 879 880 if (isStyledInlineElementToRemove(element.get())) { 881 if (mode == RemoveNone) 882 return true; 883 ASSERT(extractedStyle); 884 extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues); 885 removeNodePreservingChildren(element); 886 return true; 887 } 888 889 bool removed = false; 890 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) 891 removed = true; 892 893 if (!element->inDocument()) 894 return removed; 895 896 // If the node was converted to a span, the span may still contain relevant 897 // styles which must be removed (e.g. <b style='font-weight: bold'>) 898 if (removeCSSStyle(style, element.get(), mode, extractedStyle)) 899 removed = true; 900 901 return removed; 902 } 903 904 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem) 905 { 906 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty)) 907 removeNodePreservingChildren(elem); 908 else { 909 HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem); 910 ASSERT(newSpanElement && newSpanElement->inDocument()); 911 elem = newSpanElement; 912 } 913 } 914 915 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 916 { 917 ASSERT(style); 918 if (mode == RemoveNone) { 919 ASSERT(!extractedStyle); 920 return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element); 921 } 922 923 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways); 924 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) { 925 replaceWithSpanOrRemoveIfWithoutAttributes(element); 926 return true; 927 } 928 929 // unicode-bidi and direction are pushed down separately so don't push down with other styles 930 Vector<QualifiedName> attributes; 931 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection, 932 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) 933 return false; 934 935 for (size_t i = 0; i < attributes.size(); i++) 936 removeNodeAttribute(element, attributes[i]); 937 938 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 939 removeNodePreservingChildren(element); 940 941 return true; 942 } 943 944 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 945 { 946 ASSERT(style); 947 ASSERT(element); 948 949 if (mode == RemoveNone) 950 return style->conflictsWithInlineStyleOfElement(element); 951 952 Vector<CSSPropertyID> properties; 953 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties)) 954 return false; 955 956 // FIXME: We should use a mass-removal function here but we don't have an undoable one yet. 957 for (size_t i = 0; i < properties.size(); i++) 958 removeCSSProperty(element, properties[i]); 959 960 // No need to serialize <foo style=""> if we just removed the last css property 961 if (element->inlineStyle()->isEmpty()) 962 removeNodeAttribute(element, styleAttr); 963 964 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 965 removeNodePreservingChildren(element); 966 967 return true; 968 } 969 970 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node) 971 { 972 if (!node) 973 return 0; 974 975 HTMLElement* result = 0; 976 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node)); 977 978 for (Node *n = node; n; n = n->parentNode()) { 979 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n))) 980 result = toHTMLElement(n); 981 // Should stop at the editable root (cannot cross editing boundary) and 982 // also stop at the unsplittable element to be consistent with other UAs 983 if (n == unsplittableElement) 984 break; 985 } 986 987 return result; 988 } 989 990 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style) 991 { 992 ASSERT(node); 993 994 node->document().updateStyleIfNeeded(); 995 996 if (!style || style->isEmpty() || !node->renderer()) 997 return; 998 999 RefPtr<EditingStyle> newInlineStyle = style; 1000 if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) { 1001 newInlineStyle = style->copy(); 1002 newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues); 1003 } 1004 1005 // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead. 1006 // FIXME: applyInlineStyleToRange should be used here instead. 1007 if ((node->renderer()->isRenderBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) { 1008 setNodeAttribute(toHTMLElement(node), styleAttr, AtomicString(newInlineStyle->style()->asText())); 1009 return; 1010 } 1011 1012 if (node->renderer()->isText() && toRenderText(node->renderer())->isAllCollapsibleWhitespace()) 1013 return; 1014 1015 // We can't wrap node with the styled element here because new styled element will never be removed if we did. 1016 // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element 1017 // then we fall into an infinite loop where we keep removing and adding styled element wrapping node. 1018 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement); 1019 } 1020 1021 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode) 1022 { 1023 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode); 1024 if (!highestAncestor) 1025 return; 1026 1027 // The outer loop is traversing the tree vertically from highestAncestor to targetNode 1028 RefPtr<Node> current = highestAncestor; 1029 // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown. 1030 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown. 1031 Vector<RefPtr<Element> > elementsToPushDown; 1032 while (current && current != targetNode && current->contains(targetNode)) { 1033 NodeVector currentChildren; 1034 getChildNodes(*current, currentChildren); 1035 RefPtr<Element> styledElement; 1036 if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current))) { 1037 styledElement = toElement(current); 1038 elementsToPushDown.append(styledElement); 1039 } 1040 1041 RefPtr<EditingStyle> styleToPushDown = EditingStyle::create(); 1042 if (current->isHTMLElement()) 1043 removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get()); 1044 1045 // The inner loop will go through children on each level 1046 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately. 1047 for (size_t i = 0; i < currentChildren.size(); ++i) { 1048 Node* child = currentChildren[i].get(); 1049 if (!child->parentNode()) 1050 continue; 1051 if (!child->contains(targetNode) && elementsToPushDown.size()) { 1052 for (size_t i = 0; i < elementsToPushDown.size(); i++) { 1053 RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren(); 1054 wrapper->removeAttribute(styleAttr); 1055 surroundNodeRangeWithElement(child, child, wrapper); 1056 } 1057 } 1058 1059 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode 1060 // But if we've removed styledElement then go ahead and always apply the style. 1061 if (child != targetNode || styledElement) 1062 applyInlineStyleToPushDown(child, styleToPushDown.get()); 1063 1064 // We found the next node for the outer loop (contains targetNode) 1065 // When reached targetNode, stop the outer loop upon the completion of the current inner loop 1066 if (child == targetNode || child->contains(targetNode)) 1067 current = child; 1068 } 1069 } 1070 } 1071 1072 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end) 1073 { 1074 ASSERT(start.isNotNull()); 1075 ASSERT(end.isNotNull()); 1076 ASSERT(start.inDocument()); 1077 ASSERT(end.inDocument()); 1078 ASSERT(comparePositions(start, end) <= 0); 1079 // FIXME: We should assert that start/end are not in the middle of a text node. 1080 1081 Position pushDownStart = start.downstream(); 1082 // If the pushDownStart is at the end of a text node, then this node is not fully selected. 1083 // Move it to the next deep quivalent position to avoid removing the style from this node. 1084 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead. 1085 Node* pushDownStartContainer = pushDownStart.containerNode(); 1086 if (pushDownStartContainer && pushDownStartContainer->isTextNode() 1087 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset()) 1088 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart); 1089 Position pushDownEnd = end.upstream(); 1090 // If pushDownEnd is at the start of a text node, then this node is not fully selected. 1091 // Move it to the previous deep equivalent position to avoid removing the style from this node. 1092 Node* pushDownEndContainer = pushDownEnd.containerNode(); 1093 if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode()) 1094 pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd); 1095 1096 pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode()); 1097 pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode()); 1098 1099 // The s and e variables store the positions used to set the ending selection after style removal 1100 // takes place. This will help callers to recognize when either the start node or the end node 1101 // are removed from the document during the work of this function. 1102 // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(), 1103 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune. 1104 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start; 1105 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end; 1106 1107 RefPtr<Node> node = start.deprecatedNode(); 1108 while (node) { 1109 RefPtr<Node> next; 1110 if (editingIgnoresContent(node.get())) { 1111 ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode())); 1112 next = NodeTraversal::nextSkippingChildren(*node); 1113 } else { 1114 next = NodeTraversal::next(*node); 1115 } 1116 if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) { 1117 RefPtr<HTMLElement> elem = toHTMLElement(node); 1118 RefPtr<Node> prev = NodeTraversal::previousPostOrder(*elem); 1119 RefPtr<Node> next = NodeTraversal::next(*elem); 1120 RefPtr<EditingStyle> styleToPushDown; 1121 RefPtr<Node> childNode; 1122 if (isStyledInlineElementToRemove(elem.get())) { 1123 styleToPushDown = EditingStyle::create(); 1124 childNode = elem->firstChild(); 1125 } 1126 1127 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get()); 1128 if (!elem->inDocument()) { 1129 if (s.deprecatedNode() == elem) { 1130 // Since elem must have been fully selected, and it is at the start 1131 // of the selection, it is clear we can set the new s offset to 0. 1132 ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0); 1133 s = firstPositionInOrBeforeNode(next.get()); 1134 } 1135 if (e.deprecatedNode() == elem) { 1136 // Since elem must have been fully selected, and it is at the end 1137 // of the selection, it is clear we can set the new e offset to 1138 // the max range offset of prev. 1139 ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode())); 1140 e = lastPositionInOrAfterNode(prev.get()); 1141 } 1142 } 1143 1144 if (styleToPushDown) { 1145 for (; childNode; childNode = childNode->nextSibling()) 1146 applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get()); 1147 } 1148 } 1149 if (node == end.deprecatedNode()) 1150 break; 1151 node = next; 1152 } 1153 1154 updateStartEnd(s, e); 1155 } 1156 1157 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const 1158 { 1159 ASSERT(node); 1160 ASSERT(node->isElementNode()); 1161 1162 // The tree may have changed and Position::upstream() relies on an up-to-date layout. 1163 node->document().updateLayoutIgnorePendingStylesheets(); 1164 1165 return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0 1166 && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0; 1167 } 1168 1169 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const 1170 { 1171 ASSERT(node); 1172 ASSERT(node->isElementNode()); 1173 1174 bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0; 1175 bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0; 1176 1177 return isFullyBeforeStart || isFullyAfterEnd; 1178 } 1179 1180 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end) 1181 { 1182 ASSERT(start.containerNode()->isTextNode()); 1183 1184 Position newEnd; 1185 if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode()) 1186 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode()); 1187 else 1188 newEnd = end; 1189 1190 RefPtr<Text> text = start.containerText(); 1191 splitTextNode(text, start.offsetInContainerNode()); 1192 updateStartEnd(firstPositionInNode(text.get()), newEnd); 1193 } 1194 1195 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end) 1196 { 1197 ASSERT(end.containerNode()->isTextNode()); 1198 1199 bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode(); 1200 Text* text = toText(end.deprecatedNode()); 1201 splitTextNode(text, end.offsetInContainerNode()); 1202 1203 Node* prevNode = text->previousSibling(); 1204 if (!prevNode || !prevNode->isTextNode()) 1205 return; 1206 1207 Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start; 1208 updateStartEnd(newStart, lastPositionInNode(prevNode)); 1209 } 1210 1211 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end) 1212 { 1213 ASSERT(start.containerNode()->isTextNode()); 1214 1215 Position newEnd; 1216 if (start.containerNode() == end.containerNode()) 1217 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode()); 1218 else 1219 newEnd = end; 1220 1221 splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode()); 1222 updateStartEnd(positionBeforeNode(start.containerNode()), newEnd); 1223 } 1224 1225 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end) 1226 { 1227 ASSERT(end.containerNode()->isTextNode()); 1228 1229 bool shouldUpdateStart = start.containerNode() == end.containerNode(); 1230 splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode()); 1231 1232 Node* parentElement = end.containerNode()->parentNode(); 1233 if (!parentElement || !parentElement->previousSibling()) 1234 return; 1235 Node* firstTextNode = parentElement->previousSibling()->lastChild(); 1236 if (!firstTextNode || !firstTextNode->isTextNode()) 1237 return; 1238 1239 Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start; 1240 updateStartEnd(newStart, positionAfterNode(firstTextNode)); 1241 } 1242 1243 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style) 1244 { 1245 if (!element || !element->isHTMLElement()) 1246 return false; 1247 1248 return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element)); 1249 } 1250 1251 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position) 1252 { 1253 Node* node = position.containerNode(); 1254 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode()) 1255 return false; 1256 int offsetInText = position.offsetInContainerNode(); 1257 return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node); 1258 } 1259 1260 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end) 1261 { 1262 Node* startNode = start.containerNode(); 1263 int startOffset = start.computeOffsetInContainerNode(); 1264 if (startOffset) 1265 return false; 1266 1267 if (isAtomicNode(startNode)) { 1268 // note: prior siblings could be unrendered elements. it's silly to miss the 1269 // merge opportunity just for that. 1270 if (startNode->previousSibling()) 1271 return false; 1272 1273 startNode = startNode->parentNode(); 1274 startOffset = 0; 1275 } 1276 1277 if (!startNode->isElementNode()) 1278 return false; 1279 1280 Node* previousSibling = startNode->previousSibling(); 1281 1282 if (previousSibling && areIdenticalElements(startNode, previousSibling)) { 1283 Element* previousElement = toElement(previousSibling); 1284 Element* element = toElement(startNode); 1285 Node* startChild = element->firstChild(); 1286 ASSERT(startChild); 1287 mergeIdenticalElements(previousElement, element); 1288 1289 int startOffsetAdjustment = startChild->nodeIndex(); 1290 int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0; 1291 updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor), 1292 Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor)); 1293 return true; 1294 } 1295 1296 return false; 1297 } 1298 1299 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end) 1300 { 1301 Node* endNode = end.containerNode(); 1302 1303 if (isAtomicNode(endNode)) { 1304 int endOffset = end.computeOffsetInContainerNode(); 1305 if (offsetIsBeforeLastNodeOffset(endOffset, endNode)) 1306 return false; 1307 1308 if (end.deprecatedNode()->nextSibling()) 1309 return false; 1310 1311 endNode = end.deprecatedNode()->parentNode(); 1312 } 1313 1314 if (!endNode->isElementNode() || endNode->hasTagName(brTag)) 1315 return false; 1316 1317 Node* nextSibling = endNode->nextSibling(); 1318 if (nextSibling && areIdenticalElements(endNode, nextSibling)) { 1319 Element* nextElement = toElement(nextSibling); 1320 Element* element = toElement(endNode); 1321 Node* nextChild = nextElement->firstChild(); 1322 1323 mergeIdenticalElements(element, nextElement); 1324 1325 bool shouldUpdateStart = start.containerNode() == endNode; 1326 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length(); 1327 updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start, 1328 Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor)); 1329 return true; 1330 } 1331 1332 return false; 1333 } 1334 1335 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert) 1336 { 1337 ASSERT(passedStartNode); 1338 ASSERT(endNode); 1339 ASSERT(elementToInsert); 1340 RefPtr<Node> startNode = passedStartNode; 1341 RefPtr<Element> element = elementToInsert; 1342 1343 insertNodeBefore(element, startNode); 1344 1345 RefPtr<Node> node = startNode; 1346 while (node) { 1347 RefPtr<Node> next = node->nextSibling(); 1348 if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) { 1349 removeNode(node); 1350 appendNode(node, element); 1351 } 1352 if (node == endNode) 1353 break; 1354 node = next; 1355 } 1356 1357 RefPtr<Node> nextSibling = element->nextSibling(); 1358 RefPtr<Node> previousSibling = element->previousSibling(); 1359 if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable() 1360 && areIdenticalElements(element.get(), toElement(nextSibling))) 1361 mergeIdenticalElements(element.get(), toElement(nextSibling)); 1362 1363 if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) { 1364 Node* mergedElement = previousSibling->nextSibling(); 1365 if (mergedElement->isElementNode() && mergedElement->rendererIsEditable() 1366 && areIdenticalElements(toElement(previousSibling), toElement(mergedElement))) 1367 mergeIdenticalElements(toElement(previousSibling), toElement(mergedElement)); 1368 } 1369 1370 // FIXME: We should probably call updateStartEnd if the start or end was in the node 1371 // range so that the endingSelection() is canonicalized. See the comments at the end of 1372 // VisibleSelection::validate(). 1373 } 1374 1375 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block) 1376 { 1377 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for 1378 // inline content. 1379 if (!block) 1380 return; 1381 1382 String cssStyle = styleChange.cssStyle(); 1383 StringBuilder cssText; 1384 cssText.append(cssStyle); 1385 if (const StylePropertySet* decl = block->inlineStyle()) { 1386 if (!cssStyle.isEmpty()) 1387 cssText.append(' '); 1388 cssText.append(decl->asText()); 1389 } 1390 setNodeAttribute(block, styleAttr, cssText.toAtomicString()); 1391 } 1392 1393 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement) 1394 { 1395 if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument()) 1396 return; 1397 1398 RefPtr<Node> start = passedStart; 1399 RefPtr<Node> dummyElement; 1400 StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dummyElement)); 1401 1402 if (dummyElement) 1403 removeNode(dummyElement); 1404 1405 applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement); 1406 } 1407 1408 Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement) 1409 { 1410 // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run. 1411 if (!startNode->isElementNode()) { 1412 dummyElement = createStyleSpanElement(document()); 1413 insertNodeAt(dummyElement, positionBeforeNode(startNode.get())); 1414 return positionBeforeNode(dummyElement.get()); 1415 } 1416 1417 return firstPositionInOrBeforeNode(startNode.get()); 1418 } 1419 1420 void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, StyleChange& styleChange, EAddStyledElement addStyledElement) 1421 { 1422 RefPtr<Node> startNode = passedStart; 1423 RefPtr<Node> endNode = passedEnd; 1424 ASSERT(startNode->inDocument()); 1425 ASSERT(endNode->inDocument()); 1426 1427 // Find appropriate font and span elements top-down. 1428 HTMLElement* fontContainer = 0; 1429 HTMLElement* styleContainer = 0; 1430 for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) { 1431 if (container->isHTMLElement() && container->hasTagName(fontTag)) 1432 fontContainer = toHTMLElement(container); 1433 bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag); 1434 if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount()))) 1435 styleContainer = toHTMLElement(container); 1436 if (!container->firstChild()) 1437 break; 1438 startNode = container->firstChild(); 1439 endNode = container->lastChild(); 1440 } 1441 1442 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. 1443 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) { 1444 if (fontContainer) { 1445 if (styleChange.applyFontColor()) 1446 setNodeAttribute(fontContainer, colorAttr, AtomicString(styleChange.fontColor())); 1447 if (styleChange.applyFontFace()) 1448 setNodeAttribute(fontContainer, faceAttr, AtomicString(styleChange.fontFace())); 1449 if (styleChange.applyFontSize()) 1450 setNodeAttribute(fontContainer, sizeAttr, AtomicString(styleChange.fontSize())); 1451 } else { 1452 RefPtr<Element> fontElement = createFontElement(document()); 1453 if (styleChange.applyFontColor()) 1454 fontElement->setAttribute(colorAttr, AtomicString(styleChange.fontColor())); 1455 if (styleChange.applyFontFace()) 1456 fontElement->setAttribute(faceAttr, AtomicString(styleChange.fontFace())); 1457 if (styleChange.applyFontSize()) 1458 fontElement->setAttribute(sizeAttr, AtomicString(styleChange.fontSize())); 1459 surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); 1460 } 1461 } 1462 1463 if (styleChange.cssStyle().length()) { 1464 if (styleContainer) { 1465 if (const StylePropertySet* existingStyle = styleContainer->inlineStyle()) { 1466 String existingText = existingStyle->asText(); 1467 StringBuilder cssText; 1468 cssText.append(existingText); 1469 if (!existingText.isEmpty()) 1470 cssText.append(' '); 1471 cssText.append(styleChange.cssStyle()); 1472 setNodeAttribute(styleContainer, styleAttr, cssText.toAtomicString()); 1473 } else { 1474 setNodeAttribute(styleContainer, styleAttr, AtomicString(styleChange.cssStyle())); 1475 } 1476 } else { 1477 RefPtr<Element> styleElement = createStyleSpanElement(document()); 1478 styleElement->setAttribute(styleAttr, AtomicString(styleChange.cssStyle())); 1479 surroundNodeRangeWithElement(startNode, endNode, styleElement.release()); 1480 } 1481 } 1482 1483 if (styleChange.applyBold()) 1484 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag)); 1485 1486 if (styleChange.applyItalic()) 1487 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag)); 1488 1489 if (styleChange.applyUnderline()) 1490 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag)); 1491 1492 if (styleChange.applyLineThrough()) 1493 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag)); 1494 1495 if (styleChange.applySubscript()) 1496 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag)); 1497 else if (styleChange.applySuperscript()) 1498 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag)); 1499 1500 if (m_styledInlineElement && addStyledElement == AddStyledElement) 1501 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren()); 1502 } 1503 1504 float ApplyStyleCommand::computedFontSize(Node* node) 1505 { 1506 if (!node) 1507 return 0; 1508 1509 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node); 1510 if (!style) 1511 return 0; 1512 1513 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize)); 1514 if (!value) 1515 return 0; 1516 1517 return value->getFloatValue(CSSPrimitiveValue::CSS_PX); 1518 } 1519 1520 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end) 1521 { 1522 if (!node) 1523 return; 1524 1525 Position newStart = start; 1526 Position newEnd = end; 1527 1528 Vector<RefPtr<Text> > textNodes; 1529 for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) { 1530 if (!curr->isTextNode()) 1531 continue; 1532 1533 textNodes.append(toText(curr)); 1534 } 1535 1536 for (size_t i = 0; i < textNodes.size(); ++i) { 1537 Text* childText = textNodes[i].get(); 1538 Node* next = childText->nextSibling(); 1539 if (!next || !next->isTextNode()) 1540 continue; 1541 1542 Text* nextText = toText(next); 1543 if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode()) 1544 newStart = Position(childText, childText->length() + start.offsetInContainerNode()); 1545 if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode()) 1546 newEnd = Position(childText, childText->length() + end.offsetInContainerNode()); 1547 String textToMove = nextText->data(); 1548 insertTextIntoNode(childText, childText->length(), textToMove); 1549 removeNode(next); 1550 // don't move child node pointer. it may want to merge with more text nodes. 1551 } 1552 1553 updateStartEnd(newStart, newEnd); 1554 } 1555 1556 } 1557