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