1 /*
2 * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
3 * Copyright (C) 2010, 2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "core/editing/EditingStyle.h"
29
30 #include "HTMLNames.h"
31 #include "bindings/v8/ExceptionStatePlaceholder.h"
32 #include "core/css/CSSComputedStyleDeclaration.h"
33 #include "core/css/CSSParser.h"
34 #include "core/css/CSSRuleList.h"
35 #include "core/css/CSSStyleRule.h"
36 #include "core/css/CSSValueList.h"
37 #include "core/css/FontSize.h"
38 #include "core/css/RuntimeCSSEnabled.h"
39 #include "core/css/StylePropertySet.h"
40 #include "core/css/StyleRule.h"
41 #include "core/css/resolver/StyleResolver.h"
42 #include "core/dom/Element.h"
43 #include "core/dom/Node.h"
44 #include "core/dom/NodeTraversal.h"
45 #include "core/dom/Position.h"
46 #include "core/dom/QualifiedName.h"
47 #include "core/editing/ApplyStyleCommand.h"
48 #include "core/editing/Editor.h"
49 #include "core/editing/FrameSelection.h"
50 #include "core/editing/HTMLInterchange.h"
51 #include "core/editing/htmlediting.h"
52 #include "core/frame/Frame.h"
53 #include "core/html/HTMLFontElement.h"
54 #include "core/rendering/style/RenderStyle.h"
55
56 namespace WebCore {
57
textDecorationPropertyForEditing()58 static const CSSPropertyID& textDecorationPropertyForEditing()
59 {
60 static const CSSPropertyID property = RuntimeEnabledFeatures::css3TextDecorationsEnabled() ? CSSPropertyTextDecorationLine : CSSPropertyTextDecoration;
61 return property;
62 }
63
64 // Editing style properties must be preserved during editing operation.
65 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
66 // NOTE: Use either allEditingProperties() or inheritableEditingProperties() to
67 // respect runtime enabling of properties.
68 static const CSSPropertyID staticEditingProperties[] = {
69 CSSPropertyBackgroundColor,
70 CSSPropertyColor,
71 CSSPropertyFontFamily,
72 CSSPropertyFontSize,
73 CSSPropertyFontStyle,
74 CSSPropertyFontVariant,
75 CSSPropertyFontWeight,
76 CSSPropertyLetterSpacing,
77 CSSPropertyLineHeight,
78 CSSPropertyOrphans,
79 CSSPropertyTextAlign,
80 // FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
81 // Decoration feature is no longer experimental.
82 CSSPropertyTextDecoration,
83 CSSPropertyTextDecorationLine,
84 CSSPropertyTextIndent,
85 CSSPropertyTextTransform,
86 CSSPropertyWhiteSpace,
87 CSSPropertyWidows,
88 CSSPropertyWordSpacing,
89 CSSPropertyWebkitTextDecorationsInEffect,
90 CSSPropertyWebkitTextFillColor,
91 CSSPropertyWebkitTextStrokeColor,
92 CSSPropertyWebkitTextStrokeWidth,
93 };
94
95 enum EditingPropertiesType { OnlyInheritableEditingProperties, AllEditingProperties };
96
allEditingProperties()97 static const Vector<CSSPropertyID>& allEditingProperties()
98 {
99 DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
100 if (properties.isEmpty()) {
101 RuntimeCSSEnabled::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
102 if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
103 properties.remove(properties.find(CSSPropertyTextDecoration));
104 }
105 return properties;
106 }
107
inheritableEditingProperties()108 static const Vector<CSSPropertyID>& inheritableEditingProperties()
109 {
110 DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
111 if (properties.isEmpty()) {
112 RuntimeCSSEnabled::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
113 for (size_t index = 0; index < properties.size();) {
114 if (!CSSProperty::isInheritedProperty(properties[index])) {
115 properties.remove(index);
116 continue;
117 }
118 ++index;
119 }
120 }
121 return properties;
122 }
123
124 template <class StyleDeclarationType>
copyEditingProperties(StyleDeclarationType * style,EditingPropertiesType type=OnlyInheritableEditingProperties)125 static PassRefPtr<MutableStylePropertySet> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesType type = OnlyInheritableEditingProperties)
126 {
127 if (type == AllEditingProperties)
128 return style->copyPropertiesInSet(allEditingProperties());
129 return style->copyPropertiesInSet(inheritableEditingProperties());
130 }
131
isEditingProperty(int id)132 static inline bool isEditingProperty(int id)
133 {
134 return allEditingProperties().contains(static_cast<CSSPropertyID>(id));
135 }
136
editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style,EditingPropertiesType type=OnlyInheritableEditingProperties)137 static PassRefPtr<MutableStylePropertySet> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style, EditingPropertiesType type = OnlyInheritableEditingProperties)
138 {
139 if (!style)
140 return MutableStylePropertySet::create();
141 return copyEditingProperties(style.get(), type);
142 }
143
144 static PassRefPtr<MutableStylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
145 enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
146 static int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
147 static bool isTransparentColorValue(CSSValue*);
148 static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
149 static bool hasTransparentBackgroundColor(StylePropertySet*);
150 static PassRefPtr<CSSValue> backgroundColorInEffect(Node*);
151
152 class HTMLElementEquivalent {
153 WTF_MAKE_FAST_ALLOCATED;
154 public:
create(CSSPropertyID propertyID,CSSValueID primitiveValue,const QualifiedName & tagName)155 static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, CSSValueID primitiveValue, const QualifiedName& tagName)
156 {
157 return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
158 }
159
~HTMLElementEquivalent()160 virtual ~HTMLElementEquivalent() { }
matches(const Element * element) const161 virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
hasAttribute() const162 virtual bool hasAttribute() const { return false; }
propertyExistsInStyle(const StylePropertySet * style) const163 virtual bool propertyExistsInStyle(const StylePropertySet* style) const { return style->getPropertyCSSValue(m_propertyID); }
164 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
165 virtual void addToStyle(Element*, EditingStyle*) const;
166
167 protected:
168 HTMLElementEquivalent(CSSPropertyID);
169 HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
170 HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName);
171 const CSSPropertyID m_propertyID;
172 const RefPtr<CSSPrimitiveValue> m_primitiveValue;
173 const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
174 };
175
HTMLElementEquivalent(CSSPropertyID id)176 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
177 : m_propertyID(id)
178 , m_tagName(0)
179 {
180 }
181
HTMLElementEquivalent(CSSPropertyID id,const QualifiedName & tagName)182 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
183 : m_propertyID(id)
184 , m_tagName(&tagName)
185 {
186 }
187
HTMLElementEquivalent(CSSPropertyID id,CSSValueID primitiveValue,const QualifiedName & tagName)188 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primitiveValue, const QualifiedName& tagName)
189 : m_propertyID(id)
190 , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
191 , m_tagName(&tagName)
192 {
193 ASSERT(primitiveValue != CSSValueInvalid);
194 }
195
valueIsPresentInStyle(Element * element,StylePropertySet * style) const196 bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
197 {
198 RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
199 return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID();
200 }
201
addToStyle(Element *,EditingStyle * style) const202 void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
203 {
204 style->setProperty(m_propertyID, m_primitiveValue->cssText());
205 }
206
207 class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
208 public:
create(CSSValueID primitiveValue,const QualifiedName & tagName)209 static PassOwnPtr<HTMLElementEquivalent> create(CSSValueID primitiveValue, const QualifiedName& tagName)
210 {
211 return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
212 }
213 virtual bool propertyExistsInStyle(const StylePropertySet*) const;
214 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
215
216 private:
217 HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName);
218 };
219
HTMLTextDecorationEquivalent(CSSValueID primitiveValue,const QualifiedName & tagName)220 HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName)
221 : HTMLElementEquivalent(textDecorationPropertyForEditing(), primitiveValue, tagName)
222 // m_propertyID is used in HTMLElementEquivalent::addToStyle
223 {
224 }
225
propertyExistsInStyle(const StylePropertySet * style) const226 bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StylePropertySet* style) const
227 {
228 return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)
229 || style->getPropertyCSSValue(textDecorationPropertyForEditing());
230 }
231
valueIsPresentInStyle(Element * element,StylePropertySet * style) const232 bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
233 {
234 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
235 if (!styleValue)
236 styleValue = style->getPropertyCSSValue(textDecorationPropertyForEditing());
237 return matches(element) && styleValue && styleValue->isValueList() && toCSSValueList(styleValue.get())->hasValue(m_primitiveValue.get());
238 }
239
240 class HTMLAttributeEquivalent : public HTMLElementEquivalent {
241 public:
create(CSSPropertyID propertyID,const QualifiedName & tagName,const QualifiedName & attrName)242 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName)
243 {
244 return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
245 }
create(CSSPropertyID propertyID,const QualifiedName & attrName)246 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
247 {
248 return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
249 }
250
matches(const Element * elem) const251 bool matches(const Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
hasAttribute() const252 virtual bool hasAttribute() const { return true; }
253 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
254 virtual void addToStyle(Element*, EditingStyle*) const;
255 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
attributeName() const256 inline const QualifiedName& attributeName() const { return m_attrName; }
257
258 protected:
259 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
260 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
261 const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
262 };
263
HTMLAttributeEquivalent(CSSPropertyID id,const QualifiedName & tagName,const QualifiedName & attrName)264 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
265 : HTMLElementEquivalent(id, tagName)
266 , m_attrName(attrName)
267 {
268 }
269
HTMLAttributeEquivalent(CSSPropertyID id,const QualifiedName & attrName)270 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
271 : HTMLElementEquivalent(id)
272 , m_attrName(attrName)
273 {
274 }
275
valueIsPresentInStyle(Element * element,StylePropertySet * style) const276 bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
277 {
278 RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
279 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
280
281 return compareCSSValuePtr(value, styleValue);
282 }
283
addToStyle(Element * element,EditingStyle * style) const284 void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
285 {
286 if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
287 style->setProperty(m_propertyID, value->cssText());
288 }
289
attributeValueAsCSSValue(Element * element) const290 PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
291 {
292 ASSERT(element);
293 if (!element->hasAttribute(m_attrName))
294 return 0;
295
296 RefPtr<MutableStylePropertySet> dummyStyle;
297 dummyStyle = MutableStylePropertySet::create();
298 dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName));
299 return dummyStyle->getPropertyCSSValue(m_propertyID);
300 }
301
302 class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent {
303 public:
create()304 static PassOwnPtr<HTMLFontSizeEquivalent> create()
305 {
306 return adoptPtr(new HTMLFontSizeEquivalent());
307 }
308 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
309
310 private:
311 HTMLFontSizeEquivalent();
312 };
313
HTMLFontSizeEquivalent()314 HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
315 : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
316 {
317 }
318
attributeValueAsCSSValue(Element * element) const319 PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
320 {
321 ASSERT(element);
322 if (!element->hasAttribute(m_attrName))
323 return 0;
324 CSSValueID size;
325 if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size))
326 return 0;
327 return CSSPrimitiveValue::createIdentifier(size);
328 }
329
330 float EditingStyle::NoFontDelta = 0.0f;
331
EditingStyle()332 EditingStyle::EditingStyle()
333 : m_shouldUseFixedDefaultFontSize(false)
334 , m_fontSizeDelta(NoFontDelta)
335 {
336 }
337
EditingStyle(Node * node,PropertiesToInclude propertiesToInclude)338 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
339 : m_shouldUseFixedDefaultFontSize(false)
340 , m_fontSizeDelta(NoFontDelta)
341 {
342 init(node, propertiesToInclude);
343 }
344
EditingStyle(const Position & position,PropertiesToInclude propertiesToInclude)345 EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
346 : m_shouldUseFixedDefaultFontSize(false)
347 , m_fontSizeDelta(NoFontDelta)
348 {
349 init(position.deprecatedNode(), propertiesToInclude);
350 }
351
EditingStyle(const StylePropertySet * style)352 EditingStyle::EditingStyle(const StylePropertySet* style)
353 : m_mutableStyle(style ? style->mutableCopy() : 0)
354 , m_shouldUseFixedDefaultFontSize(false)
355 , m_fontSizeDelta(NoFontDelta)
356 {
357 extractFontSizeDelta();
358 }
359
EditingStyle(const CSSStyleDeclaration * style)360 EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
361 : m_mutableStyle(style ? style->copyProperties() : 0)
362 , m_shouldUseFixedDefaultFontSize(false)
363 , m_fontSizeDelta(NoFontDelta)
364 {
365 extractFontSizeDelta();
366 }
367
EditingStyle(CSSPropertyID propertyID,const String & value)368 EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
369 : m_mutableStyle(0)
370 , m_shouldUseFixedDefaultFontSize(false)
371 , m_fontSizeDelta(NoFontDelta)
372 {
373 setProperty(propertyID, value);
374 }
375
~EditingStyle()376 EditingStyle::~EditingStyle()
377 {
378 }
379
cssValueToRGBA(CSSValue * colorValue)380 static RGBA32 cssValueToRGBA(CSSValue* colorValue)
381 {
382 if (!colorValue || !colorValue->isPrimitiveValue())
383 return Color::transparent;
384
385 CSSPrimitiveValue* primitiveColor = toCSSPrimitiveValue(colorValue);
386 if (primitiveColor->isRGBColor())
387 return primitiveColor->getRGBA32Value();
388
389 RGBA32 rgba = 0;
390 CSSParser::parseColor(rgba, colorValue->cssText());
391 return rgba;
392 }
393
getRGBAFontColor(CSSStyleDeclaration * style)394 static inline RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
395 {
396 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyColor).get());
397 }
398
getRGBAFontColor(StylePropertySet * style)399 static inline RGBA32 getRGBAFontColor(StylePropertySet* style)
400 {
401 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyColor).get());
402 }
403
getRGBABackgroundColor(CSSStyleDeclaration * style)404 static inline RGBA32 getRGBABackgroundColor(CSSStyleDeclaration* style)
405 {
406 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor).get());
407 }
408
getRGBABackgroundColor(StylePropertySet * style)409 static inline RGBA32 getRGBABackgroundColor(StylePropertySet* style)
410 {
411 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get());
412 }
413
rgbaBackgroundColorInEffect(Node * node)414 static inline RGBA32 rgbaBackgroundColorInEffect(Node* node)
415 {
416 return cssValueToRGBA(backgroundColorInEffect(node).get());
417 }
418
textAlignResolvingStartAndEnd(int textAlign,int direction)419 static int textAlignResolvingStartAndEnd(int textAlign, int direction)
420 {
421 switch (textAlign) {
422 case CSSValueCenter:
423 case CSSValueWebkitCenter:
424 return CSSValueCenter;
425 case CSSValueJustify:
426 return CSSValueJustify;
427 case CSSValueLeft:
428 case CSSValueWebkitLeft:
429 return CSSValueLeft;
430 case CSSValueRight:
431 case CSSValueWebkitRight:
432 return CSSValueRight;
433 case CSSValueStart:
434 return direction != CSSValueRtl ? CSSValueLeft : CSSValueRight;
435 case CSSValueEnd:
436 return direction == CSSValueRtl ? CSSValueRight : CSSValueLeft;
437 }
438 return CSSValueInvalid;
439 }
440
441 template<typename T>
textAlignResolvingStartAndEnd(T * style)442 static int textAlignResolvingStartAndEnd(T* style)
443 {
444 return textAlignResolvingStartAndEnd(getIdentifierValue(style, CSSPropertyTextAlign), getIdentifierValue(style, CSSPropertyDirection));
445 }
446
init(Node * node,PropertiesToInclude propertiesToInclude)447 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
448 {
449 if (isTabSpanTextNode(node))
450 node = tabSpanNode(node)->parentNode();
451 else if (isTabSpanNode(node))
452 node = node->parentNode();
453
454 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node);
455 m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copyProperties() : editingStyleFromComputedStyle(computedStyleAtPosition);
456
457 if (propertiesToInclude == EditingPropertiesInEffect) {
458 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
459 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
460 if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
461 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
462 }
463
464 if (node && node->computedStyle()) {
465 RenderStyle* renderStyle = node->computedStyle();
466 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
467 replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
468 }
469
470 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
471 extractFontSizeDelta();
472 }
473
removeTextFillAndStrokeColorsIfNeeded(RenderStyle * renderStyle)474 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
475 {
476 // If a node's text fill color is invalid, then its children use
477 // their font-color as their text fill color (they don't
478 // inherit it). Likewise for stroke color.
479 if (!renderStyle->textFillColor().isValid())
480 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
481 if (!renderStyle->textStrokeColor().isValid())
482 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
483 }
484
setProperty(CSSPropertyID propertyID,const String & value,bool important)485 void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important)
486 {
487 if (!m_mutableStyle)
488 m_mutableStyle = MutableStylePropertySet::create();
489
490 m_mutableStyle->setProperty(propertyID, value, important);
491 }
492
replaceFontSizeByKeywordIfPossible(RenderStyle * renderStyle,CSSComputedStyleDeclaration * computedStyle)493 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
494 {
495 ASSERT(renderStyle);
496 if (renderStyle->fontDescription().keywordSize())
497 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
498 }
499
extractFontSizeDelta()500 void EditingStyle::extractFontSizeDelta()
501 {
502 if (!m_mutableStyle)
503 return;
504
505 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
506 // Explicit font size overrides any delta.
507 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
508 return;
509 }
510
511 // Get the adjustment amount out of the style.
512 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
513 if (!value || !value->isPrimitiveValue())
514 return;
515
516 CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get());
517
518 // Only PX handled now. If we handle more types in the future, perhaps
519 // a switch statement here would be more appropriate.
520 if (!primitiveValue->isPx())
521 return;
522
523 m_fontSizeDelta = primitiveValue->getFloatValue();
524 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
525 }
526
isEmpty() const527 bool EditingStyle::isEmpty() const
528 {
529 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
530 }
531
textDirection(WritingDirection & writingDirection) const532 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
533 {
534 if (!m_mutableStyle)
535 return false;
536
537 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
538 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
539 return false;
540
541 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
542 if (unicodeBidiValue == CSSValueEmbed) {
543 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
544 if (!direction || !direction->isPrimitiveValue())
545 return false;
546
547 writingDirection = toCSSPrimitiveValue(direction.get())->getValueID() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
548
549 return true;
550 }
551
552 if (unicodeBidiValue == CSSValueNormal) {
553 writingDirection = NaturalWritingDirection;
554 return true;
555 }
556
557 return false;
558 }
559
setStyle(PassRefPtr<MutableStylePropertySet> style)560 void EditingStyle::setStyle(PassRefPtr<MutableStylePropertySet> style)
561 {
562 m_mutableStyle = style;
563 // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
564 // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
565 m_shouldUseFixedDefaultFontSize = false;
566 extractFontSizeDelta();
567 }
568
overrideWithStyle(const StylePropertySet * style)569 void EditingStyle::overrideWithStyle(const StylePropertySet* style)
570 {
571 if (!style || style->isEmpty())
572 return;
573 if (!m_mutableStyle)
574 m_mutableStyle = MutableStylePropertySet::create();
575 m_mutableStyle->mergeAndOverrideOnConflict(style);
576 extractFontSizeDelta();
577 }
578
clear()579 void EditingStyle::clear()
580 {
581 m_mutableStyle.clear();
582 m_shouldUseFixedDefaultFontSize = false;
583 m_fontSizeDelta = NoFontDelta;
584 }
585
copy() const586 PassRefPtr<EditingStyle> EditingStyle::copy() const
587 {
588 RefPtr<EditingStyle> copy = EditingStyle::create();
589 if (m_mutableStyle)
590 copy->m_mutableStyle = m_mutableStyle->mutableCopy();
591 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
592 copy->m_fontSizeDelta = m_fontSizeDelta;
593 return copy;
594 }
595
extractAndRemoveBlockProperties()596 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
597 {
598 RefPtr<EditingStyle> blockProperties = EditingStyle::create();
599 if (!m_mutableStyle)
600 return blockProperties;
601
602 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
603 m_mutableStyle->removeBlockProperties();
604
605 return blockProperties;
606 }
607
extractAndRemoveTextDirection()608 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
609 {
610 RefPtr<EditingStyle> textDirection = EditingStyle::create();
611 textDirection->m_mutableStyle = MutableStylePropertySet::create();
612 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
613 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
614 m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
615
616 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
617 m_mutableStyle->removeProperty(CSSPropertyDirection);
618
619 return textDirection;
620 }
621
removeBlockProperties()622 void EditingStyle::removeBlockProperties()
623 {
624 if (!m_mutableStyle)
625 return;
626
627 m_mutableStyle->removeBlockProperties();
628 }
629
removeStyleAddedByNode(Node * node)630 void EditingStyle::removeStyleAddedByNode(Node* node)
631 {
632 if (!node || !node->parentNode())
633 return;
634 RefPtr<MutableStylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
635 RefPtr<MutableStylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
636 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
637 m_mutableStyle->removeEquivalentProperties(nodeStyle->ensureCSSStyleDeclaration());
638 }
639
removeStyleConflictingWithStyleOfNode(Node * node)640 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
641 {
642 if (!node || !node->parentNode() || !m_mutableStyle)
643 return;
644
645 RefPtr<MutableStylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
646 RefPtr<MutableStylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
647 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
648
649 unsigned propertyCount = nodeStyle->propertyCount();
650 for (unsigned i = 0; i < propertyCount; ++i)
651 m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id());
652 }
653
collapseTextDecorationProperties()654 void EditingStyle::collapseTextDecorationProperties()
655 {
656 if (!m_mutableStyle)
657 return;
658
659 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
660 if (!textDecorationsInEffect)
661 return;
662
663 if (textDecorationsInEffect->isValueList())
664 m_mutableStyle->setProperty(textDecorationPropertyForEditing(), textDecorationsInEffect->cssText(), m_mutableStyle->propertyIsImportant(textDecorationPropertyForEditing()));
665 else
666 m_mutableStyle->removeProperty(textDecorationPropertyForEditing());
667 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
668 }
669
670 // CSS properties that create a visual difference only when applied to text.
671 static const CSSPropertyID textOnlyProperties[] = {
672 // FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
673 // Decoration feature is no longer experimental.
674 CSSPropertyTextDecoration,
675 CSSPropertyTextDecorationLine,
676 CSSPropertyWebkitTextDecorationsInEffect,
677 CSSPropertyFontStyle,
678 CSSPropertyFontWeight,
679 CSSPropertyColor,
680 };
681
triStateOfStyle(EditingStyle * style) const682 TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
683 {
684 if (!style || !style->m_mutableStyle)
685 return FalseTriState;
686 return triStateOfStyle(style->m_mutableStyle->ensureCSSStyleDeclaration(), DoNotIgnoreTextOnlyProperties);
687 }
688
triStateOfStyle(CSSStyleDeclaration * styleToCompare,ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const689 TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
690 {
691 RefPtr<MutableStylePropertySet> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
692
693 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
694 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
695
696 if (difference->isEmpty())
697 return TrueTriState;
698 if (difference->propertyCount() == m_mutableStyle->propertyCount())
699 return FalseTriState;
700
701 return MixedTriState;
702 }
703
triStateOfStyle(const VisibleSelection & selection) const704 TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
705 {
706 if (!selection.isCaretOrRange())
707 return FalseTriState;
708
709 if (selection.isCaret())
710 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
711
712 TriState state = FalseTriState;
713 bool nodeIsStart = true;
714 for (Node* node = selection.start().deprecatedNode(); node; node = NodeTraversal::next(*node)) {
715 if (node->renderer() && node->rendererIsEditable()) {
716 RefPtr<CSSComputedStyleDeclaration> nodeStyle = CSSComputedStyleDeclaration::create(node);
717 if (nodeStyle) {
718 TriState nodeState = triStateOfStyle(nodeStyle.get(), node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
719 if (nodeIsStart) {
720 state = nodeState;
721 nodeIsStart = false;
722 } else if (state != nodeState && node->isTextNode()) {
723 state = MixedTriState;
724 break;
725 }
726 }
727 }
728 if (node == selection.end().deprecatedNode())
729 break;
730 }
731
732 return state;
733 }
734
conflictsWithInlineStyleOfElement(Element * element,EditingStyle * extractedStyle,Vector<CSSPropertyID> * conflictingProperties) const735 bool EditingStyle::conflictsWithInlineStyleOfElement(Element* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
736 {
737 ASSERT(element);
738 ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
739
740 const StylePropertySet* inlineStyle = element->inlineStyle();
741 if (!m_mutableStyle || !inlineStyle)
742 return false;
743
744 unsigned propertyCount = m_mutableStyle->propertyCount();
745 for (unsigned i = 0; i < propertyCount; ++i) {
746 CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
747
748 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
749 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
750 continue;
751
752 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(textDecorationPropertyForEditing())) {
753 if (!conflictingProperties)
754 return true;
755 conflictingProperties->append(CSSPropertyTextDecoration);
756 // Because text-decoration expands to text-decoration-line when CSS3
757 // Text Decoration is enabled, we also state it as conflicting.
758 if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
759 conflictingProperties->append(CSSPropertyTextDecorationLine);
760 if (extractedStyle)
761 extractedStyle->setProperty(textDecorationPropertyForEditing(), inlineStyle->getPropertyValue(textDecorationPropertyForEditing()), inlineStyle->propertyIsImportant(textDecorationPropertyForEditing()));
762 continue;
763 }
764
765 if (!inlineStyle->getPropertyCSSValue(propertyID))
766 continue;
767
768 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
769 if (!conflictingProperties)
770 return true;
771 conflictingProperties->append(CSSPropertyDirection);
772 if (extractedStyle)
773 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
774 }
775
776 if (!conflictingProperties)
777 return true;
778
779 conflictingProperties->append(propertyID);
780
781 if (extractedStyle)
782 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
783 }
784
785 return conflictingProperties && !conflictingProperties->isEmpty();
786 }
787
htmlElementEquivalents()788 static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
789 {
790 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
791
792 if (!HTMLElementEquivalents.size()) {
793 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
794 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
795 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
796 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
797 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
798 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
799
800 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag));
801 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag));
802 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag));
803 }
804
805 return HTMLElementEquivalents;
806 }
807
808
conflictsWithImplicitStyleOfElement(HTMLElement * element,EditingStyle * extractedStyle,ShouldExtractMatchingStyle shouldExtractMatchingStyle) const809 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
810 {
811 if (!m_mutableStyle)
812 return false;
813
814 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
815 for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
816 const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
817 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
818 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
819 if (extractedStyle)
820 equivalent->addToStyle(element, extractedStyle);
821 return true;
822 }
823 }
824 return false;
825 }
826
htmlAttributeEquivalents()827 static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
828 {
829 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
830
831 if (!HTMLAttributeEquivalents.size()) {
832 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
833 // of exactly one element except dirAttr.
834 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
835 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
836 HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
837
838 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
839 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
840 }
841
842 return HTMLAttributeEquivalents;
843 }
844
conflictsWithImplicitStyleOfAttributes(HTMLElement * element) const845 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
846 {
847 ASSERT(element);
848 if (!m_mutableStyle)
849 return false;
850
851 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
852 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
853 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
854 && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
855 return true;
856 }
857
858 return false;
859 }
860
extractConflictingImplicitStyleOfAttributes(HTMLElement * element,ShouldPreserveWritingDirection shouldPreserveWritingDirection,EditingStyle * extractedStyle,Vector<QualifiedName> & conflictingAttributes,ShouldExtractMatchingStyle shouldExtractMatchingStyle) const861 bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
862 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
863 {
864 ASSERT(element);
865 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
866 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
867 if (!m_mutableStyle)
868 return false;
869
870 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
871 bool removed = false;
872 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
873 const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
874
875 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
876 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
877 continue;
878
879 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
880 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
881 continue;
882
883 if (extractedStyle)
884 equivalent->addToStyle(element, extractedStyle);
885 conflictingAttributes.append(equivalent->attributeName());
886 removed = true;
887 }
888
889 return removed;
890 }
891
styleIsPresentInComputedStyleOfNode(Node * node) const892 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
893 {
894 return !m_mutableStyle || getPropertiesNotIn(m_mutableStyle.get(), CSSComputedStyleDeclaration::create(node).get())->isEmpty();
895 }
896
elementIsStyledSpanOrHTMLEquivalent(const HTMLElement * element)897 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
898 {
899 bool elementIsSpanOrElementEquivalent = false;
900 if (element->hasTagName(HTMLNames::spanTag))
901 elementIsSpanOrElementEquivalent = true;
902 else {
903 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
904 size_t i;
905 for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
906 if (HTMLElementEquivalents[i]->matches(element)) {
907 elementIsSpanOrElementEquivalent = true;
908 break;
909 }
910 }
911 }
912
913 if (!element->hasAttributes())
914 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
915
916 unsigned matchedAttributes = 0;
917 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
918 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
919 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
920 matchedAttributes++;
921 }
922
923 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
924 return false; // element is not a span, a html element equivalent, or font element.
925
926 if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
927 matchedAttributes++;
928
929 if (element->hasAttribute(HTMLNames::styleAttr)) {
930 if (const StylePropertySet* style = element->inlineStyle()) {
931 unsigned propertyCount = style->propertyCount();
932 for (unsigned i = 0; i < propertyCount; ++i) {
933 if (!isEditingProperty(style->propertyAt(i).id()))
934 return false;
935 }
936 }
937 matchedAttributes++;
938 }
939
940 // font with color attribute, span with style attribute, etc...
941 ASSERT(matchedAttributes <= element->attributeCount());
942 return matchedAttributes >= element->attributeCount();
943 }
944
prepareToApplyAt(const Position & position,ShouldPreserveWritingDirection shouldPreserveWritingDirection)945 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
946 {
947 if (!m_mutableStyle)
948 return;
949
950 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
951 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
952 // which one of editingStyleAtPosition or computedStyle is called.
953 RefPtr<EditingStyle> editingStyleAtPosition = EditingStyle::create(position, EditingPropertiesInEffect);
954 StylePropertySet* styleAtPosition = editingStyleAtPosition->m_mutableStyle.get();
955
956 RefPtr<CSSValue> unicodeBidi;
957 RefPtr<CSSValue> direction;
958 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
959 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
960 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
961 }
962
963 m_mutableStyle->removeEquivalentProperties(styleAtPosition);
964
965 if (textAlignResolvingStartAndEnd(m_mutableStyle.get()) == textAlignResolvingStartAndEnd(styleAtPosition))
966 m_mutableStyle->removeProperty(CSSPropertyTextAlign);
967
968 if (getRGBAFontColor(m_mutableStyle.get()) == getRGBAFontColor(styleAtPosition))
969 m_mutableStyle->removeProperty(CSSPropertyColor);
970
971 if (hasTransparentBackgroundColor(m_mutableStyle.get())
972 || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
973 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
974
975 if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
976 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, toCSSPrimitiveValue(unicodeBidi.get())->getValueID());
977 if (direction && direction->isPrimitiveValue())
978 m_mutableStyle->setProperty(CSSPropertyDirection, toCSSPrimitiveValue(direction.get())->getValueID());
979 }
980 }
981
mergeTypingStyle(Document * document)982 void EditingStyle::mergeTypingStyle(Document* document)
983 {
984 ASSERT(document);
985
986 RefPtr<EditingStyle> typingStyle = document->frame()->selection().typingStyle();
987 if (!typingStyle || typingStyle == this)
988 return;
989
990 mergeStyle(typingStyle->style(), OverrideValues);
991 }
992
mergeInlineStyleOfElement(Element * element,CSSPropertyOverrideMode mode,PropertiesToInclude propertiesToInclude)993 void EditingStyle::mergeInlineStyleOfElement(Element* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
994 {
995 ASSERT(element);
996 if (!element->inlineStyle())
997 return;
998
999 switch (propertiesToInclude) {
1000 case AllProperties:
1001 mergeStyle(element->inlineStyle(), mode);
1002 return;
1003 case OnlyEditingInheritableProperties:
1004 mergeStyle(copyEditingProperties(element->inlineStyle(), OnlyInheritableEditingProperties).get(), mode);
1005 return;
1006 case EditingPropertiesInEffect:
1007 mergeStyle(copyEditingProperties(element->inlineStyle(), AllEditingProperties).get(), mode);
1008 return;
1009 }
1010 }
1011
elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent * equivalent,const Element * element,EditingStyle::CSSPropertyOverrideMode mode,StylePropertySet * style)1012 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const Element* element,
1013 EditingStyle::CSSPropertyOverrideMode mode, StylePropertySet* style)
1014 {
1015 return equivalent->matches(element) && (!element->inlineStyle() || !equivalent->propertyExistsInStyle(element->inlineStyle()))
1016 && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
1017 }
1018
extractEditingProperties(const StylePropertySet * style,EditingStyle::PropertiesToInclude propertiesToInclude)1019 static PassRefPtr<MutableStylePropertySet> extractEditingProperties(const StylePropertySet* style, EditingStyle::PropertiesToInclude propertiesToInclude)
1020 {
1021 if (!style)
1022 return 0;
1023
1024 switch (propertiesToInclude) {
1025 case EditingStyle::AllProperties:
1026 case EditingStyle::EditingPropertiesInEffect:
1027 return copyEditingProperties(style, AllEditingProperties);
1028 case EditingStyle::OnlyEditingInheritableProperties:
1029 return copyEditingProperties(style, OnlyInheritableEditingProperties);
1030 }
1031
1032 ASSERT_NOT_REACHED();
1033 return 0;
1034 }
1035
mergeInlineAndImplicitStyleOfElement(Element * element,CSSPropertyOverrideMode mode,PropertiesToInclude propertiesToInclude)1036 void EditingStyle::mergeInlineAndImplicitStyleOfElement(Element* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
1037 {
1038 RefPtr<EditingStyle> styleFromRules = EditingStyle::create();
1039 styleFromRules->mergeStyleFromRulesForSerialization(element);
1040 styleFromRules->m_mutableStyle = extractEditingProperties(styleFromRules->m_mutableStyle.get(), propertiesToInclude);
1041 mergeStyle(styleFromRules->m_mutableStyle.get(), mode);
1042
1043 mergeInlineStyleOfElement(element, mode, propertiesToInclude);
1044
1045 const Vector<OwnPtr<HTMLElementEquivalent> >& elementEquivalents = htmlElementEquivalents();
1046 for (size_t i = 0; i < elementEquivalents.size(); ++i) {
1047 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1048 elementEquivalents[i]->addToStyle(element, this);
1049 }
1050
1051 const Vector<OwnPtr<HTMLAttributeEquivalent> >& attributeEquivalents = htmlAttributeEquivalents();
1052 for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
1053 if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
1054 continue; // We don't want to include directionality
1055 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1056 attributeEquivalents[i]->addToStyle(element, this);
1057 }
1058 }
1059
wrappingStyleForSerialization(Node * context,bool shouldAnnotate)1060 PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate)
1061 {
1062 RefPtr<EditingStyle> wrappingStyle;
1063 if (shouldAnnotate) {
1064 wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
1065
1066 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
1067 // to help us differentiate those styles from ones that the user has applied.
1068 // This helps us get the color of content pasted into blockquotes right.
1069 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(context), isMailBlockquote, CanCrossEditingBoundary));
1070
1071 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
1072 wrappingStyle->collapseTextDecorationProperties();
1073
1074 return wrappingStyle.release();
1075 }
1076
1077 wrappingStyle = EditingStyle::create();
1078
1079 // When not annotating for interchange, we only preserve inline style declarations.
1080 for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) {
1081 if (node->isStyledElement() && !isMailBlockquote(node)) {
1082 wrappingStyle->mergeInlineAndImplicitStyleOfElement(toElement(node), EditingStyle::DoNotOverrideValues,
1083 EditingStyle::EditingPropertiesInEffect);
1084 }
1085 }
1086
1087 return wrappingStyle.release();
1088 }
1089
1090
mergeTextDecorationValues(CSSValueList * mergedValue,const CSSValueList * valueToMerge)1091 static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
1092 {
1093 DEFINE_STATIC_REF(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1094 DEFINE_STATIC_REF(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1095
1096 if (valueToMerge->hasValue(underline) && !mergedValue->hasValue(underline))
1097 mergedValue->append(underline);
1098
1099 if (valueToMerge->hasValue(lineThrough) && !mergedValue->hasValue(lineThrough))
1100 mergedValue->append(lineThrough);
1101 }
1102
mergeStyle(const StylePropertySet * style,CSSPropertyOverrideMode mode)1103 void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode)
1104 {
1105 if (!style)
1106 return;
1107
1108 if (!m_mutableStyle) {
1109 m_mutableStyle = style->mutableCopy();
1110 return;
1111 }
1112
1113 unsigned propertyCount = style->propertyCount();
1114 for (unsigned i = 0; i < propertyCount; ++i) {
1115 StylePropertySet::PropertyReference property = style->propertyAt(i);
1116 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
1117
1118 // text decorations never override values
1119 if ((property.id() == textDecorationPropertyForEditing() || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
1120 if (value->isValueList()) {
1121 mergeTextDecorationValues(toCSSValueList(value.get()), toCSSValueList(property.value()));
1122 continue;
1123 }
1124 value = 0; // text-decoration: none is equivalent to not having the property
1125 }
1126
1127 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1128 m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant());
1129 }
1130 }
1131
styleFromMatchedRulesForElement(Element * element,unsigned rulesToInclude)1132 static PassRefPtr<MutableStylePropertySet> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude)
1133 {
1134 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create();
1135 RefPtr<StyleRuleList> matchedRules = element->document().ensureStyleResolver().styleRulesForElement(element, rulesToInclude);
1136 if (matchedRules) {
1137 for (unsigned i = 0; i < matchedRules->m_list.size(); ++i)
1138 style->mergeAndOverrideOnConflict(matchedRules->m_list[i]->properties());
1139 }
1140 return style.release();
1141 }
1142
mergeStyleFromRules(Element * element)1143 void EditingStyle::mergeStyleFromRules(Element* element)
1144 {
1145 RefPtr<MutableStylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1146 StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules);
1147 // Styles from the inline style declaration, held in the variable "style", take precedence
1148 // over those from matched rules.
1149 if (m_mutableStyle)
1150 styleFromMatchedRules->mergeAndOverrideOnConflict(m_mutableStyle.get());
1151
1152 clear();
1153 m_mutableStyle = styleFromMatchedRules;
1154 }
1155
mergeStyleFromRulesForSerialization(Element * element)1156 void EditingStyle::mergeStyleFromRulesForSerialization(Element* element)
1157 {
1158 mergeStyleFromRules(element);
1159
1160 // The property value, if it's a percentage, may not reflect the actual computed value.
1161 // For example: style="height: 1%; overflow: visible;" in quirksmode
1162 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1163 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = CSSComputedStyleDeclaration::create(element);
1164 RefPtr<MutableStylePropertySet> fromComputedStyle = MutableStylePropertySet::create();
1165 {
1166 unsigned propertyCount = m_mutableStyle->propertyCount();
1167 for (unsigned i = 0; i < propertyCount; ++i) {
1168 StylePropertySet::PropertyReference property = m_mutableStyle->propertyAt(i);
1169 CSSValue* value = property.value();
1170 if (!value->isPrimitiveValue())
1171 continue;
1172 if (toCSSPrimitiveValue(value)->isPercentage()) {
1173 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
1174 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
1175 }
1176 }
1177 }
1178 m_mutableStyle->mergeAndOverrideOnConflict(fromComputedStyle.get());
1179 }
1180
removePropertiesInStyle(MutableStylePropertySet * styleToRemovePropertiesFrom,StylePropertySet * style)1181 static void removePropertiesInStyle(MutableStylePropertySet* styleToRemovePropertiesFrom, StylePropertySet* style)
1182 {
1183 unsigned propertyCount = style->propertyCount();
1184 Vector<CSSPropertyID> propertiesToRemove(propertyCount);
1185 for (unsigned i = 0; i < propertyCount; ++i)
1186 propertiesToRemove[i] = style->propertyAt(i).id();
1187
1188 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1189 }
1190
removeStyleFromRulesAndContext(Element * element,Node * context)1191 void EditingStyle::removeStyleFromRulesAndContext(Element* element, Node* context)
1192 {
1193 ASSERT(element);
1194 if (!m_mutableStyle)
1195 return;
1196
1197 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1198 RefPtr<MutableStylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules);
1199 if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
1200 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules->ensureCSSStyleDeclaration());
1201
1202 // 2. Remove style present in context and not overriden by matched rules.
1203 RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1204 if (computedStyle->m_mutableStyle) {
1205 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1206 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1207
1208 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1209 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle->ensureCSSStyleDeclaration());
1210 }
1211
1212 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules.
1213 // These rules are added by serialization code to wrap text nodes.
1214 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1215 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline)
1216 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1217 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone)
1218 m_mutableStyle->removeProperty(CSSPropertyFloat);
1219 }
1220 }
1221
removePropertiesInElementDefaultStyle(Element * element)1222 void EditingStyle::removePropertiesInElementDefaultStyle(Element* element)
1223 {
1224 if (!m_mutableStyle || m_mutableStyle->isEmpty())
1225 return;
1226
1227 RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules);
1228
1229 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1230 }
1231
forceInline()1232 void EditingStyle::forceInline()
1233 {
1234 if (!m_mutableStyle)
1235 m_mutableStyle = MutableStylePropertySet::create();
1236 const bool propertyIsImportant = true;
1237 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1238 }
1239
legacyFontSize(Document * document) const1240 int EditingStyle::legacyFontSize(Document* document) const
1241 {
1242 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1243 if (!cssValue || !cssValue->isPrimitiveValue())
1244 return 0;
1245 return legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(cssValue.get()),
1246 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1247 }
1248
styleAtSelectionStart(const VisibleSelection & selection,bool shouldUseBackgroundColorInEffect)1249 PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1250 {
1251 if (selection.isNone())
1252 return 0;
1253
1254 Position position = adjustedSelectionStartForStyleComputation(selection);
1255
1256 // If the pos is at the end of a text node, then this node is not fully selected.
1257 // Move it to the next deep equivalent position to avoid removing the style from this node.
1258 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1259 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1260 Node* positionNode = position.containerNode();
1261 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1262 position = nextVisuallyDistinctCandidate(position);
1263
1264 Element* element = position.element();
1265 if (!element)
1266 return 0;
1267
1268 RefPtr<EditingStyle> style = EditingStyle::create(element, EditingStyle::AllProperties);
1269 style->mergeTypingStyle(&element->document());
1270
1271 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1272 // Also, if the selection is a range, ignore the background color at the start of selection,
1273 // and find the background color of the common ancestor.
1274 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1275 RefPtr<Range> range(selection.toNormalizedRange());
1276 if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(IGNORE_EXCEPTION)))
1277 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1278 }
1279
1280 return style;
1281 }
1282
textDirectionForSelection(const VisibleSelection & selection,EditingStyle * typingStyle,bool & hasNestedOrMultipleEmbeddings)1283 WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& selection, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings)
1284 {
1285 hasNestedOrMultipleEmbeddings = true;
1286
1287 if (selection.isNone())
1288 return NaturalWritingDirection;
1289
1290 Position position = selection.start().downstream();
1291
1292 Node* node = position.deprecatedNode();
1293 if (!node)
1294 return NaturalWritingDirection;
1295
1296 Position end;
1297 if (selection.isRange()) {
1298 end = selection.end().upstream();
1299
1300 ASSERT(end.document());
1301 Node* pastLast = Range::create(*end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode();
1302 for (Node* n = node; n && n != pastLast; n = NodeTraversal::next(*n)) {
1303 if (!n->isStyledElement())
1304 continue;
1305
1306 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(n);
1307 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1308 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1309 continue;
1310
1311 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
1312 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
1313 return NaturalWritingDirection;
1314 }
1315 }
1316
1317 if (selection.isCaret()) {
1318 WritingDirection direction;
1319 if (typingStyle && typingStyle->textDirection(direction)) {
1320 hasNestedOrMultipleEmbeddings = false;
1321 return direction;
1322 }
1323 node = selection.visibleStart().deepEquivalent().deprecatedNode();
1324 }
1325
1326 // The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position
1327 // to decide.
1328 Node* block = enclosingBlock(node);
1329 WritingDirection foundDirection = NaturalWritingDirection;
1330
1331 for (; node != block; node = node->parentNode()) {
1332 if (!node->isStyledElement())
1333 continue;
1334
1335 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
1336 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1337 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1338 continue;
1339
1340 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
1341 if (unicodeBidiValue == CSSValueNormal)
1342 continue;
1343
1344 if (unicodeBidiValue == CSSValueBidiOverride)
1345 return NaturalWritingDirection;
1346
1347 ASSERT(unicodeBidiValue == CSSValueEmbed);
1348 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
1349 if (!direction || !direction->isPrimitiveValue())
1350 continue;
1351
1352 int directionValue = toCSSPrimitiveValue(direction.get())->getValueID();
1353 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
1354 continue;
1355
1356 if (foundDirection != NaturalWritingDirection)
1357 return NaturalWritingDirection;
1358
1359 // In the range case, make sure that the embedding element persists until the end of the range.
1360 if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(node))
1361 return NaturalWritingDirection;
1362
1363 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
1364 }
1365 hasNestedOrMultipleEmbeddings = false;
1366 return foundDirection;
1367 }
1368
reconcileTextDecorationProperties(MutableStylePropertySet * style)1369 static void reconcileTextDecorationProperties(MutableStylePropertySet* style)
1370 {
1371 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1372 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(textDecorationPropertyForEditing());
1373 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1374 ASSERT(!textDecorationsInEffect || !textDecoration);
1375 if (textDecorationsInEffect) {
1376 style->setProperty(textDecorationPropertyForEditing(), textDecorationsInEffect->cssText());
1377 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1378 textDecoration = textDecorationsInEffect;
1379 }
1380
1381 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1382 if (textDecoration && !textDecoration->isValueList())
1383 style->removeProperty(textDecorationPropertyForEditing());
1384 }
1385
StyleChange(EditingStyle * style,const Position & position)1386 StyleChange::StyleChange(EditingStyle* style, const Position& position)
1387 : m_applyBold(false)
1388 , m_applyItalic(false)
1389 , m_applyUnderline(false)
1390 , m_applyLineThrough(false)
1391 , m_applySubscript(false)
1392 , m_applySuperscript(false)
1393 {
1394 Document* document = position.document();
1395 if (!style || !style->style() || !document || !document->frame())
1396 return;
1397
1398 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
1399 // FIXME: take care of background-color in effect
1400 RefPtr<MutableStylePropertySet> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
1401
1402 reconcileTextDecorationProperties(mutableStyle.get());
1403 if (!document->frame()->editor().shouldStyleWithCSS())
1404 extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
1405
1406 // Changing the whitespace style in a tab span would collapse the tab into a space.
1407 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1408 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1409
1410 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1411 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1412 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1413 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1414
1415 // Save the result for later
1416 m_cssStyle = mutableStyle->asText().stripWhiteSpace();
1417 }
1418
setTextDecorationProperty(MutableStylePropertySet * style,const CSSValueList * newTextDecoration,CSSPropertyID propertyID)1419 static void setTextDecorationProperty(MutableStylePropertySet* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
1420 {
1421 if (newTextDecoration->length())
1422 style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID));
1423 else {
1424 // text-decoration: none is redundant since it does not remove any text decorations.
1425 ASSERT(!style->propertyIsImportant(propertyID));
1426 style->removeProperty(propertyID);
1427 }
1428 }
1429
extractTextStyles(Document * document,MutableStylePropertySet * style,bool shouldUseFixedFontDefaultSize)1430 void StyleChange::extractTextStyles(Document* document, MutableStylePropertySet* style, bool shouldUseFixedFontDefaultSize)
1431 {
1432 ASSERT(style);
1433
1434 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
1435 style->removeProperty(CSSPropertyFontWeight);
1436 m_applyBold = true;
1437 }
1438
1439 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
1440 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1441 style->removeProperty(CSSPropertyFontStyle);
1442 m_applyItalic = true;
1443 }
1444
1445 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1446 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1447 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(textDecorationPropertyForEditing());
1448 if (textDecoration && textDecoration->isValueList()) {
1449 DEFINE_STATIC_REF(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1450 DEFINE_STATIC_REF(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1451
1452 RefPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy();
1453 if (newTextDecoration->removeAll(underline))
1454 m_applyUnderline = true;
1455 if (newTextDecoration->removeAll(lineThrough))
1456 m_applyLineThrough = true;
1457
1458 // If trimTextDecorations, delete underline and line-through
1459 setTextDecorationProperty(style, newTextDecoration.get(), textDecorationPropertyForEditing());
1460 }
1461
1462 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
1463 switch (verticalAlign) {
1464 case CSSValueSub:
1465 style->removeProperty(CSSPropertyVerticalAlign);
1466 m_applySubscript = true;
1467 break;
1468 case CSSValueSuper:
1469 style->removeProperty(CSSPropertyVerticalAlign);
1470 m_applySuperscript = true;
1471 break;
1472 }
1473
1474 if (style->getPropertyCSSValue(CSSPropertyColor)) {
1475 m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
1476 style->removeProperty(CSSPropertyColor);
1477 }
1478
1479 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
1480 // Remove single quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448
1481 m_applyFontFace.replaceWithLiteral('\'', "");
1482 style->removeProperty(CSSPropertyFontFamily);
1483
1484 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
1485 if (!fontSize->isPrimitiveValue())
1486 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1487 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(fontSize.get()),
1488 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1489 m_applyFontSize = String::number(legacyFontSize);
1490 style->removeProperty(CSSPropertyFontSize);
1491 }
1492 }
1493 }
1494
diffTextDecorations(MutableStylePropertySet * style,CSSPropertyID propertID,CSSValue * refTextDecoration)1495 static void diffTextDecorations(MutableStylePropertySet* style, CSSPropertyID propertID, CSSValue* refTextDecoration)
1496 {
1497 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
1498 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
1499 return;
1500
1501 RefPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy();
1502 CSSValueList* valuesInRefTextDecoration = toCSSValueList(refTextDecoration);
1503
1504 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
1505 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
1506
1507 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1508 }
1509
fontWeightIsBold(CSSValue * fontWeight)1510 static bool fontWeightIsBold(CSSValue* fontWeight)
1511 {
1512 if (!fontWeight->isPrimitiveValue())
1513 return false;
1514
1515 // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
1516 // Collapse all other values to either one of these two states for editing purposes.
1517 switch (toCSSPrimitiveValue(fontWeight)->getValueID()) {
1518 case CSSValue100:
1519 case CSSValue200:
1520 case CSSValue300:
1521 case CSSValue400:
1522 case CSSValue500:
1523 case CSSValueNormal:
1524 return false;
1525 case CSSValueBold:
1526 case CSSValue600:
1527 case CSSValue700:
1528 case CSSValue800:
1529 case CSSValue900:
1530 return true;
1531 default:
1532 break;
1533 }
1534
1535 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
1536 return false;
1537 }
1538
fontWeightNeedsResolving(CSSValue * fontWeight)1539 static bool fontWeightNeedsResolving(CSSValue* fontWeight)
1540 {
1541 if (!fontWeight->isPrimitiveValue())
1542 return true;
1543
1544 CSSValueID value = toCSSPrimitiveValue(fontWeight)->getValueID();
1545 return value == CSSValueLighter || value == CSSValueBolder;
1546 }
1547
getPropertiesNotIn(StylePropertySet * styleWithRedundantProperties,CSSStyleDeclaration * baseStyle)1548 PassRefPtr<MutableStylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
1549 {
1550 ASSERT(styleWithRedundantProperties);
1551 ASSERT(baseStyle);
1552 RefPtr<MutableStylePropertySet> result = styleWithRedundantProperties->mutableCopy();
1553
1554 result->removeEquivalentProperties(baseStyle);
1555
1556 RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValueInternal(CSSPropertyWebkitTextDecorationsInEffect);
1557 diffTextDecorations(result.get(), textDecorationPropertyForEditing(), baseTextDecorationsInEffect.get());
1558 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1559
1560 if (RefPtr<CSSValue> baseFontWeight = baseStyle->getPropertyCSSValueInternal(CSSPropertyFontWeight)) {
1561 if (RefPtr<CSSValue> fontWeight = result->getPropertyCSSValue(CSSPropertyFontWeight)) {
1562 if (!fontWeightNeedsResolving(fontWeight.get()) && (fontWeightIsBold(fontWeight.get()) == fontWeightIsBold(baseFontWeight.get())))
1563 result->removeProperty(CSSPropertyFontWeight);
1564 }
1565 }
1566
1567 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyColor) && getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1568 result->removeProperty(CSSPropertyColor);
1569
1570 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyTextAlign)
1571 && textAlignResolvingStartAndEnd(result.get()) == textAlignResolvingStartAndEnd(baseStyle))
1572 result->removeProperty(CSSPropertyTextAlign);
1573
1574 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyBackgroundColor) && getRGBABackgroundColor(result.get()) == getRGBABackgroundColor(baseStyle))
1575 result->removeProperty(CSSPropertyBackgroundColor);
1576
1577 return result.release();
1578 }
1579
getIdentifierValue(StylePropertySet * style,CSSPropertyID propertyID)1580 CSSValueID getIdentifierValue(StylePropertySet* style, CSSPropertyID propertyID)
1581 {
1582 if (!style)
1583 return CSSValueInvalid;
1584 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1585 if (!value || !value->isPrimitiveValue())
1586 return CSSValueInvalid;
1587 return toCSSPrimitiveValue(value.get())->getValueID();
1588 }
1589
getIdentifierValue(CSSStyleDeclaration * style,CSSPropertyID propertyID)1590 CSSValueID getIdentifierValue(CSSStyleDeclaration* style, CSSPropertyID propertyID)
1591 {
1592 if (!style)
1593 return CSSValueInvalid;
1594 RefPtr<CSSValue> value = style->getPropertyCSSValueInternal(propertyID);
1595 if (!value || !value->isPrimitiveValue())
1596 return CSSValueInvalid;
1597 return toCSSPrimitiveValue(value.get())->getValueID();
1598 }
1599
isCSSValueLength(CSSPrimitiveValue * value)1600 static bool isCSSValueLength(CSSPrimitiveValue* value)
1601 {
1602 return value->isFontIndependentLength();
1603 }
1604
legacyFontSizeFromCSSValue(Document * document,CSSPrimitiveValue * value,bool shouldUseFixedFontDefaultSize,LegacyFontSizeMode mode)1605 int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1606 {
1607 if (isCSSValueLength(value)) {
1608 int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1609 int legacyFontSize = FontSize::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1610 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1611 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1612 if (mode == AlwaysUseLegacyFontSize || FontSize::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1613 return legacyFontSize;
1614
1615 return 0;
1616 }
1617
1618 if (CSSValueXSmall <= value->getValueID() && value->getValueID() <= CSSValueWebkitXxxLarge)
1619 return value->getValueID() - CSSValueXSmall + 1;
1620
1621 return 0;
1622 }
1623
isTransparentColorValue(CSSValue * cssValue)1624 bool isTransparentColorValue(CSSValue* cssValue)
1625 {
1626 if (!cssValue)
1627 return true;
1628 if (!cssValue->isPrimitiveValue())
1629 return false;
1630 CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue);
1631 if (value->isRGBColor())
1632 return !alphaChannel(value->getRGBA32Value());
1633 return value->getValueID() == CSSValueTransparent;
1634 }
1635
hasTransparentBackgroundColor(CSSStyleDeclaration * style)1636 bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
1637 {
1638 RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
1639 return isTransparentColorValue(cssValue.get());
1640 }
1641
hasTransparentBackgroundColor(StylePropertySet * style)1642 bool hasTransparentBackgroundColor(StylePropertySet* style)
1643 {
1644 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
1645 return isTransparentColorValue(cssValue.get());
1646 }
1647
backgroundColorInEffect(Node * node)1648 PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
1649 {
1650 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1651 RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor);
1652 if (!hasTransparentBackgroundColor(ancestorStyle.get()))
1653 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
1654 }
1655 return 0;
1656 }
1657
1658 }
1659