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; }
applyUnderline() const61 bool applyUnderline() const { return m_applyUnderline; }
applyLineThrough() const62 bool applyLineThrough() const { return m_applyLineThrough; }
applySubscript() const63 bool applySubscript() const { return m_applySubscript; }
applySuperscript() const64 bool applySuperscript() const { return m_applySuperscript; }
applyFontColor() const65 bool applyFontColor() const { return m_applyFontColor.length() > 0; }
applyFontFace() const66 bool applyFontFace() const { return m_applyFontFace.length() > 0; }
applyFontSize() const67 bool applyFontSize() const { return m_applyFontSize.length() > 0; }
68
fontColor()69 String fontColor() { return m_applyFontColor; }
fontFace()70 String fontFace() { return m_applyFontFace; }
fontSize()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
StyleChange(CSSStyleDeclaration * style,const Position & position)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
init(PassRefPtr<CSSStyleDeclaration> style,const Position & position)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
reconcileTextDecorationProperties(CSSMutableStyleDeclaration * style)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
getIdentifierValue(CSSMutableStyleDeclaration * style,int propertyID)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
setTextDecorationProperty(CSSMutableStyleDeclaration * style,const CSSValueList * newTextDecoration,int propertyID)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
extractTextStyles(CSSMutableStyleDeclaration * style)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 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
186 if (textDecoration && 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
styleSpanClassString()260 static String& styleSpanClassString()
261 {
262 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
263 return styleSpanClassString;
264 }
265
isStyleSpan(const Node * node)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
isUnstyledStyleSpan(const Node * node)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
isSpanWithoutAttributesOrUnstyleStyleSpan(const Node * node)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
isEmptyFontTag(const Node * node)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
createFontElement(Document * document)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
createStyleSpanElement(Document * document)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
diffTextDecorations(CSSMutableStyleDeclaration * style,int propertID,CSSValue * refTextDecoration)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
fontWeightIsBold(CSSStyleDeclaration * style)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
getPropertiesNotInComputedStyle(CSSStyleDeclaration * style,CSSComputedStyleDeclaration * computedStyle)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
editingStyleAtPosition(Position pos,ShouldIncludeTypingStyle shouldIncludeTypingStyle)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
prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration * editingStyle,Position pos)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
removeStylesAddedByNode(CSSMutableStyleDeclaration * editingStyle,Node * node)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
ApplyStyleCommand(Document * document,CSSStyleDeclaration * style,EditAction editingAction,EPropertyLevel propertyLevel)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
ApplyStyleCommand(Document * document,CSSStyleDeclaration * style,const Position & start,const Position & end,EditAction editingAction,EPropertyLevel propertyLevel)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
ApplyStyleCommand(PassRefPtr<Element> element,bool removeOnly,EditAction editingAction)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
updateStartEnd(const Position & newStart,const Position & newEnd)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
startPosition()532 Position ApplyStyleCommand::startPosition()
533 {
534 if (m_useEndingSelection)
535 return endingSelection().start();
536
537 return m_start;
538 }
539
endPosition()540 Position ApplyStyleCommand::endPosition()
541 {
542 if (m_useEndingSelection)
543 return endingSelection().end();
544
545 return m_end;
546 }
547
doApply()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
editingAction() const573 EditAction ApplyStyleCommand::editingAction() const
574 {
575 return m_editingAction;
576 }
577
applyBlockStyle(CSSMutableStyleDeclaration * style)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
applyRelativeFontStyleChange(CSSMutableStyleDeclaration * style)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
dummySpanAncestorForNode(const Node * node)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
cleanupUnstyledAppleStyleSpans(Node * dummySpanAncestor)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
splitAncestorsWithUnicodeBidi(Node * node,bool before,RefPtr<CSSPrimitiveValue> allowedDirection)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
removeEmbeddingUpToEnclosingBlock(Node * node,Node * unsplitAncestor)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
applyInlineStyle(CSSMutableStyleDeclaration * style)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
applyInlineStyleToRange(CSSMutableStyleDeclaration * style,const Position & start,const Position & rangeEnd)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
shouldRemoveTextDecorationTag(CSSStyleDeclaration * styleToApply,int textDecorationAddedByTag) const1118 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.
implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement * elem,CSSMutableStyleDeclaration * style)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
replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement * & elem)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
removeHTMLFontStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)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
removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)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
removeCSSStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)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
hasTextDecorationProperty(Node * node)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
highestAncestorWithTextDecoration(Node * node)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
extractTextDecorationStyle(Node * node)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
extractAndNegateTextDecorationStyle(Node * node)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
applyTextDecorationStyle(Node * node,CSSMutableStyleDeclaration * style)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
pushDownTextDecorationStyleAroundNode(Node * targetNode,bool forceNegate)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
pushDownTextDecorationStyleAtBoundaries(const Position & start,const Position & end)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
maxRangeOffset(Node * n)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
removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style,const Position & start,const Position & end)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
nodeFullySelected(Node * node,const Position & start,const Position & end) const1507 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
nodeFullyUnselected(Node * node,const Position & start,const Position & end) const1516 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
splitTextAtStartIfNeeded(const Position & start,const Position & end)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
splitTextAtEndIfNeeded(const Position & start,const Position & end)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
splitTextElementAtStartIfNeeded(const Position & start,const Position & end)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
splitTextElementAtEndIfNeeded(const Position & start,const Position & end)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
areIdenticalElements(Node * first,Node * second)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
mergeStartWithPreviousIfIdentical(const Position & start,const Position & end)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
mergeEndWithNextIfIdentical(const Position & start,const Position & end)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
surroundNodeRangeWithElement(Node * startNode,Node * endNode,PassRefPtr<Element> elementToInsert)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
addBlockStyle(const StyleChange & styleChange,HTMLElement * block)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
fontColorChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)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
fontSizeChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)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
fontFaceChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)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
addInlineStyleIfNeeded(CSSMutableStyleDeclaration * style,Node * startNode,Node * endNode)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
computedFontSize(const Node * node)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
joinChildTextNodes(Node * node,const Position & start,const Position & end)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