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