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