2011-02-22 Ryosuke Niwa <rniwa@webkit.org>
[WebKit-https.git] / Source / WebCore / editing / EditingStyle.cpp
1 /*
2  * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
3  * Copyright (C) 2010 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 "EditingStyle.h"
29
30 #include "ApplyStyleCommand.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSMutableStyleDeclaration.h"
33 #include "CSSValueKeywords.h"
34 #include "Frame.h"
35 #include "HTMLNames.h"
36 #include "Node.h"
37 #include "Position.h"
38 #include "RenderStyle.h"
39 #include "SelectionController.h"
40 #include "StyledElement.h"
41 #include "htmlediting.h"
42
43 namespace WebCore {
44
45 // Editing style properties must be preserved during editing operation.
46 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
47 // FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties
48 static const int editingStyleProperties[] = {
49     // CSS inheritable properties
50     CSSPropertyBorderCollapse,
51     CSSPropertyColor,
52     CSSPropertyFontFamily,
53     CSSPropertyFontSize,
54     CSSPropertyFontStyle,
55     CSSPropertyFontVariant,
56     CSSPropertyFontWeight,
57     CSSPropertyLetterSpacing,
58     CSSPropertyLineHeight,
59     CSSPropertyOrphans,
60     CSSPropertyTextAlign,
61     CSSPropertyTextIndent,
62     CSSPropertyTextTransform,
63     CSSPropertyWhiteSpace,
64     CSSPropertyWidows,
65     CSSPropertyWordSpacing,
66     CSSPropertyWebkitBorderHorizontalSpacing,
67     CSSPropertyWebkitBorderVerticalSpacing,
68     CSSPropertyWebkitTextDecorationsInEffect,
69     CSSPropertyWebkitTextFillColor,
70     CSSPropertyWebkitTextSizeAdjust,
71     CSSPropertyWebkitTextStrokeColor,
72     CSSPropertyWebkitTextStrokeWidth,
73 };
74 size_t numEditingStyleProperties = WTF_ARRAY_LENGTH(editingStyleProperties);
75
76 static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style)
77 {
78     return style->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties);
79 }
80
81 static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style)
82 {
83     if (!style)
84         return CSSMutableStyleDeclaration::create();
85     return copyEditingProperties(style.get());
86 }
87
88 float EditingStyle::NoFontDelta = 0.0f;
89
90 EditingStyle::EditingStyle()
91     : m_shouldUseFixedDefaultFontSize(false)
92     , m_fontSizeDelta(NoFontDelta)
93 {
94 }
95
96 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
97     : m_shouldUseFixedDefaultFontSize(false)
98     , m_fontSizeDelta(NoFontDelta)
99 {
100     init(node, propertiesToInclude);
101 }
102
103 EditingStyle::EditingStyle(const Position& position)
104     : m_shouldUseFixedDefaultFontSize(false)
105     , m_fontSizeDelta(NoFontDelta)
106 {
107     init(position.deprecatedNode(), OnlyInheritableProperties);
108 }
109
110 EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
111     : m_mutableStyle(style->copy())
112     , m_shouldUseFixedDefaultFontSize(false)
113     , m_fontSizeDelta(NoFontDelta)
114 {
115     extractFontSizeDelta();
116 }
117
118 EditingStyle::~EditingStyle()
119 {
120 }
121
122 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
123 {
124     RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node);
125     m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
126
127     if (node && node->computedStyle()) {
128         RenderStyle* renderStyle = node->computedStyle();
129         removeTextFillAndStrokeColorsIfNeeded(renderStyle);
130         replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
131     }
132
133     m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
134     extractFontSizeDelta();
135 }
136
137 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
138 {
139     // If a node's text fill color is invalid, then its children use 
140     // their font-color as their text fill color (they don't
141     // inherit it).  Likewise for stroke color.
142     ExceptionCode ec = 0;
143     if (!renderStyle->textFillColor().isValid())
144         m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor, ec);
145     if (!renderStyle->textStrokeColor().isValid())
146         m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
147     ASSERT(!ec);
148 }
149
150 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
151 {
152     ASSERT(renderStyle);
153     if (renderStyle->fontDescription().keywordSize())
154         m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
155 }
156
157 void EditingStyle::extractFontSizeDelta()
158 {
159     if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
160         // Explicit font size overrides any delta.
161         m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
162         return;
163     }
164
165     // Get the adjustment amount out of the style.
166     RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
167     if (!value || value->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE)
168         return;
169
170     CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
171
172     // Only PX handled now. If we handle more types in the future, perhaps
173     // a switch statement here would be more appropriate.
174     if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_PX)
175         return;
176
177     m_fontSizeDelta = primitiveValue->getFloatValue();
178     m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
179 }
180
181 bool EditingStyle::isEmpty() const
182 {
183     return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
184 }
185
186 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
187 {
188     if (!m_mutableStyle)
189         return false;
190
191     RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
192     if (!unicodeBidi)
193         return false;
194
195     ASSERT(unicodeBidi->isPrimitiveValue());
196     int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
197     if (unicodeBidiValue == CSSValueEmbed) {
198         RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
199         ASSERT(!direction || direction->isPrimitiveValue());
200         if (!direction)
201             return false;
202
203         writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
204
205         return true;
206     }
207
208     if (unicodeBidiValue == CSSValueNormal) {
209         writingDirection = NaturalWritingDirection;
210         return true;
211     }
212
213     return false;
214 }
215
216 void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style)
217 {
218     m_mutableStyle = style;
219     // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
220     // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
221     m_shouldUseFixedDefaultFontSize = false;
222     extractFontSizeDelta();
223 }
224
225 void EditingStyle::overrideWithStyle(const CSSMutableStyleDeclaration* style)
226 {
227     if (!style || !style->length())
228         return;
229     if (!m_mutableStyle)
230         m_mutableStyle = CSSMutableStyleDeclaration::create();
231     m_mutableStyle->merge(style);
232     extractFontSizeDelta();
233 }
234
235 void EditingStyle::clear()
236 {
237     m_mutableStyle.clear();
238     m_shouldUseFixedDefaultFontSize = false;
239     m_fontSizeDelta = NoFontDelta;
240 }
241
242 PassRefPtr<EditingStyle> EditingStyle::copy() const
243 {
244     RefPtr<EditingStyle> copy = EditingStyle::create();
245     if (m_mutableStyle)
246         copy->m_mutableStyle = m_mutableStyle->copy();
247     copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
248     copy->m_fontSizeDelta = m_fontSizeDelta;
249     return copy;
250 }
251
252 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
253 {
254     RefPtr<EditingStyle> blockProperties = EditingStyle::create();
255     if (!m_mutableStyle)
256         return blockProperties;
257
258     blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
259     m_mutableStyle->removeBlockProperties();
260
261     return blockProperties;
262 }
263
264 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
265 {
266     RefPtr<EditingStyle> textDirection = EditingStyle::create();
267     textDirection->m_mutableStyle = CSSMutableStyleDeclaration::create();
268     textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->getPropertyPriority(CSSPropertyUnicodeBidi));
269     textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
270         m_mutableStyle->getPropertyPriority(CSSPropertyDirection));
271
272     m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
273     m_mutableStyle->removeProperty(CSSPropertyDirection);
274
275     return textDirection;
276 }
277
278 void EditingStyle::removeBlockProperties()
279 {
280     if (!m_mutableStyle)
281         return;
282
283     m_mutableStyle->removeBlockProperties();
284 }
285
286 void EditingStyle::removeStyleAddedByNode(Node* node)
287 {
288     if (!node || !node->parentNode())
289         return;
290     RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
291     RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
292     parentStyle->diff(nodeStyle.get());
293     nodeStyle->diff(m_mutableStyle.get());
294 }
295
296 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
297 {
298     if (!node || !node->parentNode() || !m_mutableStyle)
299         return;
300     RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
301     RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
302     parentStyle->diff(nodeStyle.get());
303
304     CSSMutableStyleDeclaration::const_iterator end = nodeStyle->end();
305     for (CSSMutableStyleDeclaration::const_iterator it = nodeStyle->begin(); it != end; ++it)
306         m_mutableStyle->removeProperty(it->id());
307 }
308
309 void EditingStyle::removeNonEditingProperties()
310 {
311     if (m_mutableStyle)
312         m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
313 }
314
315 void EditingStyle::collapseTextDecorationProperties()
316 {
317     if (!m_mutableStyle)
318         return;
319
320     RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
321     if (!textDecorationsInEffect)
322         return;
323
324     m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->getPropertyPriority(CSSPropertyTextDecoration));
325     m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
326 }
327
328 bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, Vector<CSSPropertyID>* conflictingProperties) const
329 {
330     ASSERT(element);
331     ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
332
333     CSSMutableStyleDeclaration* inlineStyle = element->inlineStyleDecl();
334     if (!m_mutableStyle || !inlineStyle)
335         return false;
336
337     if (!conflictingProperties) {
338         CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
339         for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
340             CSSPropertyID propertyID = static_cast<CSSPropertyID>(it->id());
341
342             // We don't override whitespace property of a tab span because that would collapse the tab into a space.
343             if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
344                 continue;
345
346             if (inlineStyle->getPropertyCSSValue(propertyID))
347                 return true;
348         }
349
350         return false;
351     }
352
353     CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
354     for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
355         CSSPropertyID propertyID = static_cast<CSSPropertyID>(it->id());
356         if ((propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element)) || !inlineStyle->getPropertyCSSValue(propertyID))
357             continue;
358
359         if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection))
360             conflictingProperties->append(CSSPropertyDirection);
361
362         conflictingProperties->append(propertyID);
363     }
364
365     return !conflictingProperties->isEmpty();
366 }
367
368 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
369 {
370     if (!m_mutableStyle)
371         return;
372
373     // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
374     // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
375     // which one of editingStyleAtPosition or computedStyle is called.
376     RefPtr<EditingStyle> style = EditingStyle::create(position);
377
378     RefPtr<CSSValue> unicodeBidi;
379     RefPtr<CSSValue> direction;
380     if (shouldPreserveWritingDirection == PreserveWritingDirection) {
381         unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
382         direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
383     }
384
385     style->m_mutableStyle->diff(m_mutableStyle.get());
386
387     // if alpha value is zero, we don't add the background color.
388     RefPtr<CSSValue> backgroundColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
389     if (backgroundColor && backgroundColor->isPrimitiveValue()
390         && !alphaChannel(static_cast<CSSPrimitiveValue*>(backgroundColor.get())->getRGBA32Value())) {
391         ExceptionCode ec;
392         m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec);
393     }
394
395     if (unicodeBidi) {
396         ASSERT(unicodeBidi->isPrimitiveValue());
397         m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
398         if (direction) {
399             ASSERT(direction->isPrimitiveValue());
400             m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
401         }
402     }
403 }
404
405 PassRefPtr<EditingStyle> editingStyleIncludingTypingStyle(const Position& position)
406 {
407     RefPtr<EditingStyle> editingStyle = EditingStyle::create(position);
408     RefPtr<EditingStyle> typingStyle = position.anchorNode()->document()->frame()->selection()->typingStyle();
409     if (typingStyle && typingStyle->style())
410         editingStyle->style()->merge(copyEditingProperties(typingStyle->style()).get());
411     return editingStyle;
412 }
413     
414 }