2 * Copyright (C) 2005, 2006, 2008, 2009 Apple 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 "CSSMutableStyleDeclaration.h"
31 #include "CSSParser.h"
32 #include "CSSProperty.h"
33 #include "CSSPropertyNames.h"
34 #include "CSSStyleSelector.h"
35 #include "CSSValueKeywords.h"
36 #include "CSSValueList.h"
38 #include "EditingStyle.h"
41 #include "HTMLFontElement.h"
42 #include "HTMLInterchange.h"
43 #include "HTMLNames.h"
46 #include "RenderObject.h"
47 #include "RenderText.h"
49 #include "TextIterator.h"
50 #include "htmlediting.h"
51 #include "visible_units.h"
52 #include <wtf/StdLibExtras.h>
56 using namespace HTMLNames;
58 static String& styleSpanClassString()
60 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
61 return styleSpanClassString;
64 bool isLegacyAppleStyleSpan(const Node *node)
66 if (!node || !node->isHTMLElement())
69 const HTMLElement* elem = static_cast<const HTMLElement*>(node);
70 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
73 enum ShouldStyleAttributeBeEmpty { AllowNonEmptyStyleAttribute, StyleAttributeShouldBeEmpty };
74 static bool hasNoAttributeOrOnlyStyleAttribute(const StyledElement* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
76 const bool readonly = true;
77 NamedNodeMap* map = element->attributes(readonly);
78 if (!map || map->isEmpty())
81 unsigned matchedAttributes = 0;
82 if (element->fastGetAttribute(classAttr) == styleSpanClassString())
84 if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute
85 || !element->inlineStyleDecl() || element->inlineStyleDecl()->isEmpty()))
88 ASSERT(matchedAttributes <= map->length());
89 return matchedAttributes == map->length();
92 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
94 if (!element || !element->hasTagName(spanTag))
96 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute);
99 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
101 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
103 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty);
106 static bool isEmptyFontTag(const Node *node)
108 if (!node || !node->hasTagName(fontTag))
111 const Element *elem = static_cast<const Element *>(node);
112 NamedNodeMap *map = elem->attributes(true); // true for read-only
115 return map->isEmpty() || (map->length() == 1 && elem->getAttribute(classAttr) == styleSpanClassString());
118 static PassRefPtr<Element> createFontElement(Document* document)
120 RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
121 return fontNode.release();
124 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
126 RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
127 return styleElement.release();
130 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
131 : CompositeEditCommand(document)
132 , m_style(style->copy())
133 , m_editingAction(editingAction)
134 , m_propertyLevel(propertyLevel)
135 , m_start(endingSelection().start().downstream())
136 , m_end(endingSelection().end().upstream())
137 , m_useEndingSelection(true)
138 , m_styledInlineElement(0)
139 , m_removeOnly(false)
140 , m_isInlineElementToRemoveFunction(0)
144 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
145 : CompositeEditCommand(document)
146 , m_style(style->copy())
147 , m_editingAction(editingAction)
148 , m_propertyLevel(propertyLevel)
151 , m_useEndingSelection(false)
152 , m_styledInlineElement(0)
153 , m_removeOnly(false)
154 , m_isInlineElementToRemoveFunction(0)
158 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
159 : CompositeEditCommand(element->document())
160 , m_style(EditingStyle::create())
161 , m_editingAction(editingAction)
162 , m_propertyLevel(PropertyDefault)
163 , m_start(endingSelection().start().downstream())
164 , m_end(endingSelection().end().upstream())
165 , m_useEndingSelection(true)
166 , m_styledInlineElement(element)
167 , m_removeOnly(removeOnly)
168 , m_isInlineElementToRemoveFunction(0)
172 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
173 : CompositeEditCommand(document)
174 , m_style(style->copy())
175 , m_editingAction(editingAction)
176 , m_propertyLevel(PropertyDefault)
177 , m_start(endingSelection().start().downstream())
178 , m_end(endingSelection().end().upstream())
179 , m_useEndingSelection(true)
180 , m_styledInlineElement(0)
182 , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
186 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
188 ASSERT(comparePositions(newEnd, newStart) >= 0);
190 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
191 m_useEndingSelection = true;
193 setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional()));
198 Position ApplyStyleCommand::startPosition()
200 if (m_useEndingSelection)
201 return endingSelection().start();
206 Position ApplyStyleCommand::endPosition()
208 if (m_useEndingSelection)
209 return endingSelection().end();
214 void ApplyStyleCommand::doApply()
216 switch (m_propertyLevel) {
217 case PropertyDefault: {
218 // Apply the block-centric properties of the style.
219 RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
220 if (!blockStyle->isEmpty())
221 applyBlockStyle(blockStyle.get());
222 // Apply any remaining styles to the inline elements.
223 if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
224 applyRelativeFontStyleChange(m_style.get());
225 applyInlineStyle(m_style.get());
229 case ForceBlockProperties:
230 // Force all properties to be applied as block styles.
231 applyBlockStyle(m_style.get());
236 EditAction ApplyStyleCommand::editingAction() const
238 return m_editingAction;
241 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
243 // update document layout once before removing styles
244 // so that we avoid the expense of updating before each and every call
245 // to check a computed style
248 // get positions we want to use for applying style
249 Position start = startPosition();
250 Position end = endPosition();
251 if (comparePositions(end, start) < 0) {
252 Position swap = start;
257 VisiblePosition visibleStart(start);
258 VisiblePosition visibleEnd(end);
260 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
263 // Save and restore the selection endpoints using their indices in the document, since
264 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
265 // Calculate start and end indices from the start of the tree that they're in.
266 Node* scope = highestAncestor(visibleStart.deepEquivalent().deprecatedNode());
267 RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
268 RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
269 int startIndex = TextIterator::rangeLength(startRange.get(), true);
270 int endIndex = TextIterator::rangeLength(endRange.get(), true);
272 VisiblePosition paragraphStart(startOfParagraph(visibleStart));
273 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
274 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
275 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
276 StyleChange styleChange(style, paragraphStart.deepEquivalent());
277 if (styleChange.cssStyle().length() || m_removeOnly) {
278 RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
280 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
284 ASSERT(!block || block->isHTMLElement());
285 if (block && block->isHTMLElement()) {
286 removeCSSStyle(style, toHTMLElement(block.get()));
288 addBlockStyle(styleChange, toHTMLElement(block.get()));
291 if (nextParagraphStart.isOrphan())
292 nextParagraphStart = endOfParagraph(paragraphStart).next();
295 paragraphStart = nextParagraphStart;
296 nextParagraphStart = endOfParagraph(paragraphStart).next();
299 startRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), startIndex, 0, true);
300 endRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), endIndex, 0, true);
301 if (startRange && endRange)
302 updateStartEnd(startRange->startPosition(), endRange->startPosition());
305 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
307 static const float MinimumFontSize = 0.1f;
309 if (!style || !style->hasFontSizeDelta())
312 Position start = startPosition();
313 Position end = endPosition();
314 if (comparePositions(end, start) < 0) {
315 Position swap = start;
320 // Join up any adjacent text nodes.
321 if (start.deprecatedNode()->isTextNode()) {
322 joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
323 start = startPosition();
326 if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
327 joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
328 start = startPosition();
332 // Split the start text nodes if needed to apply style.
333 if (isValidCaretPositionInTextNode(start)) {
334 splitTextAtStart(start, end);
335 start = startPosition();
339 if (isValidCaretPositionInTextNode(end)) {
340 splitTextAtEnd(start, end);
341 start = startPosition();
345 // Calculate loop end point.
346 // If the end node is before the start node (can only happen if the end node is
347 // an ancestor of the start node), we gather nodes up to the next sibling of the end node
349 if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
350 beyondEnd = end.deprecatedNode()->traverseNextSibling();
352 beyondEnd = end.deprecatedNode()->traverseNextNode();
354 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
355 Node* startNode = start.deprecatedNode();
356 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
357 startNode = startNode->traverseNextNode();
359 // Store away font size before making any changes to the document.
360 // This ensures that changes to one node won't effect another.
361 HashMap<Node*, float> startingFontSizes;
362 for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode())
363 startingFontSizes.set(node, computedFontSize(node));
365 // These spans were added by us. If empty after font size changes, they can be removed.
366 Vector<RefPtr<HTMLElement> > unstyledSpans;
368 Node* lastStyledNode = 0;
369 for (Node* node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
370 RefPtr<HTMLElement> element;
371 if (node->isHTMLElement()) {
372 // Only work on fully selected nodes.
373 if (!nodeFullySelected(node, start, end))
375 element = toHTMLElement(node);
376 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
377 // Last styled node was not parent node of this text node, but we wish to style this
378 // text node. To make this possible, add a style span to surround this text node.
379 RefPtr<HTMLElement> span = createStyleSpanElement(document());
380 surroundNodeRangeWithElement(node, node, span.get());
381 element = span.release();
383 // Only handle HTML elements and text nodes.
386 lastStyledNode = node;
388 CSSMutableStyleDeclaration* inlineStyleDecl = element->getInlineStyleDecl();
389 float currentFontSize = computedFontSize(node);
390 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
391 RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSSPropertyFontSize);
393 inlineStyleDecl->removeProperty(CSSPropertyFontSize, true);
394 currentFontSize = computedFontSize(node);
396 if (currentFontSize != desiredFontSize) {
397 inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false);
398 setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText());
400 if (inlineStyleDecl->isEmpty()) {
401 removeNodeAttribute(element.get(), styleAttr);
402 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
403 unstyledSpans.append(element.release());
407 size_t size = unstyledSpans.size();
408 for (size_t i = 0; i < size; ++i)
409 removeNodePreservingChildren(unstyledSpans[i].get());
412 static Node* dummySpanAncestorForNode(const Node* node)
414 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node))))
415 node = node->parentNode();
417 return node ? node->parentNode() : 0;
420 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
422 if (!dummySpanAncestor)
425 // Dummy spans are created when text node is split, so that style information
426 // can be propagated, which can result in more splitting. If a dummy span gets
427 // cloned/split, the new node is always a sibling of it. Therefore, we scan
428 // all the children of the dummy's parent
430 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
431 next = node->nextSibling();
432 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
433 removeNodePreservingChildren(node);
438 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
440 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
441 // In that case, we return the unsplit ancestor. Otherwise, we return 0.
442 Node* block = enclosingBlock(node);
446 Node* highestAncestorWithUnicodeBidi = 0;
447 Node* nextHighestAncestorWithUnicodeBidi = 0;
448 int highestAncestorUnicodeBidi = 0;
449 for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
450 int unicodeBidi = getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi);
451 if (unicodeBidi && unicodeBidi != CSSValueNormal) {
452 highestAncestorUnicodeBidi = unicodeBidi;
453 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
454 highestAncestorWithUnicodeBidi = n;
458 if (!highestAncestorWithUnicodeBidi)
461 HTMLElement* unsplitAncestor = 0;
463 WritingDirection highestAncestorDirection;
464 if (allowedDirection != NaturalWritingDirection
465 && highestAncestorUnicodeBidi != CSSValueBidiOverride
466 && highestAncestorWithUnicodeBidi->isHTMLElement()
467 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
468 && highestAncestorDirection == allowedDirection) {
469 if (!nextHighestAncestorWithUnicodeBidi)
470 return toHTMLElement(highestAncestorWithUnicodeBidi);
472 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
473 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
476 // Split every ancestor through highest ancestor with embedding.
479 Element* parent = static_cast<Element*>(n->parentNode());
480 if (before ? n->previousSibling() : n->nextSibling())
481 splitElement(parent, before ? n : n->nextSibling());
482 if (parent == highestAncestorWithUnicodeBidi)
486 return unsplitAncestor;
489 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
491 Node* block = enclosingBlock(node);
496 for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) {
497 parent = n->parentNode();
498 if (!n->isStyledElement())
501 StyledElement* element = static_cast<StyledElement*>(n);
502 int unicodeBidi = getIdentifierValue(computedStyle(element).get(), CSSPropertyUnicodeBidi);
503 if (!unicodeBidi || unicodeBidi == CSSValueNormal)
506 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
507 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
508 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
509 // otherwise it sets the property in the inline style declaration.
510 if (element->hasAttribute(dirAttr)) {
511 // FIXME: If this is a BDO element, we should probably just remove it if it has no
512 // other attributes, like we (should) do with B and I elements.
513 removeNodeAttribute(element, dirAttr);
515 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
516 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
517 inlineStyle->removeProperty(CSSPropertyDirection);
518 setNodeAttribute(element, styleAttr, inlineStyle->cssText());
519 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
520 removeNodePreservingChildren(element);
525 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
527 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
528 if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
535 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
537 Node* startDummySpanAncestor = 0;
538 Node* endDummySpanAncestor = 0;
540 // update document layout once before removing styles
541 // so that we avoid the expense of updating before each and every call
542 // to check a computed style
545 // adjust to the positions we want to use for applying style
546 Position start = startPosition();
547 Position end = endPosition();
548 if (comparePositions(end, start) < 0) {
549 Position swap = start;
554 // split the start node and containing element if the selection starts inside of it
555 bool splitStart = isValidCaretPositionInTextNode(start);
557 if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
558 splitTextElementAtStart(start, end);
560 splitTextAtStart(start, end);
561 start = startPosition();
563 startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
566 // split the end node and containing element if the selection ends inside of it
567 bool splitEnd = isValidCaretPositionInTextNode(end);
569 if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
570 splitTextElementAtEnd(start, end);
572 splitTextAtEnd(start, end);
573 start = startPosition();
575 endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
578 // Remove style from the selection.
579 // Use the upstream position of the start for removing style.
580 // This will ensure we remove all traces of the relevant styles from the selection
581 // and prevent us from adding redundant ones, as described in:
582 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
583 Position removeStart = start.upstream();
584 WritingDirection textDirection = NaturalWritingDirection;
585 bool hasTextDirection = style->textDirection(textDirection);
586 RefPtr<EditingStyle> styleWithoutEmbedding;
587 RefPtr<EditingStyle> embeddingStyle;
588 if (hasTextDirection) {
589 // Leave alone an ancestor that provides the desired single level embedding, if there is one.
590 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
591 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
592 removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
593 removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);
595 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
596 Position embeddingRemoveStart = removeStart;
597 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
598 embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
600 Position embeddingRemoveEnd = end;
601 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
602 embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
604 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
605 styleWithoutEmbedding = style->copy();
606 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
608 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
609 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
613 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
614 start = startPosition();
616 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
619 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
620 start = startPosition();
625 mergeEndWithNextIfIdentical(start, end);
626 start = startPosition();
630 // update document layout once before running the rest of the function
631 // so that we avoid the expense of updating before each and every call
632 // to check a computed style
635 RefPtr<EditingStyle> styleToApply = style;
636 if (hasTextDirection) {
637 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
638 Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
639 Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));
641 if (embeddingStartNode || embeddingEndNode) {
642 Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
643 Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
644 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
646 if (!embeddingStyle) {
647 styleWithoutEmbedding = style->copy();
648 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
650 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
652 styleToApply = styleWithoutEmbedding;
656 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
658 // Remove dummy style spans created by splitting text elements.
659 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
660 if (endDummySpanAncestor != startDummySpanAncestor)
661 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
664 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
666 Node* startNode = start.deprecatedNode();
668 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
669 startNode = startNode->traverseNextNode();
670 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
674 Node* pastEndNode = end.deprecatedNode();
675 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
676 pastEndNode = end.deprecatedNode()->traverseNextSibling();
678 // FIXME: Callers should perform this operation on a Range that includes the br
679 // if they want style applied to the empty line.
680 if (start == end && start.deprecatedNode()->hasTagName(brTag))
681 pastEndNode = start.deprecatedNode()->traverseNextNode();
683 // Start from the highest fully selected ancestor so that we can modify the fully selected node.
684 // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
685 // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
686 RefPtr<Range> range = Range::create(startNode->document(), start, end);
687 Element* editableRoot = startNode->rootEditableElement();
688 if (startNode != editableRoot) {
689 while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get()))
690 startNode = startNode->parentNode();
693 applyInlineStyleToNodeRange(style, startNode, pastEndNode);
696 static bool containsNonEditableRegion(Node* node)
698 if (!node->rendererIsEditable())
701 Node* sibling = node->traverseNextSibling();
702 for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = descendent->traverseNextNode()) {
703 if (!descendent->rendererIsEditable())
710 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* node, Node* pastEndNode)
715 for (RefPtr<Node> next; node && node != pastEndNode; node = next.get()) {
716 next = node->traverseNextNode();
718 if (!node->renderer() || !node->rendererIsEditable())
721 if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
722 // This is a plaintext-only region. Only proceed if it's fully selected.
723 // pastEndNode is the node after the last fully selected node, so if it's inside node then
724 // node isn't fully selected.
725 if (pastEndNode && pastEndNode->isDescendantOf(node))
727 // Add to this element's inline style and skip over its contents.
728 HTMLElement* element = toHTMLElement(node);
729 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
730 inlineStyle->merge(style->style());
731 setNodeAttribute(element, styleAttr, inlineStyle->cssText());
732 next = node->traverseNextSibling();
739 if (node->childNodeCount()) {
740 if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->rendererIsEditable())
742 if (editingIgnoresContent(node)) {
743 next = node->traverseNextSibling();
748 RefPtr<Node> runStart = node;
749 RefPtr<Node> runEnd = node;
750 Node* sibling = node->nextSibling();
751 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode)
752 && (!isBlock(sibling) || sibling->hasTagName(brTag))
753 && !containsNonEditableRegion(sibling)) {
755 sibling = runEnd->nextSibling();
757 next = runEnd->traverseNextSibling();
759 if (!removeStyleFromRunBeforeApplyingStyle(style, runStart, runEnd))
761 addInlineStyleIfNeeded(style, runStart.get(), runEnd.get(), AddStyledElement);
765 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
767 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
768 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
771 bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd)
773 ASSERT(runStart && runEnd && runStart->parentNode() == runEnd->parentNode());
774 RefPtr<Node> pastEndNode = runEnd->traverseNextSibling();
775 bool needToApplyStyle = false;
776 for (Node* node = runStart.get(); node && node != pastEndNode.get(); node = node->traverseNextNode()) {
777 if (node->childNodeCount())
779 // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
780 if (!style->styleIsPresentInComputedStyleOfNode(node)
781 || (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))) {
782 needToApplyStyle = true;
786 if (!needToApplyStyle)
789 RefPtr<Node> next = runStart;
790 for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
791 next = node->traverseNextNode();
792 if (!node->isHTMLElement())
795 RefPtr<Node> previousSibling = node->previousSibling();
796 RefPtr<Node> nextSibling = node->nextSibling();
797 RefPtr<ContainerNode> parent = node->parentNode();
798 removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways);
799 if (!node->inDocument()) {
800 // FIXME: We might need to update the start and the end of current selection here but need a test.
801 if (runStart == node)
802 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
804 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
811 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
815 if (!element->parentNode() || !element->parentNode()->rendererIsEditable())
818 if (isStyledInlineElementToRemove(element.get())) {
819 if (mode == RemoveNone)
821 ASSERT(extractedStyle);
822 extractedStyle->mergeInlineStyleOfElement(element.get());
823 removeNodePreservingChildren(element);
827 bool removed = false;
828 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
831 if (!element->inDocument())
834 // If the node was converted to a span, the span may still contain relevant
835 // styles which must be removed (e.g. <b style='font-weight: bold'>)
836 if (removeCSSStyle(style, element.get(), mode, extractedStyle))
842 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
844 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
845 removeNodePreservingChildren(elem);
847 HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem);
848 ASSERT(newSpanElement && newSpanElement->inDocument());
849 elem = newSpanElement;
853 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
856 if (mode == RemoveNone) {
857 ASSERT(!extractedStyle);
858 return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
861 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
862 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
863 replaceWithSpanOrRemoveIfWithoutAttributes(element);
867 // unicode-bidi and direction are pushed down separately so don't push down with other styles
868 Vector<QualifiedName> attributes;
869 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
870 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
873 for (size_t i = 0; i < attributes.size(); i++)
874 removeNodeAttribute(element, attributes[i]);
876 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element))
877 removeNodePreservingChildren(element);
882 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
887 if (mode == RemoveNone)
888 return style->conflictsWithInlineStyleOfElement(element);
890 Vector<CSSPropertyID> properties;
891 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
894 CSSMutableStyleDeclaration* inlineStyle = element->inlineStyleDecl();
896 // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
897 for (size_t i = 0; i < properties.size(); i++)
898 removeCSSProperty(element, properties[i]);
900 // No need to serialize <foo style=""> if we just removed the last css property
901 if (inlineStyle->isEmpty())
902 removeNodeAttribute(element, styleAttr);
904 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
905 removeNodePreservingChildren(element);
910 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
915 HTMLElement* result = 0;
916 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));
918 for (Node *n = node; n; n = n->parentNode()) {
919 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
920 result = toHTMLElement(n);
921 // Should stop at the editable root (cannot cross editing boundary) and
922 // also stop at the unsplittable element to be consistent with other UAs
923 if (n == unsplittableElement)
930 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
934 if (!style || style->isEmpty() || !node->renderer())
937 RefPtr<EditingStyle> newInlineStyle = style;
938 if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->inlineStyleDecl()) {
939 newInlineStyle = style->copy();
940 newInlineStyle->mergeInlineStyleOfElement(static_cast<HTMLElement*>(node));
943 // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
944 // FIXME: applyInlineStyleToRange should be used here instead.
945 if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
946 setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->style()->cssText());
950 if (node->renderer()->isText() && static_cast<RenderText*>(node->renderer())->isAllCollapsibleWhitespace())
953 // We can't wrap node with the styled element here because new styled element will never be removed if we did.
954 // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
955 // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
956 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
959 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
961 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
962 if (!highestAncestor)
965 // The outer loop is traversing the tree vertically from highestAncestor to targetNode
966 Node* current = highestAncestor;
967 // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
968 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
969 Vector<RefPtr<Element> > elementsToPushDown;
970 while (current != targetNode) {
972 ASSERT(current->contains(targetNode));
973 Node* child = current->firstChild();
974 Node* lastChild = current->lastChild();
975 RefPtr<StyledElement> styledElement;
976 if (current->isStyledElement() && isStyledInlineElementToRemove(static_cast<Element*>(current))) {
977 styledElement = static_cast<StyledElement*>(current);
978 elementsToPushDown.append(styledElement);
981 RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
982 if (current->isHTMLElement())
983 removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get());
985 // The inner loop will go through children on each level
986 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
988 Node* nextChild = child->nextSibling();
990 if (!child->contains(targetNode) && elementsToPushDown.size()) {
991 for (size_t i = 0; i < elementsToPushDown.size(); i++) {
992 RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
993 ExceptionCode ec = 0;
994 wrapper->removeAttribute(styleAttr, ec);
996 surroundNodeRangeWithElement(child, child, wrapper);
1000 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
1001 // But if we've removed styledElement then go ahead and always apply the style.
1002 if (child != targetNode || styledElement)
1003 applyInlineStyleToPushDown(child, styleToPushDown.get());
1005 // We found the next node for the outer loop (contains targetNode)
1006 // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1007 if (child == targetNode || child->contains(targetNode))
1010 if (child == lastChild || child->contains(lastChild))
1017 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
1019 ASSERT(start.isNotNull());
1020 ASSERT(end.isNotNull());
1021 ASSERT(start.anchorNode()->inDocument());
1022 ASSERT(end.anchorNode()->inDocument());
1023 ASSERT(comparePositions(start, end) <= 0);
1025 Position pushDownStart = start.downstream();
1026 // If the pushDownStart is at the end of a text node, then this node is not fully selected.
1027 // Move it to the next deep quivalent position to avoid removing the style from this node.
1028 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1029 Node* pushDownStartContainer = pushDownStart.containerNode();
1030 if (pushDownStartContainer && pushDownStartContainer->isTextNode()
1031 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
1032 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
1033 Position pushDownEnd = end.upstream();
1035 pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
1036 pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());
1038 // The s and e variables store the positions used to set the ending selection after style removal
1039 // takes place. This will help callers to recognize when either the start node or the end node
1040 // are removed from the document during the work of this function.
1041 // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
1042 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
1043 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
1044 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
1046 Node* node = start.deprecatedNode();
1048 RefPtr<Node> next = node->traverseNextNode();
1049 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1050 RefPtr<HTMLElement> elem = toHTMLElement(node);
1051 RefPtr<Node> prev = elem->traversePreviousNodePostOrder();
1052 RefPtr<Node> next = elem->traverseNextNode();
1053 RefPtr<EditingStyle> styleToPushDown;
1054 RefPtr<Node> childNode;
1055 if (isStyledInlineElementToRemove(elem.get())) {
1056 styleToPushDown = EditingStyle::create();
1057 childNode = elem->firstChild();
1060 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
1061 if (!elem->inDocument()) {
1062 if (s.deprecatedNode() == elem) {
1063 // Since elem must have been fully selected, and it is at the start
1064 // of the selection, it is clear we can set the new s offset to 0.
1065 ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
1066 s = firstPositionInOrBeforeNode(next.get());
1068 if (e.deprecatedNode() == elem) {
1069 // Since elem must have been fully selected, and it is at the end
1070 // of the selection, it is clear we can set the new e offset to
1071 // the max range offset of prev.
1072 ASSERT(s.anchorType() == Position::PositionIsAfterAnchor
1073 || s.offsetInContainerNode() >= lastOffsetInNode(s.containerNode()));
1074 e = lastPositionInOrAfterNode(prev.get());
1078 if (styleToPushDown) {
1079 for (; childNode; childNode = childNode->nextSibling())
1080 applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
1083 if (node == end.deprecatedNode())
1088 updateStartEnd(s, e);
1091 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
1094 ASSERT(node->isElementNode());
1096 return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
1097 && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
1100 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
1103 ASSERT(node->isElementNode());
1105 bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0;
1106 bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0;
1108 return isFullyBeforeStart || isFullyAfterEnd;
1111 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
1113 ASSERT(start.containerNode()->isTextNode());
1116 if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
1117 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1121 RefPtr<Text> text = start.containerText();
1122 splitTextNode(text, start.offsetInContainerNode());
1123 updateStartEnd(firstPositionInNode(text.get()), newEnd);
1126 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
1128 ASSERT(end.containerNode()->isTextNode());
1130 bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
1131 Text* text = static_cast<Text *>(end.deprecatedNode());
1132 splitTextNode(text, end.offsetInContainerNode());
1134 Node* prevNode = text->previousSibling();
1135 if (!prevNode || !prevNode->isTextNode())
1138 Position newStart = shouldUpdateStart ? Position(static_cast<Text*>(prevNode), start.offsetInContainerNode()) : start;
1139 updateStartEnd(newStart, lastPositionInNode(prevNode));
1142 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
1144 ASSERT(start.containerNode()->isTextNode());
1147 if (start.containerNode() == end.containerNode())
1148 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1152 splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode());
1153 updateStartEnd(positionBeforeNode(start.containerNode()), newEnd);
1156 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
1158 ASSERT(end.containerNode()->isTextNode());
1160 bool shouldUpdateStart = start.containerNode() == end.containerNode();
1161 splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode());
1163 Node* parentElement = end.containerNode()->parentNode();
1164 if (!parentElement || !parentElement->previousSibling())
1166 Node* firstTextNode = parentElement->previousSibling()->lastChild();
1167 if (!firstTextNode || !firstTextNode->isTextNode())
1170 Position newStart = shouldUpdateStart ? Position(static_cast<Text*>(firstTextNode), start.offsetInContainerNode()) : start;
1171 updateStartEnd(newStart, positionAfterNode(firstTextNode));
1174 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
1176 if (!element || !element->isHTMLElement())
1179 return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
1182 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
1184 Node* node = position.containerNode();
1185 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
1187 int offsetInText = position.offsetInContainerNode();
1188 return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
1191 static bool areIdenticalElements(Node *first, Node *second)
1193 // check that tag name and all attribute names and values are identical
1195 if (!first->isElementNode())
1198 if (!second->isElementNode())
1201 Element *firstElement = static_cast<Element *>(first);
1202 Element *secondElement = static_cast<Element *>(second);
1204 if (!firstElement->tagQName().matches(secondElement->tagQName()))
1207 NamedNodeMap *firstMap = firstElement->attributes();
1208 NamedNodeMap *secondMap = secondElement->attributes();
1210 unsigned firstLength = firstMap->length();
1212 if (firstLength != secondMap->length())
1215 for (unsigned i = 0; i < firstLength; i++) {
1216 Attribute *attribute = firstMap->attributeItem(i);
1217 Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name());
1219 if (!secondAttribute || attribute->value() != secondAttribute->value())
1226 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
1228 Node* startNode = start.containerNode();
1229 int startOffset = start.computeOffsetInContainerNode();
1233 if (isAtomicNode(startNode)) {
1234 // note: prior siblings could be unrendered elements. it's silly to miss the
1235 // merge opportunity just for that.
1236 if (startNode->previousSibling())
1239 startNode = startNode->parentNode();
1243 if (!startNode->isElementNode())
1246 Node* previousSibling = startNode->previousSibling();
1248 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1249 Element* previousElement = static_cast<Element*>(previousSibling);
1250 Element* element = static_cast<Element*>(startNode);
1251 Node* startChild = element->firstChild();
1253 mergeIdenticalElements(previousElement, element);
1255 int startOffsetAdjustment = startChild->nodeIndex();
1256 int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
1257 updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
1258 Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
1265 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
1267 Node* endNode = end.containerNode();
1268 int endOffset = end.computeOffsetInContainerNode();
1270 if (isAtomicNode(endNode)) {
1271 if (endOffset < lastOffsetInNode(endNode))
1274 unsigned parentLastOffset = end.deprecatedNode()->parentNode()->childNodes()->length() - 1;
1275 if (end.deprecatedNode()->nextSibling())
1278 endNode = end.deprecatedNode()->parentNode();
1279 endOffset = parentLastOffset;
1282 if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1285 Node* nextSibling = endNode->nextSibling();
1286 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1287 Element* nextElement = static_cast<Element *>(nextSibling);
1288 Element* element = static_cast<Element *>(endNode);
1289 Node* nextChild = nextElement->firstChild();
1291 mergeIdenticalElements(element, nextElement);
1293 bool shouldUpdateStart = start.containerNode() == endNode;
1294 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1295 updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
1296 Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
1303 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
1305 ASSERT(passedStartNode);
1307 ASSERT(elementToInsert);
1308 RefPtr<Node> startNode = passedStartNode;
1309 RefPtr<Element> element = elementToInsert;
1311 insertNodeBefore(element, startNode);
1313 RefPtr<Node> node = startNode;
1315 RefPtr<Node> next = node->nextSibling();
1316 if (node->isContentEditable()) {
1318 appendNode(node, element);
1320 if (node == endNode)
1325 RefPtr<Node> nextSibling = element->nextSibling();
1326 RefPtr<Node> previousSibling = element->previousSibling();
1327 if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
1328 && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get())))
1329 mergeIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get()));
1331 if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
1332 Node* mergedElement = previousSibling->nextSibling();
1333 if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
1334 && areIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement)))
1335 mergeIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement));
1338 // FIXME: We should probably call updateStartEnd if the start or end was in the node
1339 // range so that the endingSelection() is canonicalized. See the comments at the end of
1340 // VisibleSelection::validate().
1343 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
1345 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1350 String cssText = styleChange.cssStyle();
1351 CSSMutableStyleDeclaration* decl = block->inlineStyleDecl();
1353 cssText += decl->cssText();
1354 setNodeAttribute(block, styleAttr, cssText);
1357 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
1359 if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
1361 RefPtr<Node> startNode = passedStart;
1362 RefPtr<Node> endNode = passedEnd;
1364 // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
1365 RefPtr<HTMLElement> dummyElement;
1366 Position positionForStyleComparison;
1367 if (!startNode->isElementNode()) {
1368 dummyElement = createStyleSpanElement(document());
1369 insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
1370 positionForStyleComparison = positionBeforeNode(dummyElement.get());
1372 positionForStyleComparison = firstPositionInOrBeforeNode(startNode.get());
1374 StyleChange styleChange(style, positionForStyleComparison);
1377 removeNode(dummyElement);
1379 // Find appropriate font and span elements top-down.
1380 HTMLElement* fontContainer = 0;
1381 HTMLElement* styleContainer = 0;
1382 for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
1383 if (container->isHTMLElement() && container->hasTagName(fontTag))
1384 fontContainer = toHTMLElement(container);
1385 bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag);
1386 if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount())))
1387 styleContainer = toHTMLElement(container);
1388 if (!container->firstChild())
1390 startNode = container->firstChild();
1391 endNode = container->lastChild();
1394 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1395 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1396 if (fontContainer) {
1397 if (styleChange.applyFontColor())
1398 setNodeAttribute(fontContainer, colorAttr, styleChange.fontColor());
1399 if (styleChange.applyFontFace())
1400 setNodeAttribute(fontContainer, faceAttr, styleChange.fontFace());
1401 if (styleChange.applyFontSize())
1402 setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize());
1404 RefPtr<Element> fontElement = createFontElement(document());
1405 if (styleChange.applyFontColor())
1406 fontElement->setAttribute(colorAttr, styleChange.fontColor());
1407 if (styleChange.applyFontFace())
1408 fontElement->setAttribute(faceAttr, styleChange.fontFace());
1409 if (styleChange.applyFontSize())
1410 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
1411 surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1415 if (styleChange.cssStyle().length()) {
1416 if (styleContainer) {
1417 CSSMutableStyleDeclaration* existingStyle = toHTMLElement(styleContainer)->inlineStyleDecl();
1419 setNodeAttribute(styleContainer, styleAttr, existingStyle->cssText() + styleChange.cssStyle());
1421 setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle());
1423 RefPtr<Element> styleElement = createStyleSpanElement(document());
1424 styleElement->setAttribute(styleAttr, styleChange.cssStyle());
1425 surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
1429 if (styleChange.applyBold())
1430 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
1432 if (styleChange.applyItalic())
1433 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
1435 if (styleChange.applyUnderline())
1436 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
1438 if (styleChange.applyLineThrough())
1439 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));
1441 if (styleChange.applySubscript())
1442 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
1443 else if (styleChange.applySuperscript())
1444 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
1446 if (m_styledInlineElement && addStyledElement == AddStyledElement)
1447 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
1450 float ApplyStyleCommand::computedFontSize(Node* node)
1455 RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node);
1459 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
1463 return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1466 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
1471 Position newStart = start;
1472 Position newEnd = end;
1474 Node* child = node->firstChild();
1476 Node* next = child->nextSibling();
1477 if (child->isTextNode() && next && next->isTextNode()) {
1478 Text* childText = static_cast<Text *>(child);
1479 Text* nextText = static_cast<Text *>(next);
1480 if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
1481 newStart = Position(childText, childText->length() + start.offsetInContainerNode());
1482 if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
1483 newEnd = Position(childText, childText->length() + end.offsetInContainerNode());
1484 String textToMove = nextText->data();
1485 insertTextIntoNode(childText, childText->length(), textToMove);
1487 // don't move child node pointer. it may want to merge with more text nodes.
1490 child = child->nextSibling();
1494 updateStartEnd(newStart, newEnd);