2 * Copyright (C) 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ApplyStyleCommand.h"
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSProperty.h"
31 #include "CSSPropertyNames.h"
33 #include "HTMLElement.h"
34 #include "HTMLInterchange.h"
35 #include "HTMLNames.h"
38 #include "RenderObject.h"
40 #include "cssparser.h"
41 #include "htmlediting.h"
45 using namespace HTMLNames;
49 enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles };
51 explicit StyleChange(CSSStyleDeclaration *, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
52 StyleChange(CSSStyleDeclaration *, const Position &, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles);
54 static ELegacyHTMLStyles styleModeForParseMode(bool);
56 String cssStyle() const { return m_cssStyle; }
57 bool applyBold() const { return m_applyBold; }
58 bool applyItalic() const { return m_applyItalic; }
59 bool applyFontColor() const { return m_applyFontColor.length() > 0; }
60 bool applyFontFace() const { return m_applyFontFace.length() > 0; }
61 bool applyFontSize() const { return m_applyFontSize.length() > 0; }
63 String fontColor() { return m_applyFontColor; }
64 String fontFace() { return m_applyFontFace; }
65 String fontSize() { return m_applyFontSize; }
67 bool usesLegacyStyles() const { return m_usesLegacyStyles; }
70 void init(PassRefPtr<CSSStyleDeclaration>, const Position &);
71 bool checkForLegacyHTMLStyleChange(const CSSProperty *);
72 static bool currentlyHasStyle(const Position &, const CSSProperty *);
77 String m_applyFontColor;
78 String m_applyFontFace;
79 String m_applyFontSize;
80 bool m_usesLegacyStyles;
85 StyleChange::StyleChange(CSSStyleDeclaration *style, ELegacyHTMLStyles usesLegacyStyles)
86 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
88 init(style, Position());
91 StyleChange::StyleChange(CSSStyleDeclaration *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
92 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
94 init(style, position);
97 void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &position)
99 RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
101 String styleText("");
103 DeprecatedValueListConstIterator<CSSProperty> end;
104 for (DeprecatedValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
105 const CSSProperty *property = &*it;
107 // If position is empty or the position passed in already has the
108 // style, just move on.
109 if (position.isNotNull() && currentlyHasStyle(position, property))
112 // If needed, figure out if this change is a legacy HTML style change.
113 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
118 if (property->id() == CSS_PROP__WEBKIT_TEXT_DECORATIONS_IN_EFFECT) {
119 // we have to special-case text decorations
120 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
121 styleText += alteredProperty.cssText();
123 styleText += property->cssText();
126 // Save the result for later
127 m_cssStyle = styleText.deprecatedString().stripWhiteSpace();
130 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
132 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
135 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
137 if (!property || !property->value()) {
141 String valueText(property->value()->cssText());
142 switch (property->id()) {
143 case CSS_PROP_FONT_WEIGHT:
144 if (equalIgnoringCase(valueText, "bold")) {
149 case CSS_PROP_FONT_STYLE:
150 if (equalIgnoringCase(valueText, "italic") || equalIgnoringCase(valueText, "oblique")) {
151 m_applyItalic = true;
155 case CSS_PROP_COLOR: {
156 Color color(CSSParser::parseColor(valueText));
157 m_applyFontColor = color.name();
160 case CSS_PROP_FONT_FAMILY:
161 m_applyFontFace = valueText;
163 case CSS_PROP_FONT_SIZE:
164 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
165 CSSPrimitiveValue *value = static_cast<CSSPrimitiveValue *>(property->value());
166 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
168 m_applyFontSize = "1";
169 else if (number <= 10)
170 m_applyFontSize = "2";
171 else if (number <= 13)
172 m_applyFontSize = "3";
173 else if (number <= 16)
174 m_applyFontSize = "4";
175 else if (number <= 18)
176 m_applyFontSize = "5";
177 else if (number <= 24)
178 m_applyFontSize = "6";
180 m_applyFontSize = "7";
181 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
182 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
183 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
187 // Can't make sense of the number. Put no font size.
194 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
196 ASSERT(pos.isNotNull());
197 RefPtr<CSSComputedStyleDeclaration> style = pos.computedStyle();
198 RefPtr<CSSValue> value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
201 return equalIgnoringCase(value->cssText(), property->value()->cssText());
204 static String &styleSpanClassString()
206 static String styleSpanClassString = AppleStyleSpanClass;
207 return styleSpanClassString;
210 bool isStyleSpan(const Node *node)
212 if (!node || !node->isHTMLElement())
215 const HTMLElement *elem = static_cast<const HTMLElement *>(node);
216 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
219 static bool isEmptyStyleSpan(const Node *node)
221 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
224 const HTMLElement *elem = static_cast<const HTMLElement *>(node);
225 CSSMutableStyleDeclaration *inlineStyleDecl = elem->inlineStyleDecl();
226 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(classAttr) == styleSpanClassString();
229 static bool isEmptyFontTag(const Node *node)
231 if (!node || !node->hasTagName(fontTag))
234 const Element *elem = static_cast<const Element *>(node);
235 NamedAttrMap *map = elem->attributes(true); // true for read-only
236 return (!map || map->length() == 1) && elem->getAttribute(classAttr) == styleSpanClassString();
239 static PassRefPtr<Element> createFontElement(Document* document)
241 ExceptionCode ec = 0;
242 RefPtr<Element> fontNode = document->createElementNS(xhtmlNamespaceURI, "font", ec);
244 fontNode->setAttribute(classAttr, styleSpanClassString());
245 return fontNode.release();
248 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
250 ExceptionCode ec = 0;
251 RefPtr<Element> styleElement = document->createElementNS(xhtmlNamespaceURI, "span", ec);
253 styleElement->setAttribute(classAttr, styleSpanClassString());
254 return static_pointer_cast<HTMLElement>(styleElement.release());
257 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel)
258 : CompositeEditCommand(document)
259 , m_style(style->makeMutable())
260 , m_editingAction(editingAction)
261 , m_propertyLevel(propertyLevel)
262 , m_start(endingSelection().start().downstream())
263 , m_end(endingSelection().end().upstream())
264 , m_useEndingSelection(true)
265 , m_styledInlineElement(0)
266 , m_removeOnly(false)
270 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
271 : CompositeEditCommand(document)
272 , m_style(style->makeMutable())
273 , m_editingAction(editingAction)
274 , m_propertyLevel(propertyLevel)
277 , m_useEndingSelection(false)
278 , m_styledInlineElement(0)
279 , m_removeOnly(false)
283 ApplyStyleCommand::ApplyStyleCommand(Document* document, Element* element, bool removeOnly, EditAction editingAction)
284 : CompositeEditCommand(document)
285 , m_style(new CSSMutableStyleDeclaration())
286 , m_editingAction(editingAction)
287 , m_propertyLevel(PropertyDefault)
288 , m_start(endingSelection().start().downstream())
289 , m_end(endingSelection().end().upstream())
290 , m_useEndingSelection(true)
291 , m_styledInlineElement(element)
292 , m_removeOnly(removeOnly)
296 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
298 ASSERT(Range::compareBoundaryPoints(newEnd, newStart) >= 0);
300 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
301 m_useEndingSelection = true;
303 setEndingSelection(Selection(newStart, newEnd, VP_DEFAULT_AFFINITY));
308 Position ApplyStyleCommand::startPosition()
310 if (m_useEndingSelection)
311 return endingSelection().start();
316 Position ApplyStyleCommand::endPosition()
318 if (m_useEndingSelection)
319 return endingSelection().end();
324 void ApplyStyleCommand::doApply()
326 switch (m_propertyLevel) {
327 case PropertyDefault: {
328 // apply the block-centric properties of the style
329 RefPtr<CSSMutableStyleDeclaration> blockStyle = m_style->copyBlockProperties();
330 applyBlockStyle(blockStyle.get());
331 // apply any remaining styles to the inline elements
332 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
333 if (blockStyle->length() < m_style->length() || m_styledInlineElement) {
334 RefPtr<CSSMutableStyleDeclaration> inlineStyle = m_style->copy();
335 applyRelativeFontStyleChange(inlineStyle.get());
336 blockStyle->diff(inlineStyle.get());
337 applyInlineStyle(inlineStyle.get());
341 case ForceBlockProperties:
342 // Force all properties to be applied as block styles.
343 applyBlockStyle(m_style.get());
348 EditAction ApplyStyleCommand::editingAction() const
350 return m_editingAction;
353 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style)
355 // update document layout once before removing styles
356 // so that we avoid the expense of updating before each and every call
357 // to check a computed style
360 // get positions we want to use for applying style
361 Position start = startPosition();
362 Position end = endPosition();
363 if (Range::compareBoundaryPoints(end, start) < 0) {
364 Position swap = start;
369 // remove current values, if any, of the specified styles from the blocks
370 // NOTE: tracks the previous block to avoid repeated processing
371 // Also, gather up all the nodes we want to process in a DeprecatedPtrList before
372 // doing anything. This averts any bugs iterating over these nodes
373 // once you start removing and applying style.
374 Node *beyondEnd = end.node()->traverseNextNode();
375 DeprecatedPtrList<Node> nodes;
376 for (Node *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
380 for (DeprecatedPtrListIterator<Node> it(nodes); it.current(); ++it) {
381 Node *block = it.current()->enclosingBlockFlowElement();
382 if (block != prevBlock && block->isHTMLElement()) {
383 removeCSSStyle(style, static_cast<HTMLElement *>(block));
391 // apply specified styles to the block flow elements in the selected range
393 for (DeprecatedPtrListIterator<Node> it(nodes); it.current(); ++it) {
394 Node *node = it.current();
395 if (node->renderer()) {
396 Node *block = node->enclosingBlockFlowElement();
397 if (block != prevBlock) {
398 addBlockStyleIfNeeded(style, node);
405 #define NoFontDelta (0.0f)
406 #define MinimumFontSize (0.1f)
408 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration *style)
410 RefPtr<CSSValue> value = style->getPropertyCSSValue(CSS_PROP_FONT_SIZE);
412 // Explicit font size overrides any delta.
413 style->removeProperty(CSS_PROP__WEBKIT_FONT_SIZE_DELTA);
417 // Get the adjustment amount out of the style.
418 value = style->getPropertyCSSValue(CSS_PROP__WEBKIT_FONT_SIZE_DELTA);
421 float adjustment = NoFontDelta;
422 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
423 CSSPrimitiveValue *primitiveValue = static_cast<CSSPrimitiveValue *>(value.get());
424 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
425 // Only PX handled now. If we handle more types in the future, perhaps
426 // a switch statement here would be more appropriate.
427 adjustment = primitiveValue->getFloatValue();
430 style->removeProperty(CSS_PROP__WEBKIT_FONT_SIZE_DELTA);
431 if (adjustment == NoFontDelta)
434 Position start = startPosition();
435 Position end = endPosition();
436 if (Range::compareBoundaryPoints(end, start) < 0) {
437 Position swap = start;
442 // Join up any adjacent text nodes.
443 if (start.node()->isTextNode()) {
444 joinChildTextNodes(start.node()->parentNode(), start, end);
445 start = startPosition();
448 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
449 joinChildTextNodes(end.node()->parentNode(), start, end);
450 start = startPosition();
454 // Split the start text nodes if needed to apply style.
455 bool splitStart = splitTextAtStartIfNeeded(start, end);
457 start = startPosition();
460 bool splitEnd = splitTextAtEndIfNeeded(start, end);
462 start = startPosition();
466 Node *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
467 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
468 Node *startNode = start.node();
469 if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
470 startNode = startNode->traverseNextNode();
472 // Store away font size before making any changes to the document.
473 // This ensures that changes to one node won't effect another.
474 HashMap<Node*, float> startingFontSizes;
475 for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode())
476 startingFontSizes.set(node, computedFontSize(node));
478 // These spans were added by us. If empty after font size changes, they can be removed.
479 DeprecatedPtrList<Node> emptySpans;
481 Node *lastStyledNode = 0;
482 for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
483 HTMLElement *elem = 0;
484 if (node->isHTMLElement()) {
485 // Only work on fully selected nodes.
486 if (!nodeFullySelected(node, start, end))
488 elem = static_cast<HTMLElement *>(node);
489 } else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
490 // Last styled node was not parent node of this text node, but we wish to style this
491 // text node. To make this possible, add a style span to surround this text node.
492 RefPtr<HTMLElement> span = createStyleSpanElement(document());
493 insertNodeBefore(span.get(), node);
494 surroundNodeRangeWithElement(node, node, span.get());
497 // Only handle HTML elements and text nodes.
500 lastStyledNode = node;
502 CSSMutableStyleDeclaration* inlineStyleDecl = elem->getInlineStyleDecl();
503 float currentFontSize = computedFontSize(node);
504 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + adjustment);
505 RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE);
507 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
508 currentFontSize = computedFontSize(node);
510 if (currentFontSize != desiredFontSize) {
511 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, String::number(desiredFontSize) + "px", false, false);
512 setNodeAttribute(elem, styleAttr, inlineStyleDecl->cssText());
514 if (inlineStyleDecl->length() == 0) {
515 removeNodeAttribute(elem, styleAttr);
516 if (isEmptyStyleSpan(elem))
517 emptySpans.append(elem);
521 for (DeprecatedPtrListIterator<Node> it(emptySpans); it.current(); ++it)
522 removeNodePreservingChildren(it.current());
526 #undef MinimumFontSize
528 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
530 // update document layout once before removing styles
531 // so that we avoid the expense of updating before each and every call
532 // to check a computed style
535 // adjust to the positions we want to use for applying style
536 Position start = startPosition();
537 Position end = endPosition();
538 if (Range::compareBoundaryPoints(end, start) < 0) {
539 Position swap = start;
544 // split the start node and containing element if the selection starts inside of it
545 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
547 start = startPosition();
551 // split the end node and containing element if the selection ends inside of it
552 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
554 start = startPosition();
558 // Remove style from the selection.
559 // Use the upstream position of the start for removing style.
560 // This will ensure we remove all traces of the relevant styles from the selection
561 // and prevent us from adding redundant ones, as described in:
562 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
563 removeInlineStyle(style, start.upstream(), end);
564 start = startPosition();
568 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
570 start = startPosition();
576 mergeEndWithNextIfIdentical(start, end);
577 start = startPosition();
581 // update document layout once before running the rest of the function
582 // so that we avoid the expense of updating before each and every call
583 // to check a computed style
586 Node* node = start.node();
587 Node* endNode = end.node();
589 if (start.offset() >= start.node()->caretMaxOffset())
590 node = node->traverseNextNode();
592 if (end.node()->renderer()->isTable() && end.offset() == maxDeepOffset(end.node()))
593 endNode = end.node()->lastDescendant();
595 if (start.node() == endNode) {
596 addInlineStyleIfNeeded(style, node, node);
599 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline() && node->isContentRichlyEditable()) {
600 Node *runStart = node;
602 Node *next = node->traverseNextNode();
603 // Break if node is the end node, or if the next node does not fit in with
604 // the current group.
605 if (node == endNode ||
606 runStart->parentNode() != next->parentNode() ||
607 (next->isElementNode() && !next->hasTagName(brTag)) ||
608 (next->renderer() && !next->renderer()->isInline()))
612 // Now apply style to the run we found.
613 addInlineStyleIfNeeded(style, runStart, node);
617 node = node->traverseNextNode();
621 if (splitStart || splitEnd) {
622 cleanUpEmptyStyleSpans(start, end);
626 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration *style, HTMLElement *elem)
628 DeprecatedValueListConstIterator<CSSProperty> end;
629 for (DeprecatedValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
630 switch ((*it).id()) {
631 case CSS_PROP_FONT_WEIGHT:
632 if (elem->hasLocalName(bTag))
635 case CSS_PROP_FONT_STYLE:
636 if (elem->hasLocalName(iTag))
644 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElement *elem)
646 // This node can be removed.
647 // EDIT FIXME: This does not handle the case where the node
648 // has attributes. But how often do people add attributes to <B> tags?
649 // Not so often I think.
651 removeNodePreservingChildren(elem);
654 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
659 if (!elem->hasLocalName(fontTag))
662 ExceptionCode ec = 0;
663 DeprecatedValueListConstIterator<CSSProperty> end;
664 for (DeprecatedValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
665 switch ((*it).id()) {
667 elem->removeAttribute(colorAttr, ec);
670 case CSS_PROP_FONT_FAMILY:
671 elem->removeAttribute(faceAttr, ec);
674 case CSS_PROP_FONT_SIZE:
675 elem->removeAttribute(sizeAttr, ec);
681 if (isEmptyFontTag(elem))
682 removeNodePreservingChildren(elem);
685 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
690 CSSMutableStyleDeclaration *decl = elem->inlineStyleDecl();
694 DeprecatedValueListConstIterator<CSSProperty> end;
695 for (DeprecatedValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
696 int propertyID = (*it).id();
697 RefPtr<CSSValue> value = decl->getPropertyCSSValue(propertyID);
698 if (value && (propertyID != CSS_PROP_WHITE_SPACE || !isTabSpanNode(elem)))
699 removeCSSProperty(decl, propertyID);
702 if (isEmptyStyleSpan(elem))
703 removeNodePreservingChildren(elem);
706 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclaration *style, const Position &start, const Position &end)
708 ASSERT(start.isNotNull());
709 ASSERT(end.isNotNull());
710 ASSERT(start.node()->inDocument());
711 ASSERT(end.node()->inDocument());
712 ASSERT(Range::compareBoundaryPoints(start, end) <= 0);
716 static bool hasTextDecorationProperty(Node *node)
718 if (!node->isElementNode())
721 Element *element = static_cast<Element *>(node);
722 CSSComputedStyleDeclaration style(element);
723 RefPtr<CSSValue> value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
724 return value && !equalIgnoringCase(value->cssText(), "none");
727 static Node* highestAncestorWithTextDecoration(Node *node)
731 for (Node *n = node; n; n = n->parentNode()) {
732 if (hasTextDecorationProperty(n))
739 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractTextDecorationStyle(Node* node)
742 ASSERT(node->isElementNode());
744 // non-html elements not handled yet
745 if (!node->isHTMLElement())
748 HTMLElement *element = static_cast<HTMLElement *>(node);
749 RefPtr<CSSMutableStyleDeclaration> style = element->inlineStyleDecl();
753 int properties[1] = { CSS_PROP_TEXT_DECORATION };
754 RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = style->copyPropertiesInSet(properties, 1);
756 RefPtr<CSSValue> property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
757 if (property && !equalIgnoringCase(property->cssText(), "none"))
758 removeCSSProperty(style.get(), CSS_PROP_TEXT_DECORATION);
760 return textDecorationStyle.release();
763 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node *node)
766 ASSERT(node->isElementNode());
768 // non-html elements not handled yet
769 if (!node->isHTMLElement())
772 HTMLElement *element = static_cast<HTMLElement *>(node);
773 RefPtr<CSSComputedStyleDeclaration> computedStyle = new CSSComputedStyleDeclaration(element);
774 ASSERT(computedStyle);
776 int properties[1] = { CSS_PROP_TEXT_DECORATION };
777 RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
779 RefPtr<CSSValue> property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
780 if (property && !equalIgnoringCase(property->cssText(), "none")) {
781 RefPtr<CSSMutableStyleDeclaration> newStyle = textDecorationStyle->copy();
782 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
783 applyTextDecorationStyle(node, newStyle.get());
786 return textDecorationStyle.release();
789 void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDeclaration *style)
793 if (!style || !style->cssText().length())
796 if (node->isTextNode()) {
797 RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document());
798 insertNodeBefore(styleSpan.get(), node);
799 surroundNodeRangeWithElement(node, node, styleSpan.get());
800 node = styleSpan.get();
803 if (!node->isElementNode())
806 HTMLElement *element = static_cast<HTMLElement *>(node);
808 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
809 if (styleChange.cssStyle().length() > 0) {
810 String cssText = styleChange.cssStyle();
811 CSSMutableStyleDeclaration *decl = element->inlineStyleDecl();
813 cssText += decl->cssText();
814 setNodeAttribute(element, styleAttr, cssText);
818 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node *node, const Position &start, const Position &end, bool force)
820 Node *highestAncestor = highestAncestorWithTextDecoration(node);
822 if (highestAncestor) {
825 for (Node *current = highestAncestor; current != node; current = nextCurrent) {
830 RefPtr<CSSMutableStyleDeclaration> decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
832 for (Node *child = current->firstChild(); child; child = nextChild) {
833 nextChild = child->nextSibling();
837 } else if (node->isAncestor(child)) {
838 applyTextDecorationStyle(child, decoration.get());
841 applyTextDecorationStyle(child, decoration.get());
848 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
850 // We need to work in two passes. First we push down any inline
851 // styles that set text decoration. Then we look for any remaining
852 // styles (caused by stylesheets) and explicitly negate text
853 // decoration while pushing down.
855 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
857 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
859 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
861 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
864 static int maxRangeOffset(Node *n)
866 if (n->offsetInCharacters())
867 return n->maxOffset();
869 if (n->isElementNode())
870 return n->childNodeCount();
875 void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style, const Position &start, const Position &end)
877 ASSERT(start.isNotNull());
878 ASSERT(end.isNotNull());
879 ASSERT(start.node()->inDocument());
880 ASSERT(end.node()->inDocument());
881 ASSERT(Range::compareBoundaryPoints(start, end) <= 0);
883 RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__WEBKIT_TEXT_DECORATIONS_IN_EFFECT);
885 if (textDecorationSpecialProperty) {
886 pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
887 style = style->copy();
888 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__WEBKIT_TEXT_DECORATIONS_IN_EFFECT));
891 // The s and e variables store the positions used to set the ending selection after style removal
892 // takes place. This will help callers to recognize when either the start node or the end node
893 // are removed from the document during the work of this function.
897 Node *node = start.node();
899 Node *next = node->traverseNextNode();
900 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
901 HTMLElement *elem = static_cast<HTMLElement *>(node);
902 Node *prev = elem->traversePreviousNodePostOrder();
903 Node *next = elem->traverseNextNode();
904 if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName()))
905 removeNodePreservingChildren(elem);
906 if (isHTMLStyleNode(style.get(), elem))
907 removeHTMLStyleNode(elem);
909 removeHTMLFontStyle(style.get(), elem);
910 removeCSSStyle(style.get(), elem);
912 if (!elem->inDocument()) {
913 if (s.node() == elem) {
914 // Since elem must have been fully selected, and it is at the start
915 // of the selection, it is clear we can set the new s offset to 0.
916 ASSERT(s.offset() <= s.node()->caretMinOffset());
917 s = Position(next, 0);
919 if (e.node() == elem) {
920 // Since elem must have been fully selected, and it is at the end
921 // of the selection, it is clear we can set the new e offset to
922 // the max range offset of prev.
923 ASSERT(e.offset() >= maxRangeOffset(e.node()));
924 e = Position(prev, maxRangeOffset(prev));
928 if (node == end.node())
933 ASSERT(s.node()->inDocument());
934 ASSERT(e.node()->inDocument());
935 updateStartEnd(s, e);
938 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
941 ASSERT(node->isElementNode());
943 Position pos = Position(node, node->childNodeCount()).upstream();
944 return Range::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
945 Range::compareBoundaryPoints(pos, end) <= 0;
948 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
951 ASSERT(node->isElementNode());
953 Position pos = Position(node, node->childNodeCount()).upstream();
954 bool isFullyBeforeStart = Range::compareBoundaryPoints(pos, start) < 0;
955 bool isFullyAfterEnd = Range::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
957 return isFullyBeforeStart || isFullyAfterEnd;
961 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
963 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
964 int endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
965 Text *text = static_cast<Text *>(start.node());
966 splitTextNode(text, start.offset());
967 updateStartEnd(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment));
973 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
975 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
976 Text *text = static_cast<Text *>(end.node());
977 splitTextNode(text, end.offset());
979 Node *prevNode = text->previousSibling();
981 Node *startNode = start.node() == end.node() ? prevNode : start.node();
983 updateStartEnd(Position(startNode, start.offset()), Position(prevNode, prevNode->caretMaxOffset()));
989 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
991 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
992 int endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
993 Text *text = static_cast<Text *>(start.node());
994 splitTextNodeContainingElement(text, start.offset());
996 updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.offset() - endOffsetAdjustment));
1002 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1004 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
1005 Text *text = static_cast<Text *>(end.node());
1006 splitTextNodeContainingElement(text, end.offset());
1008 Node *prevNode = text->parent()->previousSibling()->lastChild();
1010 Node *startNode = start.node() == end.node() ? prevNode : start.node();
1012 updateStartEnd(Position(startNode, start.offset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1));
1018 static bool areIdenticalElements(Node *first, Node *second)
1020 // check that tag name and all attribute names and values are identical
1022 if (!first->isElementNode())
1025 if (!second->isElementNode())
1028 Element *firstElement = static_cast<Element *>(first);
1029 Element *secondElement = static_cast<Element *>(second);
1031 if (!firstElement->tagQName().matches(secondElement->tagQName()))
1034 NamedAttrMap *firstMap = firstElement->attributes();
1035 NamedAttrMap *secondMap = secondElement->attributes();
1037 unsigned firstLength = firstMap->length();
1039 if (firstLength != secondMap->length())
1042 for (unsigned i = 0; i < firstLength; i++) {
1043 Attribute *attribute = firstMap->attributeItem(i);
1044 Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name());
1046 if (!secondAttribute || attribute->value() != secondAttribute->value())
1053 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
1055 Node *startNode = start.node();
1056 int startOffset = start.offset();
1058 if (isAtomicNode(start.node())) {
1059 if (start.offset() != 0)
1062 if (start.node()->previousSibling())
1065 startNode = start.node()->parent();
1069 if (!startNode->isElementNode())
1072 if (startOffset != 0)
1075 Node *previousSibling = startNode->previousSibling();
1077 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1078 Element *previousElement = static_cast<Element *>(previousSibling);
1079 Element *element = static_cast<Element *>(startNode);
1080 Node *startChild = element->firstChild();
1082 mergeIdenticalElements(previousElement, element);
1084 int startOffsetAdjustment = startChild->nodeIndex();
1085 int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
1086 updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.offset() + endOffsetAdjustment));
1093 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
1095 Node *endNode = end.node();
1096 int endOffset = end.offset();
1098 if (isAtomicNode(endNode)) {
1099 if (endOffset < endNode->caretMaxOffset())
1102 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
1103 if (end.node()->nextSibling())
1106 endNode = end.node()->parent();
1107 endOffset = parentLastOffset;
1110 if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1113 Node *nextSibling = endNode->nextSibling();
1115 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1116 Element *nextElement = static_cast<Element *>(nextSibling);
1117 Element *element = static_cast<Element *>(endNode);
1118 Node *nextChild = nextElement->firstChild();
1120 mergeIdenticalElements(element, nextElement);
1122 Node *startNode = start.node() == endNode ? nextElement : start.node();
1125 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1126 updateStartEnd(Position(startNode, start.offset()), Position(nextElement, endOffset));
1133 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
1136 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
1139 if (node && isEmptyStyleSpan(node->previousSibling())) {
1140 removeNodePreservingChildren(node->previousSibling());
1143 if (start.node() == end.node()) {
1144 if (start.node()->isTextNode()) {
1145 for (Node *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1146 if (isEmptyStyleSpan(cur)) {
1147 removeNodePreservingChildren(cur);
1154 if (start.node()->isTextNode()) {
1155 for (Node *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
1156 if (isEmptyStyleSpan(cur)) {
1157 removeNodePreservingChildren(cur);
1163 if (end.node()->isTextNode()) {
1164 for (Node *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
1165 if (isEmptyStyleSpan(cur)) {
1166 removeNodePreservingChildren(cur);
1173 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
1175 if (node && isEmptyStyleSpan(node->nextSibling())) {
1176 removeNodePreservingChildren(node->nextSibling());
1180 void ApplyStyleCommand::surroundNodeRangeWithElement(Node *startNode, Node *endNode, Element *element)
1186 Node *node = startNode;
1188 Node *next = node->traverseNextNode();
1189 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1191 appendNode(node, element);
1193 if (node == endNode)
1199 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *node)
1201 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1206 HTMLElement *block = static_cast<HTMLElement *>(node->enclosingBlockFlowElement());
1210 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1211 if (styleChange.cssStyle().length() > 0) {
1212 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
1213 block = static_cast<HTMLElement *>(node->enclosingBlockFlowElement());
1214 String cssText = styleChange.cssStyle();
1215 CSSMutableStyleDeclaration *decl = block->inlineStyleDecl();
1217 cssText += decl->cssText();
1218 setNodeAttribute(block, styleAttr, cssText);
1222 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode)
1227 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1228 ExceptionCode ec = 0;
1231 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1233 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1234 RefPtr<Element> fontElement = createFontElement(document());
1236 insertNodeBefore(fontElement.get(), startNode);
1237 if (styleChange.applyFontColor())
1238 fontElement->setAttribute(colorAttr, styleChange.fontColor());
1239 if (styleChange.applyFontFace())
1240 fontElement->setAttribute(faceAttr, styleChange.fontFace());
1241 if (styleChange.applyFontSize())
1242 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
1243 surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1246 if (styleChange.cssStyle().length() > 0) {
1247 RefPtr<Element> styleElement = createStyleSpanElement(document());
1248 styleElement->setAttribute(styleAttr, styleChange.cssStyle());
1249 insertNodeBefore(styleElement.get(), startNode);
1250 surroundNodeRangeWithElement(startNode, endNode, styleElement.get());
1253 if (styleChange.applyBold()) {
1254 RefPtr<Element> boldElement = document()->createElementNS(xhtmlNamespaceURI, "b", ec);
1256 insertNodeBefore(boldElement.get(), startNode);
1257 surroundNodeRangeWithElement(startNode, endNode, boldElement.get());
1260 if (styleChange.applyItalic()) {
1261 RefPtr<Element> italicElement = document()->createElementNS(xhtmlNamespaceURI, "i", ec);
1263 insertNodeBefore(italicElement.get(), startNode);
1264 surroundNodeRangeWithElement(startNode, endNode, italicElement.get());
1267 if (m_styledInlineElement) {
1268 RefPtr<Element> clonedElement = static_pointer_cast<Element>(m_styledInlineElement->cloneNode(false));
1269 insertNodeBefore(clonedElement.get(), startNode);
1270 surroundNodeRangeWithElement(startNode, endNode, clonedElement.get());
1274 float ApplyStyleCommand::computedFontSize(const Node *node)
1279 Position pos(const_cast<Node *>(node), 0);
1280 RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
1284 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
1288 return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1291 void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, const Position &end)
1296 Position newStart = start;
1297 Position newEnd = end;
1299 Node *child = node->firstChild();
1301 Node *next = child->nextSibling();
1302 if (child->isTextNode() && next && next->isTextNode()) {
1303 Text *childText = static_cast<Text *>(child);
1304 Text *nextText = static_cast<Text *>(next);
1305 if (next == start.node())
1306 newStart = Position(childText, childText->length() + start.offset());
1307 if (next == end.node())
1308 newEnd = Position(childText, childText->length() + end.offset());
1309 String textToMove = nextText->data();
1310 insertTextIntoNode(childText, childText->length(), textToMove);
1312 // don't move child node pointer. it may want to merge with more text nodes.
1315 child = child->nextSibling();
1319 updateStartEnd(newStart, newEnd);