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