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/rendering/RenderObject.h" 48 #include "core/rendering/RenderText.h" 49 #include "platform/heap/Handle.h" 50 #include "wtf/StdLibExtras.h" 51 #include "wtf/text/StringBuilder.h" 52 53 namespace WebCore { 54 55 using namespace HTMLNames; 56 57 static String& styleSpanClassString() 58 { 59 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass))); 60 return styleSpanClassString; 61 } 62 63 bool isLegacyAppleStyleSpan(const Node *node) 64 { 65 if (!node || !node->isHTMLElement()) 66 return false; 67 68 const HTMLElement* elem = toHTMLElement(node); 69 if (!elem->hasLocalName(spanAttr) || elem->getAttribute(classAttr) != styleSpanClassString()) 70 return false; 71 UseCounter::count(elem->document(), UseCounter::EditingAppleStyleSpanClass); 72 return true; 73 } 74 75 static bool hasNoAttributeOrOnlyStyleAttribute(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) 76 { 77 if (!element->hasAttributes()) 78 return true; 79 80 unsigned matchedAttributes = 0; 81 if (element->getAttribute(classAttr) == styleSpanClassString()) 82 matchedAttributes++; 83 if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute 84 || !element->inlineStyle() || element->inlineStyle()->isEmpty())) 85 matchedAttributes++; 86 87 ASSERT(matchedAttributes <= element->attributeCount()); 88 return matchedAttributes == element->attributeCount(); 89 } 90 91 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element) 92 { 93 if (!isHTMLSpanElement(element)) 94 return false; 95 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute); 96 } 97 98 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node) 99 { 100 if (!isHTMLSpanElement(node)) 101 return false; 102 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty); 103 } 104 105 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty) 106 { 107 if (!isHTMLFontElement(element)) 108 return false; 109 110 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty); 111 } 112 113 static PassRefPtrWillBeRawPtr<Element> createFontElement(Document& document) 114 { 115 return createHTMLElement(document, fontTag); 116 } 117 118 PassRefPtrWillBeRawPtr<HTMLElement> createStyleSpanElement(Document& document) 119 { 120 return createHTMLElement(document, spanTag); 121 } 122 123 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel) 124 : CompositeEditCommand(document) 125 , m_style(style->copy()) 126 , m_editingAction(editingAction) 127 , m_propertyLevel(propertyLevel) 128 , m_start(endingSelection().start().downstream()) 129 , m_end(endingSelection().end().upstream()) 130 , m_useEndingSelection(true) 131 , m_styledInlineElement(nullptr) 132 , m_removeOnly(false) 133 , m_isInlineElementToRemoveFunction(0) 134 { 135 } 136 137 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel) 138 : CompositeEditCommand(document) 139 , m_style(style->copy()) 140 , m_editingAction(editingAction) 141 , m_propertyLevel(propertyLevel) 142 , m_start(start) 143 , m_end(end) 144 , m_useEndingSelection(false) 145 , m_styledInlineElement(nullptr) 146 , m_removeOnly(false) 147 , m_isInlineElementToRemoveFunction(0) 148 { 149 } 150 151 ApplyStyleCommand::ApplyStyleCommand(PassRefPtrWillBeRawPtr<Element> element, bool removeOnly, EditAction editingAction) 152 : CompositeEditCommand(element->document()) 153 , m_style(EditingStyle::create()) 154 , m_editingAction(editingAction) 155 , m_propertyLevel(PropertyDefault) 156 , m_start(endingSelection().start().downstream()) 157 , m_end(endingSelection().end().upstream()) 158 , m_useEndingSelection(true) 159 , m_styledInlineElement(element) 160 , m_removeOnly(removeOnly) 161 , m_isInlineElementToRemoveFunction(0) 162 { 163 } 164 165 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction) 166 : CompositeEditCommand(document) 167 , m_style(style->copy()) 168 , m_editingAction(editingAction) 169 , m_propertyLevel(PropertyDefault) 170 , m_start(endingSelection().start().downstream()) 171 , m_end(endingSelection().end().upstream()) 172 , m_useEndingSelection(true) 173 , m_styledInlineElement(nullptr) 174 , m_removeOnly(true) 175 , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction) 176 { 177 } 178 179 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd) 180 { 181 ASSERT(comparePositions(newEnd, newStart) >= 0); 182 183 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end)) 184 m_useEndingSelection = true; 185 186 setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional())); 187 m_start = newStart; 188 m_end = newEnd; 189 } 190 191 Position ApplyStyleCommand::startPosition() 192 { 193 if (m_useEndingSelection) 194 return endingSelection().start(); 195 196 return m_start; 197 } 198 199 Position ApplyStyleCommand::endPosition() 200 { 201 if (m_useEndingSelection) 202 return endingSelection().end(); 203 204 return m_end; 205 } 206 207 void ApplyStyleCommand::doApply() 208 { 209 switch (m_propertyLevel) { 210 case PropertyDefault: { 211 // Apply the block-centric properties of the style. 212 RefPtrWillBeRawPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties(); 213 if (!blockStyle->isEmpty()) 214 applyBlockStyle(blockStyle.get()); 215 // Apply any remaining styles to the inline elements. 216 if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) { 217 applyRelativeFontStyleChange(m_style.get()); 218 applyInlineStyle(m_style.get()); 219 } 220 break; 221 } 222 case ForceBlockProperties: 223 // Force all properties to be applied as block styles. 224 applyBlockStyle(m_style.get()); 225 break; 226 } 227 } 228 229 EditAction ApplyStyleCommand::editingAction() const 230 { 231 return m_editingAction; 232 } 233 234 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) 235 { 236 // update document layout once before removing styles 237 // so that we avoid the expense of updating before each and every call 238 // to check a computed style 239 document().updateLayoutIgnorePendingStylesheets(); 240 241 // get positions we want to use for applying style 242 Position start = startPosition(); 243 Position end = endPosition(); 244 if (comparePositions(end, start) < 0) { 245 Position swap = start; 246 start = end; 247 end = swap; 248 } 249 250 VisiblePosition visibleStart(start); 251 VisiblePosition visibleEnd(end); 252 253 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan()) 254 return; 255 256 // Save and restore the selection endpoints using their indices in the document, since 257 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints. 258 // Calculate start and end indices from the start of the tree that they're in. 259 Node& scope = visibleStart.deepEquivalent().deprecatedNode()->highestAncestorOrSelf(); 260 RefPtrWillBeRawPtr<Range> startRange = Range::create(document(), firstPositionInNode(&scope), visibleStart.deepEquivalent().parentAnchoredEquivalent()); 261 RefPtrWillBeRawPtr<Range> endRange = Range::create(document(), firstPositionInNode(&scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent()); 262 int startIndex = TextIterator::rangeLength(startRange.get(), true); 263 int endIndex = TextIterator::rangeLength(endRange.get(), true); 264 265 VisiblePosition paragraphStart(startOfParagraph(visibleStart)); 266 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); 267 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); 268 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { 269 StyleChange styleChange(style, paragraphStart.deepEquivalent()); 270 if (styleChange.cssStyle().length() || m_removeOnly) { 271 RefPtrWillBeRawPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode()); 272 const Position& paragraphStartToMove = paragraphStart.deepEquivalent(); 273 if (!m_removeOnly && isEditablePosition(paragraphStartToMove)) { 274 RefPtrWillBeRawPtr<Element> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStartToMove); 275 if (newBlock) 276 block = newBlock; 277 } 278 ASSERT(!block || block->isHTMLElement()); 279 if (block && block->isHTMLElement()) { 280 removeCSSStyle(style, toHTMLElement(block)); 281 if (!m_removeOnly) 282 addBlockStyle(styleChange, toHTMLElement(block)); 283 } 284 285 if (nextParagraphStart.isOrphan()) 286 nextParagraphStart = endOfParagraph(paragraphStart).next(); 287 } 288 289 paragraphStart = nextParagraphStart; 290 nextParagraphStart = endOfParagraph(paragraphStart).next(); 291 } 292 293 startRange = PlainTextRange(startIndex).createRangeForSelection(toContainerNode(scope)); 294 endRange = PlainTextRange(endIndex).createRangeForSelection(toContainerNode(scope)); 295 if (startRange && endRange) 296 updateStartEnd(startRange->startPosition(), endRange->startPosition()); 297 } 298 299 static PassRefPtrWillBeRawPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style) 300 { 301 if (!style) 302 return MutableStylePropertySet::create(); 303 return style->mutableCopy(); 304 } 305 306 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style) 307 { 308 static const float MinimumFontSize = 0.1f; 309 310 if (!style || !style->hasFontSizeDelta()) 311 return; 312 313 Position start = startPosition(); 314 Position end = endPosition(); 315 if (comparePositions(end, start) < 0) { 316 Position swap = start; 317 start = end; 318 end = swap; 319 } 320 321 // Join up any adjacent text nodes. 322 if (start.deprecatedNode()->isTextNode()) { 323 joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end); 324 start = startPosition(); 325 end = endPosition(); 326 } 327 328 if (start.isNull() || end.isNull()) 329 return; 330 331 if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) { 332 joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end); 333 start = startPosition(); 334 end = endPosition(); 335 } 336 337 if (start.isNull() || end.isNull()) 338 return; 339 340 // Split the start text nodes if needed to apply style. 341 if (isValidCaretPositionInTextNode(start)) { 342 splitTextAtStart(start, end); 343 start = startPosition(); 344 end = endPosition(); 345 } 346 347 if (isValidCaretPositionInTextNode(end)) { 348 splitTextAtEnd(start, end); 349 start = startPosition(); 350 end = endPosition(); 351 } 352 353 // Calculate loop end point. 354 // If the end node is before the start node (can only happen if the end node is 355 // an ancestor of the start node), we gather nodes up to the next sibling of the end node 356 Node* beyondEnd; 357 ASSERT(start.deprecatedNode()); 358 ASSERT(end.deprecatedNode()); 359 if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) 360 beyondEnd = NodeTraversal::nextSkippingChildren(*end.deprecatedNode()); 361 else 362 beyondEnd = NodeTraversal::next(*end.deprecatedNode()); 363 364 start = start.upstream(); // Move upstream to ensure we do not add redundant spans. 365 Node* startNode = start.deprecatedNode(); 366 ASSERT(startNode); 367 368 // Make sure we're not already at the end or the next NodeTraversal::next() will traverse 369 // past it. 370 if (startNode == beyondEnd) 371 return; 372 373 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. 374 startNode = NodeTraversal::next(*startNode); 375 376 // Store away font size before making any changes to the document. 377 // This ensures that changes to one node won't effect another. 378 HashMap<Node*, float> startingFontSizes; 379 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) { 380 ASSERT(node); 381 startingFontSizes.set(node, computedFontSize(node)); 382 } 383 384 // These spans were added by us. If empty after font size changes, they can be removed. 385 WillBeHeapVector<RefPtrWillBeMember<HTMLElement> > unstyledSpans; 386 387 Node* lastStyledNode = 0; 388 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) { 389 ASSERT(node); 390 RefPtrWillBeRawPtr<HTMLElement> element = nullptr; 391 if (node->isHTMLElement()) { 392 // Only work on fully selected nodes. 393 if (!nodeFullySelected(node, start, end)) 394 continue; 395 element = toHTMLElement(node); 396 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { 397 // Last styled node was not parent node of this text node, but we wish to style this 398 // text node. To make this possible, add a style span to surround this text node. 399 RefPtrWillBeRawPtr<HTMLElement> span = createStyleSpanElement(document()); 400 surroundNodeRangeWithElement(node, node, span.get()); 401 element = span.release(); 402 } else { 403 // Only handle HTML elements and text nodes. 404 continue; 405 } 406 lastStyledNode = node; 407 408 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 409 float currentFontSize = computedFontSize(node); 410 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta()); 411 RefPtrWillBeRawPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize); 412 if (value) { 413 element->removeInlineStyleProperty(CSSPropertyFontSize); 414 currentFontSize = computedFontSize(node); 415 } 416 if (currentFontSize != desiredFontSize) { 417 inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false); 418 setNodeAttribute(element.get(), styleAttr, AtomicString(inlineStyle->asText())); 419 } 420 if (inlineStyle->isEmpty()) { 421 removeNodeAttribute(element.get(), styleAttr); 422 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get())) 423 unstyledSpans.append(element.release()); 424 } 425 } 426 427 size_t size = unstyledSpans.size(); 428 for (size_t i = 0; i < size; ++i) 429 removeNodePreservingChildren(unstyledSpans[i].get()); 430 } 431 432 static ContainerNode* dummySpanAncestorForNode(const Node* node) 433 { 434 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node)))) 435 node = node->parentNode(); 436 437 return node ? node->parentNode() : 0; 438 } 439 440 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(ContainerNode* dummySpanAncestor) 441 { 442 if (!dummySpanAncestor) 443 return; 444 445 // Dummy spans are created when text node is split, so that style information 446 // can be propagated, which can result in more splitting. If a dummy span gets 447 // cloned/split, the new node is always a sibling of it. Therefore, we scan 448 // all the children of the dummy's parent 449 Node* next; 450 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) { 451 next = node->nextSibling(); 452 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node)) 453 removeNodePreservingChildren(node); 454 } 455 } 456 457 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection) 458 { 459 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection. 460 // In that case, we return the unsplit ancestor. Otherwise, we return 0. 461 Node* block = enclosingBlock(node); 462 if (!block) 463 return 0; 464 465 Node* highestAncestorWithUnicodeBidi = 0; 466 Node* nextHighestAncestorWithUnicodeBidi = 0; 467 int highestAncestorUnicodeBidi = 0; 468 for (Node* n = node->parentNode(); n != block; n = n->parentNode()) { 469 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi); 470 if (unicodeBidi && unicodeBidi != CSSValueNormal) { 471 highestAncestorUnicodeBidi = unicodeBidi; 472 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi; 473 highestAncestorWithUnicodeBidi = n; 474 } 475 } 476 477 if (!highestAncestorWithUnicodeBidi) 478 return 0; 479 480 HTMLElement* unsplitAncestor = 0; 481 482 WritingDirection highestAncestorDirection; 483 if (allowedDirection != NaturalWritingDirection 484 && highestAncestorUnicodeBidi != CSSValueBidiOverride 485 && highestAncestorWithUnicodeBidi->isHTMLElement() 486 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection) 487 && highestAncestorDirection == allowedDirection) { 488 if (!nextHighestAncestorWithUnicodeBidi) 489 return toHTMLElement(highestAncestorWithUnicodeBidi); 490 491 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi); 492 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; 493 } 494 495 // Split every ancestor through highest ancestor with embedding. 496 RefPtrWillBeRawPtr<Node> currentNode = node; 497 while (currentNode) { 498 RefPtrWillBeRawPtr<Element> parent = toElement(currentNode->parentNode()); 499 if (before ? currentNode->previousSibling() : currentNode->nextSibling()) 500 splitElement(parent, before ? currentNode.get() : currentNode->nextSibling()); 501 if (parent == highestAncestorWithUnicodeBidi) 502 break; 503 currentNode = parent; 504 } 505 return unsplitAncestor; 506 } 507 508 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor) 509 { 510 Node* block = enclosingBlock(node); 511 if (!block) 512 return; 513 514 for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = n->parentNode()) { 515 if (!n->isStyledElement()) 516 continue; 517 518 Element* element = toElement(n); 519 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(element).get(), CSSPropertyUnicodeBidi); 520 if (!unicodeBidi || unicodeBidi == CSSValueNormal) 521 continue; 522 523 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration, 524 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. 525 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and 526 // otherwise it sets the property in the inline style declaration. 527 if (element->hasAttribute(dirAttr)) { 528 // FIXME: If this is a BDO element, we should probably just remove it if it has no 529 // other attributes, like we (should) do with B and I elements. 530 removeNodeAttribute(element, dirAttr); 531 } else { 532 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 533 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); 534 inlineStyle->removeProperty(CSSPropertyDirection); 535 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText())); 536 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 537 removeNodePreservingChildren(element); 538 } 539 } 540 } 541 542 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode) 543 { 544 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) { 545 if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed) 546 return n; 547 } 548 549 return 0; 550 } 551 552 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style) 553 { 554 RefPtrWillBeRawPtr<ContainerNode> startDummySpanAncestor = nullptr; 555 RefPtrWillBeRawPtr<ContainerNode> endDummySpanAncestor = nullptr; 556 557 // update document layout once before removing styles 558 // so that we avoid the expense of updating before each and every call 559 // to check a computed style 560 document().updateLayoutIgnorePendingStylesheets(); 561 562 // adjust to the positions we want to use for applying style 563 Position start = startPosition(); 564 Position end = endPosition(); 565 566 if (start.isNull() || end.isNull()) 567 return; 568 569 if (comparePositions(end, start) < 0) { 570 Position swap = start; 571 start = end; 572 end = swap; 573 } 574 575 // split the start node and containing element if the selection starts inside of it 576 bool splitStart = isValidCaretPositionInTextNode(start); 577 if (splitStart) { 578 if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style)) 579 splitTextElementAtStart(start, end); 580 else 581 splitTextAtStart(start, end); 582 start = startPosition(); 583 end = endPosition(); 584 startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode()); 585 } 586 587 // split the end node and containing element if the selection ends inside of it 588 bool splitEnd = isValidCaretPositionInTextNode(end); 589 if (splitEnd) { 590 if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style)) 591 splitTextElementAtEnd(start, end); 592 else 593 splitTextAtEnd(start, end); 594 start = startPosition(); 595 end = endPosition(); 596 endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode()); 597 } 598 599 // Remove style from the selection. 600 // Use the upstream position of the start for removing style. 601 // This will ensure we remove all traces of the relevant styles from the selection 602 // and prevent us from adding redundant ones, as described in: 603 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags 604 Position removeStart = start.upstream(); 605 WritingDirection textDirection = NaturalWritingDirection; 606 bool hasTextDirection = style->textDirection(textDirection); 607 RefPtrWillBeRawPtr<EditingStyle> styleWithoutEmbedding = nullptr; 608 RefPtrWillBeRawPtr<EditingStyle> embeddingStyle = nullptr; 609 if (hasTextDirection) { 610 // Leave alone an ancestor that provides the desired single level embedding, if there is one. 611 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection); 612 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection); 613 removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor); 614 removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor); 615 616 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors. 617 Position embeddingRemoveStart = removeStart; 618 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end)) 619 embeddingRemoveStart = positionInParentAfterNode(*startUnsplitAncestor); 620 621 Position embeddingRemoveEnd = end; 622 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end)) 623 embeddingRemoveEnd = positionInParentBeforeNode(*endUnsplitAncestor).downstream(); 624 625 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) { 626 styleWithoutEmbedding = style->copy(); 627 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); 628 629 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0) 630 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd); 631 } 632 } 633 634 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end); 635 start = startPosition(); 636 end = endPosition(); 637 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan()) 638 return; 639 640 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) { 641 start = startPosition(); 642 end = endPosition(); 643 } 644 645 if (splitEnd) { 646 mergeEndWithNextIfIdentical(start, end); 647 start = startPosition(); 648 end = endPosition(); 649 } 650 651 // update document layout once before running the rest of the function 652 // so that we avoid the expense of updating before each and every call 653 // to check a computed style 654 document().updateLayoutIgnorePendingStylesheets(); 655 656 RefPtrWillBeRawPtr<EditingStyle> styleToApply = style; 657 if (hasTextDirection) { 658 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them. 659 Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode())); 660 Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode())); 661 662 if (embeddingStartNode || embeddingEndNode) { 663 Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(*embeddingStartNode) : start; 664 Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(*embeddingEndNode) : end; 665 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()); 666 667 if (!embeddingStyle) { 668 styleWithoutEmbedding = style->copy(); 669 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection(); 670 } 671 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd); 672 673 styleToApply = styleWithoutEmbedding; 674 } 675 } 676 677 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end); 678 679 // Remove dummy style spans created by splitting text elements. 680 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get()); 681 if (endDummySpanAncestor != startDummySpanAncestor) 682 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get()); 683 } 684 685 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end) 686 { 687 Node* startNode = start.deprecatedNode(); 688 ASSERT(startNode); 689 690 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) { 691 startNode = NodeTraversal::next(*startNode); 692 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0) 693 return; 694 } 695 696 Node* pastEndNode = end.deprecatedNode(); 697 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode())) 698 pastEndNode = NodeTraversal::nextSkippingChildren(*end.deprecatedNode()); 699 700 // FIXME: Callers should perform this operation on a Range that includes the br 701 // if they want style applied to the empty line. 702 if (start == end && isHTMLBRElement(*start.deprecatedNode())) 703 pastEndNode = NodeTraversal::next(*start.deprecatedNode()); 704 705 // Start from the highest fully selected ancestor so that we can modify the fully selected node. 706 // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run 707 // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font> 708 RefPtrWillBeRawPtr<Range> range = Range::create(startNode->document(), start, end); 709 Element* editableRoot = startNode->rootEditableElement(); 710 if (startNode != editableRoot) { 711 while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(*startNode->parentNode(), *range)) 712 startNode = startNode->parentNode(); 713 } 714 715 applyInlineStyleToNodeRange(style, startNode, pastEndNode); 716 } 717 718 static bool containsNonEditableRegion(Node& node) 719 { 720 if (!node.rendererIsEditable()) 721 return true; 722 723 Node* sibling = NodeTraversal::nextSkippingChildren(node); 724 for (Node* descendent = node.firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(*descendent)) { 725 if (!descendent->rendererIsEditable()) 726 return true; 727 } 728 729 return false; 730 } 731 732 class InlineRunToApplyStyle { 733 ALLOW_ONLY_INLINE_ALLOCATION(); 734 public: 735 InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode) 736 : start(start) 737 , end(end) 738 , pastEndNode(pastEndNode) 739 { 740 ASSERT(start->parentNode() == end->parentNode()); 741 } 742 743 bool startAndEndAreStillInDocument() 744 { 745 return start && end && start->inDocument() && end->inDocument(); 746 } 747 748 void trace(Visitor* visitor) 749 { 750 visitor->trace(start); 751 visitor->trace(end); 752 visitor->trace(pastEndNode); 753 visitor->trace(positionForStyleComputation); 754 visitor->trace(dummyElement); 755 } 756 757 RefPtrWillBeMember<Node> start; 758 RefPtrWillBeMember<Node> end; 759 RefPtrWillBeMember<Node> pastEndNode; 760 Position positionForStyleComputation; 761 RefPtrWillBeMember<Node> dummyElement; 762 StyleChange change; 763 }; 764 765 } // namespace WebCore 766 767 WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(WebCore::InlineRunToApplyStyle); 768 769 namespace WebCore { 770 771 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtrWillBeRawPtr<Node> startNode, PassRefPtrWillBeRawPtr<Node> pastEndNode) 772 { 773 if (m_removeOnly) 774 return; 775 776 document().updateLayoutIgnorePendingStylesheets(); 777 778 WillBeHeapVector<InlineRunToApplyStyle> runs; 779 RefPtrWillBeRawPtr<Node> node = startNode; 780 for (RefPtrWillBeRawPtr<Node> next; node && node != pastEndNode; node = next) { 781 next = NodeTraversal::next(*node); 782 783 if (!node->renderer() || !node->rendererIsEditable()) 784 continue; 785 786 if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) { 787 // This is a plaintext-only region. Only proceed if it's fully selected. 788 // pastEndNode is the node after the last fully selected node, so if it's inside node then 789 // node isn't fully selected. 790 if (pastEndNode && pastEndNode->isDescendantOf(node.get())) 791 break; 792 // Add to this element's inline style and skip over its contents. 793 HTMLElement* element = toHTMLElement(node); 794 next = NodeTraversal::nextSkippingChildren(*node); 795 if (!style->style()) 796 continue; 797 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle()); 798 inlineStyle->mergeAndOverrideOnConflict(style->style()); 799 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText())); 800 continue; 801 } 802 803 if (isBlock(node.get())) 804 continue; 805 806 if (node->hasChildren()) { 807 if (node->contains(pastEndNode.get()) || containsNonEditableRegion(*node) || !node->parentNode()->rendererIsEditable()) 808 continue; 809 if (editingIgnoresContent(node.get())) { 810 next = NodeTraversal::nextSkippingChildren(*node); 811 continue; 812 } 813 } 814 815 Node* runStart = node.get(); 816 Node* runEnd = node.get(); 817 Node* sibling = node->nextSibling(); 818 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get()) 819 && (!isBlock(sibling) || isHTMLBRElement(*sibling)) 820 && !containsNonEditableRegion(*sibling)) { 821 runEnd = sibling; 822 sibling = runEnd->nextSibling(); 823 } 824 ASSERT(runEnd); 825 next = NodeTraversal::nextSkippingChildren(*runEnd); 826 827 Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd); 828 if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode)) 829 continue; 830 831 runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode)); 832 } 833 834 for (size_t i = 0; i < runs.size(); i++) { 835 removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode); 836 if (runs[i].startAndEndAreStillInDocument()) 837 runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement); 838 } 839 840 document().updateLayoutIgnorePendingStylesheets(); 841 842 for (size_t i = 0; i < runs.size(); i++) { 843 if (runs[i].positionForStyleComputation.isNotNull()) 844 runs[i].change = StyleChange(style, runs[i].positionForStyleComputation); 845 } 846 847 for (size_t i = 0; i < runs.size(); i++) { 848 InlineRunToApplyStyle run = runs[i]; 849 if (run.dummyElement) 850 removeNode(run.dummyElement); 851 if (run.startAndEndAreStillInDocument()) 852 applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement); 853 } 854 } 855 856 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const 857 { 858 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) 859 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element)); 860 } 861 862 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode) 863 { 864 ASSERT(style && runStart); 865 866 for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(*node)) { 867 if (node->hasChildren()) 868 continue; 869 // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified 870 if (!style->styleIsPresentInComputedStyleOfNode(node)) 871 return true; 872 if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName())) 873 return true; 874 } 875 return false; 876 } 877 878 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtrWillBeMember<Node>& runStart, RefPtrWillBeMember<Node>& runEnd, PassRefPtrWillBeRawPtr<Node> pastEndNode) 879 { 880 ASSERT(runStart && runEnd); 881 RefPtrWillBeRawPtr<Node> next = runStart; 882 for (RefPtrWillBeRawPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) { 883 if (editingIgnoresContent(node.get())) { 884 ASSERT(!node->contains(pastEndNode.get())); 885 next = NodeTraversal::nextSkippingChildren(*node); 886 } else { 887 next = NodeTraversal::next(*node); 888 } 889 if (!node->isHTMLElement()) 890 continue; 891 892 RefPtrWillBeRawPtr<Node> previousSibling = node->previousSibling(); 893 RefPtrWillBeRawPtr<Node> nextSibling = node->nextSibling(); 894 RefPtrWillBeRawPtr<ContainerNode> parent = node->parentNode(); 895 removeInlineStyleFromElement(style, toHTMLElement(node), RemoveAlways); 896 if (!node->inDocument()) { 897 // FIXME: We might need to update the start and the end of current selection here but need a test. 898 if (runStart == node) 899 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild(); 900 if (runEnd == node) 901 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild(); 902 } 903 } 904 } 905 906 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtrWillBeRawPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 907 { 908 ASSERT(element); 909 910 if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) 911 return false; 912 913 if (isStyledInlineElementToRemove(element.get())) { 914 if (mode == RemoveNone) 915 return true; 916 if (extractedStyle) 917 extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues); 918 removeNodePreservingChildren(element); 919 return true; 920 } 921 922 bool removed = false; 923 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) 924 removed = true; 925 926 if (!element->inDocument()) 927 return removed; 928 929 // If the node was converted to a span, the span may still contain relevant 930 // styles which must be removed (e.g. <b style='font-weight: bold'>) 931 if (removeCSSStyle(style, element.get(), mode, extractedStyle)) 932 removed = true; 933 934 return removed; 935 } 936 937 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement* elem) 938 { 939 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty)) 940 removeNodePreservingChildren(elem); 941 else 942 replaceElementWithSpanPreservingChildrenAndAttributes(elem); 943 } 944 945 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 946 { 947 ASSERT(style); 948 if (mode == RemoveNone) { 949 ASSERT(!extractedStyle); 950 return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element); 951 } 952 953 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways); 954 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) { 955 replaceWithSpanOrRemoveIfWithoutAttributes(element); 956 return true; 957 } 958 959 // unicode-bidi and direction are pushed down separately so don't push down with other styles 960 Vector<QualifiedName> attributes; 961 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection, 962 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) 963 return false; 964 965 for (size_t i = 0; i < attributes.size(); i++) 966 removeNodeAttribute(element, attributes[i]); 967 968 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 969 removeNodePreservingChildren(element); 970 971 return true; 972 } 973 974 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle) 975 { 976 ASSERT(style); 977 ASSERT(element); 978 979 if (mode == RemoveNone) 980 return style->conflictsWithInlineStyleOfElement(element); 981 982 Vector<CSSPropertyID> properties; 983 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties)) 984 return false; 985 986 // FIXME: We should use a mass-removal function here but we don't have an undoable one yet. 987 for (size_t i = 0; i < properties.size(); i++) 988 removeCSSProperty(element, properties[i]); 989 990 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element)) 991 removeNodePreservingChildren(element); 992 993 return true; 994 } 995 996 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node) 997 { 998 if (!node) 999 return 0; 1000 1001 HTMLElement* result = 0; 1002 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node)); 1003 1004 for (Node *n = node; n; n = n->parentNode()) { 1005 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n))) 1006 result = toHTMLElement(n); 1007 // Should stop at the editable root (cannot cross editing boundary) and 1008 // also stop at the unsplittable element to be consistent with other UAs 1009 if (n == unsplittableElement) 1010 break; 1011 } 1012 1013 return result; 1014 } 1015 1016 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style) 1017 { 1018 ASSERT(node); 1019 1020 node->document().updateRenderTreeIfNeeded(); 1021 1022 if (!style || style->isEmpty() || !node->renderer() || isHTMLIFrameElement(*node)) 1023 return; 1024 1025 RefPtrWillBeRawPtr<EditingStyle> newInlineStyle = style; 1026 if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) { 1027 newInlineStyle = style->copy(); 1028 newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues); 1029 } 1030 1031 // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead. 1032 // FIXME: applyInlineStyleToRange should be used here instead. 1033 if ((node->renderer()->isRenderBlockFlow() || node->hasChildren()) && node->isHTMLElement()) { 1034 setNodeAttribute(toHTMLElement(node), styleAttr, AtomicString(newInlineStyle->style()->asText())); 1035 return; 1036 } 1037 1038 if (node->renderer()->isText() && toRenderText(node->renderer())->isAllCollapsibleWhitespace()) 1039 return; 1040 1041 // We can't wrap node with the styled element here because new styled element will never be removed if we did. 1042 // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element 1043 // then we fall into an infinite loop where we keep removing and adding styled element wrapping node. 1044 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement); 1045 } 1046 1047 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode) 1048 { 1049 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode); 1050 if (!highestAncestor) 1051 return; 1052 1053 // The outer loop is traversing the tree vertically from highestAncestor to targetNode 1054 RefPtrWillBeRawPtr<Node> current = highestAncestor; 1055 // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown. 1056 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown. 1057 WillBeHeapVector<RefPtrWillBeMember<Element> > elementsToPushDown; 1058 while (current && current != targetNode && current->contains(targetNode)) { 1059 NodeVector currentChildren; 1060 getChildNodes(*current, currentChildren); 1061 RefPtrWillBeRawPtr<Element> styledElement = nullptr; 1062 if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current))) { 1063 styledElement = toElement(current); 1064 elementsToPushDown.append(styledElement); 1065 } 1066 1067 RefPtrWillBeRawPtr<EditingStyle> styleToPushDown = EditingStyle::create(); 1068 if (current->isHTMLElement()) 1069 removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get()); 1070 1071 // The inner loop will go through children on each level 1072 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately. 1073 for (size_t i = 0; i < currentChildren.size(); ++i) { 1074 Node* child = currentChildren[i].get(); 1075 if (!child->parentNode()) 1076 continue; 1077 if (!child->contains(targetNode) && elementsToPushDown.size()) { 1078 for (size_t i = 0; i < elementsToPushDown.size(); i++) { 1079 RefPtrWillBeRawPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren(); 1080 wrapper->removeAttribute(styleAttr); 1081 surroundNodeRangeWithElement(child, child, wrapper); 1082 } 1083 } 1084 1085 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode 1086 // But if we've removed styledElement then go ahead and always apply the style. 1087 if (child != targetNode || styledElement) 1088 applyInlineStyleToPushDown(child, styleToPushDown.get()); 1089 1090 // We found the next node for the outer loop (contains targetNode) 1091 // When reached targetNode, stop the outer loop upon the completion of the current inner loop 1092 if (child == targetNode || child->contains(targetNode)) 1093 current = child; 1094 } 1095 } 1096 } 1097 1098 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end) 1099 { 1100 ASSERT(start.isNotNull()); 1101 ASSERT(end.isNotNull()); 1102 ASSERT(start.inDocument()); 1103 ASSERT(end.inDocument()); 1104 ASSERT(comparePositions(start, end) <= 0); 1105 // FIXME: We should assert that start/end are not in the middle of a text node. 1106 1107 Position pushDownStart = start.downstream(); 1108 // If the pushDownStart is at the end of a text node, then this node is not fully selected. 1109 // Move it to the next deep quivalent position to avoid removing the style from this node. 1110 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead. 1111 Node* pushDownStartContainer = pushDownStart.containerNode(); 1112 if (pushDownStartContainer && pushDownStartContainer->isTextNode() 1113 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset()) 1114 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart); 1115 Position pushDownEnd = end.upstream(); 1116 // If pushDownEnd is at the start of a text node, then this node is not fully selected. 1117 // Move it to the previous deep equivalent position to avoid removing the style from this node. 1118 Node* pushDownEndContainer = pushDownEnd.containerNode(); 1119 if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode()) 1120 pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd); 1121 1122 pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode()); 1123 pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode()); 1124 1125 // The s and e variables store the positions used to set the ending selection after style removal 1126 // takes place. This will help callers to recognize when either the start node or the end node 1127 // are removed from the document during the work of this function. 1128 // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(), 1129 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune. 1130 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start; 1131 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end; 1132 1133 RefPtrWillBeRawPtr<Node> node = start.deprecatedNode(); 1134 while (node) { 1135 RefPtrWillBeRawPtr<Node> next = nullptr; 1136 if (editingIgnoresContent(node.get())) { 1137 ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode())); 1138 next = NodeTraversal::nextSkippingChildren(*node); 1139 } else { 1140 next = NodeTraversal::next(*node); 1141 } 1142 if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) { 1143 RefPtrWillBeRawPtr<HTMLElement> elem = toHTMLElement(node); 1144 RefPtrWillBeRawPtr<Node> prev = NodeTraversal::previousPostOrder(*elem); 1145 RefPtrWillBeRawPtr<Node> next = NodeTraversal::next(*elem); 1146 RefPtrWillBeRawPtr<EditingStyle> styleToPushDown = nullptr; 1147 RefPtrWillBeRawPtr<Node> childNode = nullptr; 1148 if (isStyledInlineElementToRemove(elem.get())) { 1149 styleToPushDown = EditingStyle::create(); 1150 childNode = elem->firstChild(); 1151 } 1152 1153 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get()); 1154 if (!elem->inDocument()) { 1155 if (s.deprecatedNode() == elem) { 1156 // Since elem must have been fully selected, and it is at the start 1157 // of the selection, it is clear we can set the new s offset to 0. 1158 ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0); 1159 s = firstPositionInOrBeforeNode(next.get()); 1160 } 1161 if (e.deprecatedNode() == elem) { 1162 // Since elem must have been fully selected, and it is at the end 1163 // of the selection, it is clear we can set the new e offset to 1164 // the max range offset of prev. 1165 ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode())); 1166 e = lastPositionInOrAfterNode(prev.get()); 1167 } 1168 } 1169 1170 if (styleToPushDown) { 1171 for (; childNode; childNode = childNode->nextSibling()) 1172 applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get()); 1173 } 1174 } 1175 if (node == end.deprecatedNode()) 1176 break; 1177 node = next; 1178 } 1179 1180 updateStartEnd(s, e); 1181 } 1182 1183 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const 1184 { 1185 ASSERT(node); 1186 ASSERT(node->isElementNode()); 1187 1188 // The tree may have changed and Position::upstream() relies on an up-to-date layout. 1189 node->document().updateLayoutIgnorePendingStylesheets(); 1190 1191 return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0 1192 && comparePositions(lastPositionInOrAfterNode(node).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->rendererIsEditable() 1373 && areIdenticalElements(element.get(), toElement(nextSibling))) 1374 mergeIdenticalElements(element.get(), toElement(nextSibling)); 1375 1376 if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) { 1377 Node* mergedElement = previousSibling->nextSibling(); 1378 if (mergedElement->isElementNode() && mergedElement->rendererIsEditable() 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<Node> 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<Node>& 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 HTMLElement* fontContainer = 0; 1442 HTMLElement* styleContainer = 0; 1443 for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) { 1444 if (isHTMLFontElement(*container)) 1445 fontContainer = toHTMLElement(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->firstChild()) 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<Element> 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<Element> 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