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