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