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 "CSSValueKeywords.h" 35 #include "Document.h" 36 #include "Editor.h" 37 #include "Frame.h" 38 #include "HTMLElement.h" 39 #include "HTMLInterchange.h" 40 #include "HTMLNames.h" 41 #include "NodeList.h" 42 #include "Range.h" 43 #include "RenderObject.h" 44 #include "Text.h" 45 #include "TextIterator.h" 46 #include "htmlediting.h" 47 #include "visible_units.h" 48 #include <wtf/StdLibExtras.h> 49 50 namespace WebCore { 51 52 using namespace HTMLNames; 53 54 class StyleChange { 55 public: 56 explicit StyleChange(CSSStyleDeclaration*, const Position&); 57 58 String cssStyle() const { return m_cssStyle; } 59 bool applyBold() const { return m_applyBold; } 60 bool applyItalic() const { return m_applyItalic; } 61 bool applyUnderline() const { return m_applyUnderline; } 62 bool applyLineThrough() const { return m_applyLineThrough; } 63 bool applySubscript() const { return m_applySubscript; } 64 bool applySuperscript() const { return m_applySuperscript; } 65 bool applyFontColor() const { return m_applyFontColor.length() > 0; } 66 bool applyFontFace() const { return m_applyFontFace.length() > 0; } 67 bool applyFontSize() const { return m_applyFontSize.length() > 0; } 68 69 String fontColor() { return m_applyFontColor; } 70 String fontFace() { return m_applyFontFace; } 71 String fontSize() { return m_applyFontSize; } 72 73 private: 74 void init(PassRefPtr<CSSStyleDeclaration>, const Position&); 75 void reconcileTextDecorationProperties(CSSMutableStyleDeclaration*); 76 void extractTextStyles(CSSMutableStyleDeclaration*); 77 78 String m_cssStyle; 79 bool m_applyBold; 80 bool m_applyItalic; 81 bool m_applyUnderline; 82 bool m_applyLineThrough; 83 bool m_applySubscript; 84 bool m_applySuperscript; 85 String m_applyFontColor; 86 String m_applyFontFace; 87 String m_applyFontSize; 88 }; 89 90 91 StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position) 92 : m_applyBold(false) 93 , m_applyItalic(false) 94 , m_applyUnderline(false) 95 , m_applyLineThrough(false) 96 , m_applySubscript(false) 97 , m_applySuperscript(false) 98 { 99 init(style, position); 100 } 101 102 void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& position) 103 { 104 Document* document = position.node() ? position.node()->document() : 0; 105 if (!document || !document->frame()) 106 return; 107 108 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle(); 109 RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotInComputedStyle(style.get(), computedStyle.get()); 110 111 reconcileTextDecorationProperties(mutableStyle.get()); 112 if (!document->frame()->editor()->shouldStyleWithCSS()) 113 extractTextStyles(mutableStyle.get()); 114 115 // Changing the whitespace style in a tab span would collapse the tab into a space. 116 if (isTabSpanTextNode(position.node()) || isTabSpanNode((position.node()))) 117 mutableStyle->removeProperty(CSSPropertyWhiteSpace); 118 119 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle. 120 // FIXME: Shouldn't this be done in getPropertiesNotInComputedStyle? 121 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection)) 122 mutableStyle->setProperty(CSSPropertyDirection, style->getPropertyValue(CSSPropertyDirection)); 123 124 // Save the result for later 125 m_cssStyle = mutableStyle->cssText().stripWhiteSpace(); 126 } 127 128 void StyleChange::reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style) 129 { 130 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); 131 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration); 132 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense. 133 ASSERT(!textDecorationsInEffect || !textDecoration); 134 if (textDecorationsInEffect) { 135 style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText()); 136 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); 137 textDecoration = textDecorationsInEffect; 138 } 139 140 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none". 141 if (textDecoration && !textDecoration->isValueList()) 142 style->removeProperty(CSSPropertyTextDecoration); 143 } 144 145 static int getIdentifierValue(CSSMutableStyleDeclaration* style, int propertyID) 146 { 147 if (!style) 148 return 0; 149 150 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); 151 if (!value || !value->isPrimitiveValue()) 152 return 0; 153 154 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent(); 155 } 156 157 static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID) 158 { 159 if (newTextDecoration->length()) 160 style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID)); 161 else { 162 // text-decoration: none is redundant since it does not remove any text decorations. 163 ASSERT(!style->getPropertyPriority(propertyID)); 164 style->removeProperty(propertyID); 165 } 166 } 167 168 void StyleChange::extractTextStyles(CSSMutableStyleDeclaration* style) 169 { 170 ASSERT(style); 171 172 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) { 173 style->removeProperty(CSSPropertyFontWeight); 174 m_applyBold = true; 175 } 176 177 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle); 178 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) { 179 style->removeProperty(CSSPropertyFontStyle); 180 m_applyItalic = true; 181 } 182 183 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect 184 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList. 185 if (RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration)) { 186 ASSERT(textDecoration->isValueList()); 187 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline))); 188 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough))); 189 190 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy(); 191 if (newTextDecoration->removeAll(underline.get())) 192 m_applyUnderline = true; 193 if (newTextDecoration->removeAll(lineThrough.get())) 194 m_applyLineThrough = true; 195 196 // If trimTextDecorations, delete underline and line-through 197 setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration); 198 } 199 200 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign); 201 switch (verticalAlign) { 202 case CSSValueSub: 203 style->removeProperty(CSSPropertyVerticalAlign); 204 m_applySubscript = true; 205 break; 206 case CSSValueSuper: 207 style->removeProperty(CSSPropertyVerticalAlign); 208 m_applySuperscript = true; 209 break; 210 } 211 212 if (RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor)) { 213 ASSERT(colorValue->isPrimitiveValue()); 214 CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get()); 215 RGBA32 rgba; 216 if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) { 217 CSSParser::parseColor(rgba, colorValue->cssText()); 218 // Need to take care of named color such as green and black 219 // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed. 220 } else 221 rgba = primitiveColor->getRGBA32Value(); 222 m_applyFontColor = Color(rgba).name(); 223 style->removeProperty(CSSPropertyColor); 224 } 225 226 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily); 227 style->removeProperty(CSSPropertyFontFamily); 228 229 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) { 230 if (!fontSize->isPrimitiveValue()) 231 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size. 232 else { 233 CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(fontSize.get()); 234 235 // Only accept absolute scale 236 if (value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC) { 237 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX); 238 if (number <= 9) 239 m_applyFontSize = "1"; 240 else if (number <= 10) 241 m_applyFontSize = "2"; 242 else if (number <= 13) 243 m_applyFontSize = "3"; 244 else if (number <= 16) 245 m_applyFontSize = "4"; 246 else if (number <= 18) 247 m_applyFontSize = "5"; 248 else if (number <= 24) 249 m_applyFontSize = "6"; 250 else 251 m_applyFontSize = "7"; 252 } 253 // Huge quirk in Microsoft Entourage is that they understand CSS font-size, but also write 254 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all, 255 // like Eudora). Yes, they write out *both*. We need to write out both as well. 256 } 257 } 258 } 259 260 static String& styleSpanClassString() 261 { 262 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass))); 263 return styleSpanClassString; 264 } 265 266 bool isStyleSpan(const Node *node) 267 { 268 if (!node || !node->isHTMLElement()) 269 return false; 270 271 const HTMLElement* elem = static_cast<const HTMLElement*>(node); 272 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString(); 273 } 274 275 static bool isUnstyledStyleSpan(const Node* node) 276 { 277 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 278 return false; 279 280 const HTMLElement* elem = static_cast<const HTMLElement*>(node); 281 CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl(); 282 return (!inlineStyleDecl || inlineStyleDecl->isEmpty()) && elem->getAttribute(classAttr) == styleSpanClassString(); 283 } 284 285 static bool isSpanWithoutAttributesOrUnstyleStyleSpan(const Node* node) 286 { 287 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag)) 288 return false; 289 290 const HTMLElement* elem = static_cast<const HTMLElement*>(node); 291 NamedNodeMap* attributes = elem->attributes(true); // readonly 292 if (attributes->isEmpty()) 293 return true; 294 295 return isUnstyledStyleSpan(node); 296 } 297 298 static bool isEmptyFontTag(const Node *node) 299 { 300 if (!node || !node->hasTagName(fontTag)) 301 return false; 302 303 const Element *elem = static_cast<const Element *>(node); 304 NamedNodeMap *map = elem->attributes(true); // true for read-only 305 if (!map) 306 return true; 307 return map->isEmpty() || (map->length() == 1 && elem->getAttribute(classAttr) == styleSpanClassString()); 308 } 309 310 static PassRefPtr<Element> createFontElement(Document* document) 311 { 312 RefPtr<Element> fontNode = createHTMLElement(document, fontTag); 313 fontNode->setAttribute(classAttr, styleSpanClassString()); 314 return fontNode.release(); 315 } 316 317 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document) 318 { 319 RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag); 320 styleElement->setAttribute(classAttr, styleSpanClassString()); 321 return styleElement.release(); 322 } 323 324 static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration) 325 { 326 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID); 327 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList()) 328 return; 329 330 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy(); 331 CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration); 332 333 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++) 334 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i)); 335 336 setTextDecorationProperty(style, newTextDecoration.get(), propertID); 337 } 338 339 static bool fontWeightIsBold(CSSStyleDeclaration* style) 340 { 341 ASSERT(style); 342 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight); 343 344 if (!fontWeight) 345 return false; 346 if (!fontWeight->isPrimitiveValue()) 347 return false; 348 349 // Because b tag can only bold text, there are only two states in plain html: bold and not bold. 350 // Collapse all other values to either one of these two states for editing purposes. 351 switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) { 352 case CSSValue100: 353 case CSSValue200: 354 case CSSValue300: 355 case CSSValue400: 356 case CSSValue500: 357 case CSSValueNormal: 358 return false; 359 case CSSValueBold: 360 case CSSValue600: 361 case CSSValue700: 362 case CSSValue800: 363 case CSSValue900: 364 return true; 365 } 366 367 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter 368 return false; // Make compiler happy 369 } 370 371 RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDeclaration* style, CSSComputedStyleDeclaration* computedStyle) 372 { 373 ASSERT(style); 374 ASSERT(computedStyle); 375 RefPtr<CSSMutableStyleDeclaration> result = style->copy(); 376 computedStyle->diff(result.get()); 377 378 RefPtr<CSSValue> computedTextDecorationsInEffect = computedStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); 379 diffTextDecorations(result.get(), CSSPropertyTextDecoration, computedTextDecorationsInEffect.get()); 380 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, computedTextDecorationsInEffect.get()); 381 382 if (fontWeightIsBold(result.get()) == fontWeightIsBold(computedStyle)) 383 result->removeProperty(CSSPropertyFontWeight); 384 385 return result; 386 } 387 388 // Editing style properties must be preserved during editing operation. 389 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. 390 // FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties 391 static const int editingStyleProperties[] = { 392 // CSS inheritable properties 393 CSSPropertyBorderCollapse, 394 CSSPropertyColor, 395 CSSPropertyFontFamily, 396 CSSPropertyFontSize, 397 CSSPropertyFontStyle, 398 CSSPropertyFontVariant, 399 CSSPropertyFontWeight, 400 CSSPropertyLetterSpacing, 401 CSSPropertyLineHeight, 402 CSSPropertyOrphans, 403 CSSPropertyTextAlign, 404 CSSPropertyTextIndent, 405 CSSPropertyTextTransform, 406 CSSPropertyWhiteSpace, 407 CSSPropertyWidows, 408 CSSPropertyWordSpacing, 409 CSSPropertyWebkitBorderHorizontalSpacing, 410 CSSPropertyWebkitBorderVerticalSpacing, 411 CSSPropertyWebkitTextDecorationsInEffect, 412 CSSPropertyWebkitTextFillColor, 413 CSSPropertyWebkitTextSizeAdjust, 414 CSSPropertyWebkitTextStrokeColor, 415 CSSPropertyWebkitTextStrokeWidth, 416 }; 417 size_t numEditingStyleProperties = sizeof(editingStyleProperties)/sizeof(editingStyleProperties[0]); 418 419 PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle) 420 { 421 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = pos.computedStyle(); 422 RefPtr<CSSMutableStyleDeclaration> style; 423 if (!computedStyleAtPosition) 424 style = CSSMutableStyleDeclaration::create(); 425 else 426 style = computedStyleAtPosition->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties); 427 428 if (style && pos.node() && pos.node()->computedStyle()) { 429 RenderStyle* renderStyle = pos.node()->computedStyle(); 430 // If a node's text fill color is invalid, then its children use 431 // their font-color as their text fill color (they don't 432 // inherit it). Likewise for stroke color. 433 ExceptionCode ec = 0; 434 if (!renderStyle->textFillColor().isValid()) 435 style->removeProperty(CSSPropertyWebkitTextFillColor, ec); 436 if (!renderStyle->textStrokeColor().isValid()) 437 style->removeProperty(CSSPropertyWebkitTextStrokeColor, ec); 438 ASSERT(ec == 0); 439 if (renderStyle->fontDescription().keywordSize()) 440 style->setProperty(CSSPropertyFontSize, computedStyleAtPosition->getFontSizeCSSValuePreferringKeyword()->cssText()); 441 } 442 443 if (shouldIncludeTypingStyle == IncludeTypingStyle) { 444 CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->typingStyle(); 445 if (typingStyle) 446 style->merge(typingStyle); 447 } 448 449 return style.release(); 450 } 451 452 void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration* editingStyle, Position pos) 453 { 454 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style. 455 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate 456 // which one of editingStyleAtPosition or computedStyle is called. 457 RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(pos); 458 style->diff(editingStyle); 459 460 // if alpha value is zero, we don't add the background color. 461 RefPtr<CSSValue> backgroundColor = editingStyle->getPropertyCSSValue(CSSPropertyBackgroundColor); 462 if (backgroundColor && backgroundColor->isPrimitiveValue()) { 463 CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(backgroundColor.get()); 464 Color color = Color(primitiveValue->getRGBA32Value()); 465 ExceptionCode ec; 466 if (color.alpha() == 0) 467 editingStyle->removeProperty(CSSPropertyBackgroundColor, ec); 468 } 469 } 470 471 void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* node) 472 { 473 ASSERT(node); 474 ASSERT(node->parentNode()); 475 RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleAtPosition(Position(node->parentNode(), 0)); 476 RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(Position(node, 0)); 477 parentStyle->diff(style.get()); 478 style->diff(editingStyle); 479 } 480 481 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel) 482 : CompositeEditCommand(document) 483 , m_style(style->makeMutable()) 484 , m_editingAction(editingAction) 485 , m_propertyLevel(propertyLevel) 486 , m_start(endingSelection().start().downstream()) 487 , m_end(endingSelection().end().upstream()) 488 , m_useEndingSelection(true) 489 , m_styledInlineElement(0) 490 , m_removeOnly(false) 491 { 492 } 493 494 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel) 495 : CompositeEditCommand(document) 496 , m_style(style->makeMutable()) 497 , m_editingAction(editingAction) 498 , m_propertyLevel(propertyLevel) 499 , m_start(start) 500 , m_end(end) 501 , m_useEndingSelection(false) 502 , m_styledInlineElement(0) 503 , m_removeOnly(false) 504 { 505 } 506 507 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction) 508 : CompositeEditCommand(element->document()) 509 , m_style(CSSMutableStyleDeclaration::create()) 510 , m_editingAction(editingAction) 511 , m_propertyLevel(PropertyDefault) 512 , m_start(endingSelection().start().downstream()) 513 , m_end(endingSelection().end().upstream()) 514 , m_useEndingSelection(true) 515 , m_styledInlineElement(element) 516 , m_removeOnly(removeOnly) 517 { 518 } 519 520 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd) 521 { 522 ASSERT(comparePositions(newEnd, newStart) >= 0); 523 524 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end)) 525 m_useEndingSelection = true; 526 527 setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY)); 528 m_start = newStart; 529 m_end = newEnd; 530 } 531 532 Position ApplyStyleCommand::startPosition() 533 { 534 if (m_useEndingSelection) 535 return endingSelection().start(); 536 537 return m_start; 538 } 539 540 Position ApplyStyleCommand::endPosition() 541 { 542 if (m_useEndingSelection) 543 return endingSelection().end(); 544 545 return m_end; 546 } 547 548 void ApplyStyleCommand::doApply() 549 { 550 switch (m_propertyLevel) { 551 case PropertyDefault: { 552 // apply the block-centric properties of the style 553 RefPtr<CSSMutableStyleDeclaration> blockStyle = m_style->copyBlockProperties(); 554 if (blockStyle->length()) 555 applyBlockStyle(blockStyle.get()); 556 // apply any remaining styles to the inline elements 557 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff 558 if (blockStyle->length() < m_style->length() || m_styledInlineElement) { 559 RefPtr<CSSMutableStyleDeclaration> inlineStyle = m_style->copy(); 560 applyRelativeFontStyleChange(inlineStyle.get()); 561 blockStyle->diff(inlineStyle.get()); 562 applyInlineStyle(inlineStyle.get()); 563 } 564 break; 565 } 566 case ForceBlockProperties: 567 // Force all properties to be applied as block styles. 568 applyBlockStyle(m_style.get()); 569 break; 570 } 571 } 572 573 EditAction ApplyStyleCommand::editingAction() const 574 { 575 return m_editingAction; 576 } 577 578 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) 579 { 580 // update document layout once before removing styles 581 // so that we avoid the expense of updating before each and every call 582 // to check a computed style 583 updateLayout(); 584 585 // get positions we want to use for applying style 586 Position start = startPosition(); 587 Position end = endPosition(); 588 if (comparePositions(end, start) < 0) { 589 Position swap = start; 590 start = end; 591 end = swap; 592 } 593 594 VisiblePosition visibleStart(start); 595 VisiblePosition visibleEnd(end); 596 // Save and restore the selection endpoints using their indices in the document, since 597 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints. 598 // Calculate start and end indices from the start of the tree that they're in. 599 Node* scope = highestAncestor(visibleStart.deepEquivalent().node()); 600 Position rangeStart(scope, 0); 601 RefPtr<Range> startRange = Range::create(document(), rangeStart, rangeCompliantEquivalent(visibleStart.deepEquivalent())); 602 RefPtr<Range> endRange = Range::create(document(), rangeStart, rangeCompliantEquivalent(visibleEnd.deepEquivalent())); 603 int startIndex = TextIterator::rangeLength(startRange.get(), true); 604 int endIndex = TextIterator::rangeLength(endRange.get(), true); 605 606 VisiblePosition paragraphStart(startOfParagraph(visibleStart)); 607 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); 608 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); 609 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { 610 StyleChange styleChange(style, paragraphStart.deepEquivalent()); 611 if (styleChange.cssStyle().length() || m_removeOnly) { 612 RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node()); 613 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent()); 614 if (newBlock) 615 block = newBlock; 616 ASSERT(block->isHTMLElement()); 617 if (block->isHTMLElement()) { 618 removeCSSStyle(style, static_cast<HTMLElement*>(block.get())); 619 if (!m_removeOnly) 620 addBlockStyle(styleChange, static_cast<HTMLElement*>(block.get())); 621 } 622 } 623 paragraphStart = nextParagraphStart; 624 nextParagraphStart = endOfParagraph(paragraphStart).next(); 625 } 626 627 startRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), startIndex, 0, true); 628 endRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), endIndex, 0, true); 629 if (startRange && endRange) 630 updateStartEnd(startRange->startPosition(), endRange->startPosition()); 631 } 632 633 #define NoFontDelta (0.0f) 634 #define MinimumFontSize (0.1f) 635 636 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration *style) 637 { 638 RefPtr<CSSValue> value = style->getPropertyCSSValue(CSSPropertyFontSize); 639 if (value) { 640 // Explicit font size overrides any delta. 641 style->removeProperty(CSSPropertyWebkitFontSizeDelta); 642 return; 643 } 644 645 // Get the adjustment amount out of the style. 646 value = style->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta); 647 if (!value) 648 return; 649 float adjustment = NoFontDelta; 650 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) { 651 CSSPrimitiveValue *primitiveValue = static_cast<CSSPrimitiveValue *>(value.get()); 652 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) { 653 // Only PX handled now. If we handle more types in the future, perhaps 654 // a switch statement here would be more appropriate. 655 adjustment = primitiveValue->getFloatValue(); 656 } 657 } 658 style->removeProperty(CSSPropertyWebkitFontSizeDelta); 659 if (adjustment == NoFontDelta) 660 return; 661 662 Position start = startPosition(); 663 Position end = endPosition(); 664 if (comparePositions(end, start) < 0) { 665 Position swap = start; 666 start = end; 667 end = swap; 668 } 669 670 // Join up any adjacent text nodes. 671 if (start.node()->isTextNode()) { 672 joinChildTextNodes(start.node()->parentNode(), start, end); 673 start = startPosition(); 674 end = endPosition(); 675 } 676 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) { 677 joinChildTextNodes(end.node()->parentNode(), start, end); 678 start = startPosition(); 679 end = endPosition(); 680 } 681 682 // Split the start text nodes if needed to apply style. 683 bool splitStart = splitTextAtStartIfNeeded(start, end); 684 if (splitStart) { 685 start = startPosition(); 686 end = endPosition(); 687 } 688 bool splitEnd = splitTextAtEndIfNeeded(start, end); 689 if (splitEnd) { 690 start = startPosition(); 691 end = endPosition(); 692 } 693 694 // Calculate loop end point. 695 // If the end node is before the start node (can only happen if the end node is 696 // an ancestor of the start node), we gather nodes up to the next sibling of the end node 697 Node *beyondEnd; 698 if (start.node()->isDescendantOf(end.node())) 699 beyondEnd = end.node()->traverseNextSibling(); 700 else 701 beyondEnd = end.node()->traverseNextNode(); 702 703 start = start.upstream(); // Move upstream to ensure we do not add redundant spans. 704 Node *startNode = start.node(); 705 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. 706 startNode = startNode->traverseNextNode(); 707 708 // Store away font size before making any changes to the document. 709 // This ensures that changes to one node won't effect another. 710 HashMap<Node*, float> startingFontSizes; 711 for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode()) 712 startingFontSizes.set(node, computedFontSize(node)); 713 714 // These spans were added by us. If empty after font size changes, they can be removed. 715 Vector<RefPtr<HTMLElement> > unstyledSpans; 716 717 Node* lastStyledNode = 0; 718 for (Node* node = startNode; node != beyondEnd; node = node->traverseNextNode()) { 719 RefPtr<HTMLElement> element; 720 if (node->isHTMLElement()) { 721 // Only work on fully selected nodes. 722 if (!nodeFullySelected(node, start, end)) 723 continue; 724 element = static_cast<HTMLElement*>(node); 725 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) { 726 // Last styled node was not parent node of this text node, but we wish to style this 727 // text node. To make this possible, add a style span to surround this text node. 728 RefPtr<HTMLElement> span = createStyleSpanElement(document()); 729 surroundNodeRangeWithElement(node, node, span.get()); 730 element = span.release(); 731 } else { 732 // Only handle HTML elements and text nodes. 733 continue; 734 } 735 lastStyledNode = node; 736 737 CSSMutableStyleDeclaration* inlineStyleDecl = element->getInlineStyleDecl(); 738 float currentFontSize = computedFontSize(node); 739 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + adjustment); 740 RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSSPropertyFontSize); 741 if (value) { 742 inlineStyleDecl->removeProperty(CSSPropertyFontSize, true); 743 currentFontSize = computedFontSize(node); 744 } 745 if (currentFontSize != desiredFontSize) { 746 inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false); 747 setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText()); 748 } 749 if (inlineStyleDecl->isEmpty()) { 750 removeNodeAttribute(element.get(), styleAttr); 751 // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test. 752 if (isUnstyledStyleSpan(element.get())) 753 unstyledSpans.append(element.release()); 754 } 755 } 756 757 size_t size = unstyledSpans.size(); 758 for (size_t i = 0; i < size; ++i) 759 removeNodePreservingChildren(unstyledSpans[i].get()); 760 } 761 762 #undef NoFontDelta 763 #undef MinimumFontSize 764 765 static Node* dummySpanAncestorForNode(const Node* node) 766 { 767 while (node && !isStyleSpan(node)) 768 node = node->parent(); 769 770 return node ? node->parent() : 0; 771 } 772 773 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor) 774 { 775 if (!dummySpanAncestor) 776 return; 777 778 // Dummy spans are created when text node is split, so that style information 779 // can be propagated, which can result in more splitting. If a dummy span gets 780 // cloned/split, the new node is always a sibling of it. Therefore, we scan 781 // all the children of the dummy's parent 782 Node* next; 783 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) { 784 next = node->nextSibling(); 785 if (isUnstyledStyleSpan(node)) 786 removeNodePreservingChildren(node); 787 node = next; 788 } 789 } 790 791 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, RefPtr<CSSPrimitiveValue> allowedDirection) 792 { 793 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection. 794 // In that case, we return the unsplit ancestor. Otherwise, we return 0. 795 Node* block = enclosingBlock(node); 796 if (!block) 797 return 0; 798 799 Node* highestAncestorWithUnicodeBidi = 0; 800 Node* nextHighestAncestorWithUnicodeBidi = 0; 801 RefPtr<CSSPrimitiveValue> highestAncestorUnicodeBidi; 802 for (Node* n = node->parent(); n != block; n = n->parent()) { 803 RefPtr<CSSValue> unicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); 804 if (unicodeBidi) { 805 ASSERT(unicodeBidi->isPrimitiveValue()); 806 if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) { 807 highestAncestorUnicodeBidi = static_cast<CSSPrimitiveValue*>(unicodeBidi.get()); 808 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi; 809 highestAncestorWithUnicodeBidi = n; 810 } 811 } 812 } 813 814 if (!highestAncestorWithUnicodeBidi) 815 return 0; 816 817 HTMLElement* unsplitAncestor = 0; 818 819 if (allowedDirection && highestAncestorUnicodeBidi->getIdent() != CSSValueBidiOverride) { 820 RefPtr<CSSValue> highestAncestorDirection = computedStyle(highestAncestorWithUnicodeBidi)->getPropertyCSSValue(CSSPropertyDirection); 821 ASSERT(highestAncestorDirection->isPrimitiveValue()); 822 if (static_cast<CSSPrimitiveValue*>(highestAncestorDirection.get())->getIdent() == allowedDirection->getIdent() && highestAncestorWithUnicodeBidi->isHTMLElement()) { 823 if (!nextHighestAncestorWithUnicodeBidi) 824 return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); 825 826 unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi); 827 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi; 828 } 829 } 830 831 // Split every ancestor through highest ancestor with embedding. 832 Node* n = node; 833 while (true) { 834 Element* parent = static_cast<Element*>(n->parent()); 835 if (before ? n->previousSibling() : n->nextSibling()) 836 splitElement(parent, before ? n : n->nextSibling()); 837 if (parent == highestAncestorWithUnicodeBidi) 838 break; 839 n = n->parent(); 840 } 841 return unsplitAncestor; 842 } 843 844 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor) 845 { 846 Node* block = enclosingBlock(node); 847 if (!block) 848 return; 849 850 Node* n = node->parent(); 851 while (n != block && n != unsplitAncestor) { 852 Node* parent = n->parent(); 853 if (!n->isStyledElement()) { 854 n = parent; 855 continue; 856 } 857 858 StyledElement* element = static_cast<StyledElement*>(n); 859 RefPtr<CSSValue> unicodeBidi = computedStyle(element)->getPropertyCSSValue(CSSPropertyUnicodeBidi); 860 if (unicodeBidi) { 861 ASSERT(unicodeBidi->isPrimitiveValue()); 862 if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) { 863 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration, 864 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'. 865 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and 866 // otherwise it sets the property in the inline style declaration. 867 if (element->hasAttribute(dirAttr)) { 868 // FIXME: If this is a BDO element, we should probably just remove it if it has no 869 // other attributes, like we (should) do with B and I elements. 870 removeNodeAttribute(element, dirAttr); 871 } else { 872 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy(); 873 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal); 874 inlineStyle->removeProperty(CSSPropertyDirection); 875 setNodeAttribute(element, styleAttr, inlineStyle->cssText()); 876 // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test. 877 if (isUnstyledStyleSpan(element)) 878 removeNodePreservingChildren(element); 879 } 880 } 881 } 882 n = parent; 883 } 884 } 885 886 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style) 887 { 888 Node* startDummySpanAncestor = 0; 889 Node* endDummySpanAncestor = 0; 890 891 // update document layout once before removing styles 892 // so that we avoid the expense of updating before each and every call 893 // to check a computed style 894 updateLayout(); 895 896 // adjust to the positions we want to use for applying style 897 Position start = startPosition(); 898 Position end = endPosition(); 899 if (comparePositions(end, start) < 0) { 900 Position swap = start; 901 start = end; 902 end = swap; 903 } 904 905 // split the start node and containing element if the selection starts inside of it 906 bool splitStart = splitTextElementAtStartIfNeeded(start, end); 907 if (splitStart) { 908 start = startPosition(); 909 end = endPosition(); 910 startDummySpanAncestor = dummySpanAncestorForNode(start.node()); 911 } 912 913 // split the end node and containing element if the selection ends inside of it 914 bool splitEnd = splitTextElementAtEndIfNeeded(start, end); 915 if (splitEnd) { 916 start = startPosition(); 917 end = endPosition(); 918 endDummySpanAncestor = dummySpanAncestorForNode(end.node()); 919 } 920 921 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); 922 RefPtr<CSSValue> direction; 923 HTMLElement* startUnsplitAncestor = 0; 924 HTMLElement* endUnsplitAncestor = 0; 925 if (unicodeBidi) { 926 RefPtr<CSSPrimitiveValue> allowedDirection; 927 ASSERT(unicodeBidi->isPrimitiveValue()); 928 if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() == CSSValueEmbed) { 929 // Leave alone an ancestor that provides the desired single level embedding, if there is one. 930 direction = style->getPropertyCSSValue(CSSPropertyDirection); 931 ASSERT(direction->isPrimitiveValue()); 932 allowedDirection = static_cast<CSSPrimitiveValue*>(direction.get()); 933 } 934 startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, allowedDirection); 935 endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, allowedDirection); 936 removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor); 937 removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor); 938 } 939 940 // Remove style from the selection. 941 // Use the upstream position of the start for removing style. 942 // This will ensure we remove all traces of the relevant styles from the selection 943 // and prevent us from adding redundant ones, as described in: 944 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags 945 Position removeStart = start.upstream(); 946 Position embeddingRemoveStart = removeStart; 947 Position embeddingRemoveEnd = end; 948 if (unicodeBidi) { 949 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors. 950 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end)) 951 embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor); 952 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end)) 953 embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream(); 954 } 955 956 if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) { 957 RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create(); 958 embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed); 959 embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); 960 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0) 961 removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd); 962 963 RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy(); 964 styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi); 965 styleWithoutEmbedding->removeProperty(CSSPropertyDirection); 966 removeInlineStyle(styleWithoutEmbedding, removeStart, end); 967 } else 968 removeInlineStyle(style, removeStart, end); 969 970 start = startPosition(); 971 end = endPosition(); 972 973 if (splitStart) { 974 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end); 975 if (mergedStart) { 976 start = startPosition(); 977 end = endPosition(); 978 } 979 } 980 981 if (splitEnd) { 982 mergeEndWithNextIfIdentical(start, end); 983 start = startPosition(); 984 end = endPosition(); 985 } 986 987 // update document layout once before running the rest of the function 988 // so that we avoid the expense of updating before each and every call 989 // to check a computed style 990 updateLayout(); 991 992 Position embeddingApplyStart = start; 993 Position embeddingApplyEnd = end; 994 if (unicodeBidi) { 995 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them. 996 Node* startEnclosingBlock = enclosingBlock(start.node()); 997 for (Node* n = start.node(); n != startEnclosingBlock; n = n->parent()) { 998 if (n->isHTMLElement()) { 999 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); 1000 if (ancestorUnicodeBidi) { 1001 ASSERT(ancestorUnicodeBidi->isPrimitiveValue()); 1002 if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) { 1003 embeddingApplyStart = positionInParentAfterNode(n); 1004 break; 1005 } 1006 } 1007 } 1008 } 1009 1010 Node* endEnclosingBlock = enclosingBlock(end.node()); 1011 for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) { 1012 if (n->isHTMLElement()) { 1013 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi); 1014 if (ancestorUnicodeBidi) { 1015 ASSERT(ancestorUnicodeBidi->isPrimitiveValue()); 1016 if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) { 1017 embeddingApplyEnd = positionInParentBeforeNode(n); 1018 break; 1019 } 1020 } 1021 } 1022 } 1023 } 1024 1025 if (embeddingApplyStart != start || embeddingApplyEnd != end) { 1026 if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) { 1027 RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create(); 1028 embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed); 1029 embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); 1030 applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd); 1031 } 1032 1033 RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy(); 1034 styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi); 1035 styleWithoutEmbedding->removeProperty(CSSPropertyDirection); 1036 applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end); 1037 } else 1038 applyInlineStyleToRange(style, start, end); 1039 1040 // Remove dummy style spans created by splitting text elements. 1041 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor); 1042 if (endDummySpanAncestor != startDummySpanAncestor) 1043 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor); 1044 } 1045 1046 void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* style, const Position& start, const Position& rangeEnd) 1047 { 1048 Node* node = start.node(); 1049 Position end = rangeEnd; 1050 1051 bool rangeIsEmpty = false; 1052 1053 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.node())) { 1054 node = node->traverseNextNode(); 1055 Position newStart = Position(node, 0); 1056 if (!node || comparePositions(end, newStart) < 0) 1057 rangeIsEmpty = true; 1058 } 1059 1060 if (!rangeIsEmpty) { 1061 // pastEndNode is the node after the last fully selected node. 1062 Node* pastEndNode = end.node(); 1063 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.node())) 1064 pastEndNode = end.node()->traverseNextSibling(); 1065 // FIXME: Callers should perform this operation on a Range that includes the br 1066 // if they want style applied to the empty line. 1067 if (start == end && start.node()->hasTagName(brTag)) 1068 pastEndNode = start.node()->traverseNextNode(); 1069 // Add the style to selected inline runs. 1070 for (Node* next; node && node != pastEndNode; node = next) { 1071 1072 next = node->traverseNextNode(); 1073 1074 if (!node->renderer() || !node->isContentEditable()) 1075 continue; 1076 1077 if (!node->isContentRichlyEditable() && node->isHTMLElement()) { 1078 // This is a plaintext-only region. Only proceed if it's fully selected. 1079 // pastEndNode is the node after the last fully selected node, so if it's inside node then 1080 // node isn't fully selected. 1081 if (pastEndNode && pastEndNode->isDescendantOf(node)) 1082 break; 1083 // Add to this element's inline style and skip over its contents. 1084 HTMLElement* element = static_cast<HTMLElement*>(node); 1085 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy(); 1086 inlineStyle->merge(style); 1087 setNodeAttribute(element, styleAttr, inlineStyle->cssText()); 1088 next = node->traverseNextSibling(); 1089 continue; 1090 } 1091 1092 if (isBlock(node)) 1093 continue; 1094 1095 if (node->childNodeCount()) { 1096 if (editingIgnoresContent(node)) { 1097 next = node->traverseNextSibling(); 1098 continue; 1099 } 1100 continue; 1101 } 1102 1103 Node* runStart = node; 1104 // Find the end of the run. 1105 Node* sibling = node->nextSibling(); 1106 while (sibling && sibling != pastEndNode && (!sibling->isElementNode() || sibling->hasTagName(brTag)) && !isBlock(sibling)) { 1107 node = sibling; 1108 sibling = node->nextSibling(); 1109 } 1110 // Recompute next, since node has changed. 1111 next = node->traverseNextNode(); 1112 // Apply the style to the run. 1113 addInlineStyleIfNeeded(style, runStart, node); 1114 } 1115 } 1116 } 1117 1118 bool ApplyStyleCommand::shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const 1119 { 1120 // Honor text-decorations-in-effect 1121 RefPtr<CSSValue> textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); 1122 if (!textDecorationsToApply || !textDecorationsToApply->isValueList()) 1123 textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyTextDecoration); 1124 1125 // When there is no text decorations to apply, remove any one of u, s, & strike 1126 if (!textDecorationsToApply || !textDecorationsToApply->isValueList()) 1127 return true; 1128 1129 // Remove node if it implicitly adds style not present in styleToApply 1130 CSSValueList* valueList = static_cast<CSSValueList*>(textDecorationsToApply.get()); 1131 RefPtr<CSSPrimitiveValue> value = CSSPrimitiveValue::createIdentifier(textDecorationAddedByTag); 1132 return !valueList->hasValue(value.get()); 1133 } 1134 1135 // This function maps from styling tags to CSS styles. Used for knowing which 1136 // styling tags should be removed when toggling styles. 1137 bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style) 1138 { 1139 CSSMutableStyleDeclaration::const_iterator end = style->end(); 1140 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { 1141 const CSSProperty& property = *it; 1142 // FIXME: This should probably be re-written to lookup the tagname in a 1143 // hash and match against an expected property/value pair. 1144 switch (property.id()) { 1145 case CSSPropertyFontWeight: 1146 // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational 1147 if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag)) 1148 return !equalIgnoringCase(property.value()->cssText(), "bold") || !elem->hasChildNodes(); 1149 break; 1150 case CSSPropertyVerticalAlign: 1151 if (elem->hasLocalName(subTag)) 1152 return !equalIgnoringCase(property.value()->cssText(), "sub") || !elem->hasChildNodes(); 1153 if (elem->hasLocalName(supTag)) 1154 return !equalIgnoringCase(property.value()->cssText(), "sup") || !elem->hasChildNodes(); 1155 break; 1156 case CSSPropertyFontStyle: 1157 // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational 1158 if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag)) 1159 return !equalIgnoringCase(property.value()->cssText(), "italic") || !elem->hasChildNodes(); 1160 break; 1161 case CSSPropertyTextDecoration: 1162 case CSSPropertyWebkitTextDecorationsInEffect: 1163 if (elem->hasLocalName(uTag)) 1164 return shouldRemoveTextDecorationTag(style, CSSValueUnderline) || !elem->hasChildNodes(); 1165 else if (elem->hasLocalName(sTag) || elem->hasTagName(strikeTag)) 1166 return shouldRemoveTextDecorationTag(style,CSSValueLineThrough) || !elem->hasChildNodes(); 1167 } 1168 } 1169 return false; 1170 } 1171 1172 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem) 1173 { 1174 bool removeNode = false; 1175 1176 // Similar to isSpanWithoutAttributesOrUnstyleStyleSpan, but does not look for Apple-style-span. 1177 NamedNodeMap* attributes = elem->attributes(true); // readonly 1178 if (!attributes || attributes->isEmpty()) 1179 removeNode = true; 1180 else if (attributes->length() == 1 && elem->hasAttribute(styleAttr)) { 1181 // Remove the element even if it has just style='' (this might be redundantly checked later too) 1182 CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl(); 1183 if (!inlineStyleDecl || inlineStyleDecl->isEmpty()) 1184 removeNode = true; 1185 } 1186 1187 if (removeNode) 1188 removeNodePreservingChildren(elem); 1189 else { 1190 HTMLElement* newSpanElement = replaceNodeWithSpanPreservingChildrenAndAttributes(elem); 1191 ASSERT(newSpanElement && newSpanElement->inDocument()); 1192 elem = newSpanElement; 1193 } 1194 } 1195 1196 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem) 1197 { 1198 ASSERT(style); 1199 ASSERT(elem); 1200 1201 if (!elem->hasLocalName(fontTag)) 1202 return; 1203 1204 CSSMutableStyleDeclaration::const_iterator end = style->end(); 1205 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { 1206 switch ((*it).id()) { 1207 case CSSPropertyColor: 1208 removeNodeAttribute(elem, colorAttr); 1209 break; 1210 case CSSPropertyFontFamily: 1211 removeNodeAttribute(elem, faceAttr); 1212 break; 1213 case CSSPropertyFontSize: 1214 removeNodeAttribute(elem, sizeAttr); 1215 break; 1216 } 1217 } 1218 1219 if (isEmptyFontTag(elem)) 1220 removeNodePreservingChildren(elem); 1221 } 1222 1223 void ApplyStyleCommand::removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem) 1224 { 1225 ASSERT(style); 1226 ASSERT(elem); 1227 1228 if (!elem->hasAttribute(dirAttr)) 1229 return; 1230 1231 if (!style->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection)) 1232 return; 1233 1234 removeNodeAttribute(elem, dirAttr); 1235 1236 // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan? Need a test. 1237 if (isUnstyledStyleSpan(elem)) 1238 removeNodePreservingChildren(elem); 1239 } 1240 1241 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem) 1242 { 1243 ASSERT(style); 1244 ASSERT(elem); 1245 1246 CSSMutableStyleDeclaration* decl = elem->inlineStyleDecl(); 1247 if (!decl) 1248 return; 1249 1250 CSSMutableStyleDeclaration::const_iterator end = style->end(); 1251 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { 1252 CSSPropertyID propertyID = static_cast<CSSPropertyID>((*it).id()); 1253 RefPtr<CSSValue> value = decl->getPropertyCSSValue(propertyID); 1254 if (value && (propertyID != CSSPropertyWhiteSpace || !isTabSpanNode(elem))) { 1255 removeCSSProperty(decl, propertyID); 1256 if (propertyID == CSSPropertyUnicodeBidi && !decl->getPropertyValue(CSSPropertyDirection).isEmpty()) 1257 removeCSSProperty(decl, CSSPropertyDirection); 1258 } 1259 } 1260 1261 // No need to serialize <foo style=""> if we just removed the last css property 1262 if (decl->isEmpty()) 1263 removeNodeAttribute(elem, styleAttr); 1264 1265 if (isSpanWithoutAttributesOrUnstyleStyleSpan(elem)) 1266 removeNodePreservingChildren(elem); 1267 } 1268 1269 static bool hasTextDecorationProperty(Node *node) 1270 { 1271 if (!node->isElementNode()) 1272 return false; 1273 1274 RefPtr<CSSValue> value = computedStyle(node)->getPropertyCSSValue(CSSPropertyTextDecoration, DoNotUpdateLayout); 1275 return value && !equalIgnoringCase(value->cssText(), "none"); 1276 } 1277 1278 static Node* highestAncestorWithTextDecoration(Node *node) 1279 { 1280 ASSERT(node); 1281 Node* result = 0; 1282 Node* unsplittableElement = unsplittableElementForPosition(Position(node, 0)); 1283 1284 for (Node *n = node; n; n = n->parentNode()) { 1285 if (hasTextDecorationProperty(n)) 1286 result = n; 1287 // Should stop at the editable root (cannot cross editing boundary) and 1288 // also stop at the unsplittable element to be consistent with other UAs 1289 if (n == unsplittableElement) 1290 break; 1291 } 1292 1293 return result; 1294 } 1295 1296 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractTextDecorationStyle(Node* node) 1297 { 1298 ASSERT(node); 1299 ASSERT(node->isElementNode()); 1300 1301 // non-html elements not handled yet 1302 if (!node->isHTMLElement()) 1303 return 0; 1304 1305 HTMLElement *element = static_cast<HTMLElement *>(node); 1306 RefPtr<CSSMutableStyleDeclaration> style = element->inlineStyleDecl(); 1307 if (!style) 1308 return 0; 1309 1310 int properties[1] = { CSSPropertyTextDecoration }; 1311 RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = style->copyPropertiesInSet(properties, 1); 1312 1313 RefPtr<CSSValue> property = style->getPropertyCSSValue(CSSPropertyTextDecoration); 1314 if (property && !equalIgnoringCase(property->cssText(), "none")) 1315 removeCSSProperty(style.get(), CSSPropertyTextDecoration); 1316 1317 return textDecorationStyle.release(); 1318 } 1319 1320 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node* node) 1321 { 1322 ASSERT(node); 1323 ASSERT(node->isElementNode()); 1324 1325 // non-html elements not handled yet 1326 if (!node->isHTMLElement()) 1327 return 0; 1328 1329 RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node); 1330 ASSERT(nodeStyle); 1331 1332 int properties[1] = { CSSPropertyTextDecoration }; 1333 RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = nodeStyle->copyPropertiesInSet(properties, 1); 1334 1335 RefPtr<CSSValue> property = nodeStyle->getPropertyCSSValue(CSSPropertyTextDecoration); 1336 if (property && !equalIgnoringCase(property->cssText(), "none")) { 1337 RefPtr<CSSMutableStyleDeclaration> newStyle = textDecorationStyle->copy(); 1338 newStyle->setProperty(CSSPropertyTextDecoration, "none"); 1339 applyTextDecorationStyle(node, newStyle.get()); 1340 } 1341 1342 return textDecorationStyle.release(); 1343 } 1344 1345 void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDeclaration *style) 1346 { 1347 ASSERT(node); 1348 1349 if (!style || style->cssText().isEmpty()) 1350 return; 1351 1352 StyleChange styleChange(style, Position(node, 0)); 1353 if (styleChange.cssStyle().length()) { 1354 if (node->isTextNode()) { 1355 RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document()); 1356 surroundNodeRangeWithElement(node, node, styleSpan.get()); 1357 node = styleSpan.get(); 1358 } 1359 1360 if (!node->isElementNode()) 1361 return; 1362 1363 HTMLElement *element = static_cast<HTMLElement *>(node); 1364 String cssText = styleChange.cssStyle(); 1365 CSSMutableStyleDeclaration *decl = element->inlineStyleDecl(); 1366 if (decl) 1367 cssText += decl->cssText(); 1368 setNodeAttribute(element, styleAttr, cssText); 1369 } 1370 1371 if (styleChange.applyUnderline()) 1372 surroundNodeRangeWithElement(node, node, createHTMLElement(document(), uTag)); 1373 1374 if (styleChange.applyLineThrough()) 1375 surroundNodeRangeWithElement(node, node, createHTMLElement(document(), sTag)); 1376 } 1377 1378 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, bool forceNegate) 1379 { 1380 ASSERT(targetNode); 1381 Node* highestAncestor = highestAncestorWithTextDecoration(targetNode); 1382 if (!highestAncestor) 1383 return; 1384 1385 // The outer loop is traversing the tree vertically from highestAncestor to targetNode 1386 Node* current = highestAncestor; 1387 while (current != targetNode) { 1388 ASSERT(current); 1389 ASSERT(current->contains(targetNode)); 1390 RefPtr<CSSMutableStyleDeclaration> decoration = forceNegate ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current); 1391 1392 // The inner loop will go through children on each level 1393 Node* child = current->firstChild(); 1394 while (child) { 1395 Node* nextChild = child->nextSibling(); 1396 1397 // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode 1398 if (child != targetNode) 1399 applyTextDecorationStyle(child, decoration.get()); 1400 1401 // We found the next node for the outer loop (contains targetNode) 1402 // When reached targetNode, stop the outer loop upon the completion of the current inner loop 1403 if (child == targetNode || child->contains(targetNode)) 1404 current = child; 1405 1406 child = nextChild; 1407 } 1408 } 1409 } 1410 1411 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end) 1412 { 1413 // We need to work in two passes. First we push down any inline 1414 // styles that set text decoration. Then we look for any remaining 1415 // styles (caused by stylesheets) and explicitly negate text 1416 // decoration while pushing down. 1417 1418 pushDownTextDecorationStyleAroundNode(start.node(), false); 1419 updateLayout(); 1420 pushDownTextDecorationStyleAroundNode(start.node(), true); 1421 1422 pushDownTextDecorationStyleAroundNode(end.node(), false); 1423 updateLayout(); 1424 pushDownTextDecorationStyleAroundNode(end.node(), true); 1425 } 1426 1427 // FIXME: Why does this exist? Callers should either use lastOffsetForEditing or lastOffsetInNode 1428 static int maxRangeOffset(Node *n) 1429 { 1430 if (n->offsetInCharacters()) 1431 return n->maxCharacterOffset(); 1432 1433 if (n->isElementNode()) 1434 return n->childNodeCount(); 1435 1436 return 1; 1437 } 1438 1439 void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style, const Position &start, const Position &end) 1440 { 1441 ASSERT(start.isNotNull()); 1442 ASSERT(end.isNotNull()); 1443 ASSERT(start.node()->inDocument()); 1444 ASSERT(end.node()->inDocument()); 1445 ASSERT(comparePositions(start, end) <= 0); 1446 1447 RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); 1448 1449 if (textDecorationSpecialProperty) { 1450 pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream()); 1451 style = style->copy(); 1452 style->setProperty(CSSPropertyTextDecoration, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect)); 1453 } 1454 1455 // The s and e variables store the positions used to set the ending selection after style removal 1456 // takes place. This will help callers to recognize when either the start node or the end node 1457 // are removed from the document during the work of this function. 1458 Position s = start; 1459 Position e = end; 1460 1461 Node* node = start.node(); 1462 while (node) { 1463 Node* next = node->traverseNextNode(); 1464 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) { 1465 HTMLElement* elem = static_cast<HTMLElement*>(node); 1466 Node* prev = elem->traversePreviousNodePostOrder(); 1467 Node* next = elem->traverseNextNode(); 1468 if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName())) 1469 removeNodePreservingChildren(elem); 1470 1471 if (implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(elem, style.get())) 1472 replaceWithSpanOrRemoveIfWithoutAttributes(elem); 1473 1474 // If the node was converted to a span, the span may still contain relevant 1475 // styles which must be removed (e.g. <b style='font-weight: bold'>) 1476 if (elem->inDocument()) { 1477 removeHTMLFontStyle(style.get(), elem); 1478 removeHTMLBidiEmbeddingStyle(style.get(), elem); 1479 removeCSSStyle(style.get(), elem); 1480 } 1481 if (!elem->inDocument()) { 1482 if (s.node() == elem) { 1483 // Since elem must have been fully selected, and it is at the start 1484 // of the selection, it is clear we can set the new s offset to 0. 1485 ASSERT(s.deprecatedEditingOffset() <= caretMinOffset(s.node())); 1486 s = Position(next, 0); 1487 } 1488 if (e.node() == elem) { 1489 // Since elem must have been fully selected, and it is at the end 1490 // of the selection, it is clear we can set the new e offset to 1491 // the max range offset of prev. 1492 ASSERT(e.deprecatedEditingOffset() >= maxRangeOffset(e.node())); 1493 e = Position(prev, maxRangeOffset(prev)); 1494 } 1495 } 1496 } 1497 if (node == end.node()) 1498 break; 1499 node = next; 1500 } 1501 1502 ASSERT(s.node()->inDocument()); 1503 ASSERT(e.node()->inDocument()); 1504 updateStartEnd(s, e); 1505 } 1506 1507 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const 1508 { 1509 ASSERT(node); 1510 ASSERT(node->isElementNode()); 1511 1512 Position pos = Position(node, node->childNodeCount()).upstream(); 1513 return comparePositions(Position(node, 0), start) >= 0 && comparePositions(pos, end) <= 0; 1514 } 1515 1516 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const 1517 { 1518 ASSERT(node); 1519 ASSERT(node->isElementNode()); 1520 1521 Position pos = Position(node, node->childNodeCount()).upstream(); 1522 bool isFullyBeforeStart = comparePositions(pos, start) < 0; 1523 bool isFullyAfterEnd = comparePositions(Position(node, 0), end) > 0; 1524 1525 return isFullyBeforeStart || isFullyAfterEnd; 1526 } 1527 1528 1529 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end) 1530 { 1531 if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) { 1532 int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0; 1533 Text *text = static_cast<Text *>(start.node()); 1534 splitTextNode(text, start.deprecatedEditingOffset()); 1535 updateStartEnd(Position(start.node(), 0), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment)); 1536 return true; 1537 } 1538 return false; 1539 } 1540 1541 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end) 1542 { 1543 if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) { 1544 Text *text = static_cast<Text *>(end.node()); 1545 splitTextNode(text, end.deprecatedEditingOffset()); 1546 1547 Node *prevNode = text->previousSibling(); 1548 ASSERT(prevNode); 1549 Node *startNode = start.node() == end.node() ? prevNode : start.node(); 1550 ASSERT(startNode); 1551 updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode, caretMaxOffset(prevNode))); 1552 return true; 1553 } 1554 return false; 1555 } 1556 1557 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end) 1558 { 1559 if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) { 1560 int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0; 1561 Text *text = static_cast<Text *>(start.node()); 1562 splitTextNodeContainingElement(text, start.deprecatedEditingOffset()); 1563 1564 updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment)); 1565 return true; 1566 } 1567 return false; 1568 } 1569 1570 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end) 1571 { 1572 if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) { 1573 Text *text = static_cast<Text *>(end.node()); 1574 splitTextNodeContainingElement(text, end.deprecatedEditingOffset()); 1575 1576 Node *prevNode = text->parent()->previousSibling()->lastChild(); 1577 ASSERT(prevNode); 1578 Node *startNode = start.node() == end.node() ? prevNode : start.node(); 1579 ASSERT(startNode); 1580 updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1)); 1581 return true; 1582 } 1583 return false; 1584 } 1585 1586 static bool areIdenticalElements(Node *first, Node *second) 1587 { 1588 // check that tag name and all attribute names and values are identical 1589 1590 if (!first->isElementNode()) 1591 return false; 1592 1593 if (!second->isElementNode()) 1594 return false; 1595 1596 Element *firstElement = static_cast<Element *>(first); 1597 Element *secondElement = static_cast<Element *>(second); 1598 1599 if (!firstElement->tagQName().matches(secondElement->tagQName())) 1600 return false; 1601 1602 NamedNodeMap *firstMap = firstElement->attributes(); 1603 NamedNodeMap *secondMap = secondElement->attributes(); 1604 1605 unsigned firstLength = firstMap->length(); 1606 1607 if (firstLength != secondMap->length()) 1608 return false; 1609 1610 for (unsigned i = 0; i < firstLength; i++) { 1611 Attribute *attribute = firstMap->attributeItem(i); 1612 Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name()); 1613 1614 if (!secondAttribute || attribute->value() != secondAttribute->value()) 1615 return false; 1616 } 1617 1618 return true; 1619 } 1620 1621 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end) 1622 { 1623 Node *startNode = start.node(); 1624 int startOffset = start.deprecatedEditingOffset(); 1625 1626 if (isAtomicNode(start.node())) { 1627 if (start.deprecatedEditingOffset() != 0) 1628 return false; 1629 1630 // note: prior siblings could be unrendered elements. it's silly to miss the 1631 // merge opportunity just for that. 1632 if (start.node()->previousSibling()) 1633 return false; 1634 1635 startNode = start.node()->parent(); 1636 startOffset = 0; 1637 } 1638 1639 if (!startNode->isElementNode()) 1640 return false; 1641 1642 if (startOffset != 0) 1643 return false; 1644 1645 Node *previousSibling = startNode->previousSibling(); 1646 1647 if (previousSibling && areIdenticalElements(startNode, previousSibling)) { 1648 Element *previousElement = static_cast<Element *>(previousSibling); 1649 Element *element = static_cast<Element *>(startNode); 1650 Node *startChild = element->firstChild(); 1651 ASSERT(startChild); 1652 mergeIdenticalElements(previousElement, element); 1653 1654 int startOffsetAdjustment = startChild->nodeIndex(); 1655 int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0; 1656 updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.deprecatedEditingOffset() + endOffsetAdjustment)); 1657 return true; 1658 } 1659 1660 return false; 1661 } 1662 1663 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end) 1664 { 1665 Node *endNode = end.node(); 1666 int endOffset = end.deprecatedEditingOffset(); 1667 1668 if (isAtomicNode(endNode)) { 1669 if (endOffset < caretMaxOffset(endNode)) 1670 return false; 1671 1672 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1; 1673 if (end.node()->nextSibling()) 1674 return false; 1675 1676 endNode = end.node()->parent(); 1677 endOffset = parentLastOffset; 1678 } 1679 1680 if (!endNode->isElementNode() || endNode->hasTagName(brTag)) 1681 return false; 1682 1683 Node *nextSibling = endNode->nextSibling(); 1684 1685 if (nextSibling && areIdenticalElements(endNode, nextSibling)) { 1686 Element *nextElement = static_cast<Element *>(nextSibling); 1687 Element *element = static_cast<Element *>(endNode); 1688 Node *nextChild = nextElement->firstChild(); 1689 1690 mergeIdenticalElements(element, nextElement); 1691 1692 Node *startNode = start.node() == endNode ? nextElement : start.node(); 1693 ASSERT(startNode); 1694 1695 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length(); 1696 updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(nextElement, endOffset)); 1697 return true; 1698 } 1699 1700 return false; 1701 } 1702 1703 void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endNode, PassRefPtr<Element> elementToInsert) 1704 { 1705 ASSERT(startNode); 1706 ASSERT(endNode); 1707 ASSERT(elementToInsert); 1708 RefPtr<Element> element = elementToInsert; 1709 1710 insertNodeBefore(element, startNode); 1711 1712 Node* node = startNode; 1713 while (1) { 1714 Node* next = node->traverseNextNode(); 1715 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { 1716 removeNode(node); 1717 appendNode(node, element); 1718 } 1719 if (node == endNode) 1720 break; 1721 node = next; 1722 } 1723 1724 Node* nextSibling = element->nextSibling(); 1725 Node* previousSibling = element->previousSibling(); 1726 if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable() 1727 && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling))) 1728 mergeIdenticalElements(element, static_cast<Element*>(nextSibling)); 1729 1730 if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) { 1731 Node* mergedElement = previousSibling->nextSibling(); 1732 if (mergedElement->isElementNode() && mergedElement->isContentEditable() 1733 && areIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement))) 1734 mergeIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement)); 1735 } 1736 1737 // FIXME: We should probably call updateStartEnd if the start or end was in the node 1738 // range so that the endingSelection() is canonicalized. See the comments at the end of 1739 // VisibleSelection::validate(). 1740 } 1741 1742 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block) 1743 { 1744 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for 1745 // inline content. 1746 if (!block) 1747 return; 1748 1749 String cssText = styleChange.cssStyle(); 1750 CSSMutableStyleDeclaration* decl = block->inlineStyleDecl(); 1751 if (decl) 1752 cssText += decl->cssText(); 1753 setNodeAttribute(block, styleAttr, cssText); 1754 } 1755 1756 static bool fontColorChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange) 1757 { 1758 if (styleChange.applyFontColor()) { 1759 if (Color(styleChange.fontColor()) != computedStyle->color()) 1760 return true; 1761 } 1762 return false; 1763 } 1764 1765 static bool fontSizeChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange) 1766 { 1767 if (styleChange.applyFontSize()) { 1768 if (styleChange.fontSize().toInt() != computedStyle->fontSize()) 1769 return true; 1770 } 1771 return false; 1772 } 1773 1774 static bool fontFaceChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange) 1775 { 1776 if (styleChange.applyFontFace()) { 1777 if (computedStyle->fontDescription().family().family().string() != styleChange.fontFace()) 1778 return true; 1779 } 1780 return false; 1781 } 1782 1783 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode) 1784 { 1785 if (m_removeOnly) 1786 return; 1787 1788 StyleChange styleChange(style, Position(startNode, 0)); 1789 1790 // 1791 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. 1792 // 1793 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) { 1794 RefPtr<Element> fontElement = createFontElement(document()); 1795 RenderStyle* computedStyle = startNode->computedStyle(); 1796 1797 // We only want to insert a font element if it will end up changing the style of the 1798 // text somehow. Otherwise it will be a garbage node that will create problems for us 1799 // most notably when we apply a blockquote style for a message reply. 1800 if (fontColorChangesComputedStyle(computedStyle, styleChange) 1801 || fontFaceChangesComputedStyle(computedStyle, styleChange) 1802 || fontSizeChangesComputedStyle(computedStyle, styleChange)) { 1803 if (styleChange.applyFontColor()) 1804 fontElement->setAttribute(colorAttr, styleChange.fontColor()); 1805 if (styleChange.applyFontFace()) 1806 fontElement->setAttribute(faceAttr, styleChange.fontFace()); 1807 if (styleChange.applyFontSize()) 1808 fontElement->setAttribute(sizeAttr, styleChange.fontSize()); 1809 surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); 1810 } 1811 } 1812 1813 if (styleChange.cssStyle().length()) { 1814 RefPtr<Element> styleElement = createStyleSpanElement(document()); 1815 styleElement->setAttribute(styleAttr, styleChange.cssStyle()); 1816 surroundNodeRangeWithElement(startNode, endNode, styleElement.release()); 1817 } 1818 1819 if (styleChange.applyBold()) 1820 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag)); 1821 1822 if (styleChange.applyItalic()) 1823 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag)); 1824 1825 if (styleChange.applyUnderline()) 1826 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag)); 1827 1828 if (styleChange.applyLineThrough()) 1829 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), sTag)); 1830 1831 if (styleChange.applySubscript()) 1832 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag)); 1833 else if (styleChange.applySuperscript()) 1834 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag)); 1835 1836 if (m_styledInlineElement) 1837 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren()); 1838 } 1839 1840 float ApplyStyleCommand::computedFontSize(const Node *node) 1841 { 1842 if (!node) 1843 return 0; 1844 1845 Position pos(const_cast<Node *>(node), 0); 1846 RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle(); 1847 if (!computedStyle) 1848 return 0; 1849 1850 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(computedStyle->getPropertyCSSValue(CSSPropertyFontSize)); 1851 if (!value) 1852 return 0; 1853 1854 return value->getFloatValue(CSSPrimitiveValue::CSS_PX); 1855 } 1856 1857 void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, const Position &end) 1858 { 1859 if (!node) 1860 return; 1861 1862 Position newStart = start; 1863 Position newEnd = end; 1864 1865 Node *child = node->firstChild(); 1866 while (child) { 1867 Node *next = child->nextSibling(); 1868 if (child->isTextNode() && next && next->isTextNode()) { 1869 Text *childText = static_cast<Text *>(child); 1870 Text *nextText = static_cast<Text *>(next); 1871 if (next == start.node()) 1872 newStart = Position(childText, childText->length() + start.deprecatedEditingOffset()); 1873 if (next == end.node()) 1874 newEnd = Position(childText, childText->length() + end.deprecatedEditingOffset()); 1875 String textToMove = nextText->data(); 1876 insertTextIntoNode(childText, childText->length(), textToMove); 1877 removeNode(next); 1878 // don't move child node pointer. it may want to merge with more text nodes. 1879 } 1880 else { 1881 child = child->nextSibling(); 1882 } 1883 } 1884 1885 updateStartEnd(newStart, newEnd); 1886 } 1887 1888 } 1889